├── symi.png
├── captures
├── 1.png
├── 2.png
└── 3.png
├── LICENSE
├── index.html
├── README.md
├── temperature-map-gl.min.js
└── temperature-map-gl.js
/symi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ham-systems/temperature-map-gl/HEAD/symi.png
--------------------------------------------------------------------------------
/captures/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ham-systems/temperature-map-gl/HEAD/captures/1.png
--------------------------------------------------------------------------------
/captures/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ham-systems/temperature-map-gl/HEAD/captures/2.png
--------------------------------------------------------------------------------
/captures/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ham-systems/temperature-map-gl/HEAD/captures/3.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Lefteris Chatzipetrou
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | temperature-map-js demo page
4 |
5 |
17 |
18 |
45 |
46 |
47 |
48 |

49 |
50 |
51 |
52 |

53 |
54 |
55 |
56 |

57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Minimalist Library to draw temperature maps (heat maps) using WebGL in pure Javascript. Except a O(N) pre-process step which is done in Javascipt, all calculations and drawing are done with shaders in WebGL, so it is pretty fast. It is also very small (~3kB minified and gzipped)
2 |
3 | 'OES_texture_float' extension is required.
4 |
5 | ### Using the library
6 |
7 | #### HTML
8 | ```html
9 | ...
10 |
11 | ...
12 |
13 |

14 |
15 | ```
16 |
17 | #### Javascript
18 | ```js
19 | var image = document.getElementById("map-image0");
20 | var temperature_map = new temperature_map_gl(image);
21 | temperature_map.set_points(points);
22 | temperature_map.draw();
23 | ```
24 |
25 | points are in this format:
26 | ```js
27 | var points = [
28 | [x0,y0,v0],
29 | [x1,y1,v1],
30 | ...
31 | [xN,yN,vN]
32 | ]
33 | ```
34 |
35 | #### Available options (the defaults are shown)
36 | ```js
37 | var temperature_map = new temperature_map_gl(image), {
38 | p: 3, // used in calculating the IDW values, see wikipedia article mentioned at the bottom of this
39 | canvas: null, //use this canvas element and don't create a new one
40 | opacity: 0.5,// opacity of the canvas
41 | range_factor: 0.00390625,//used in scaling the values so they don't clip when storing them as channels of the framebuffer texture
42 | gamma: 2.2,//used in altering the color during draw pass
43 | brightness: 0.00,//used in brightening the color during draw pass
44 | show_points: false,//add
45 | framebuffer_factor: 1.0,//the ratio of the dimensions of the calculation framebuffer in relation to the actual canvas
46 | image_zindex: 0,//style z-index given to the image
47 | point_text: function(val) {//used when the show_points is true of the draw_points() method is called explicitly. It returns the text on the points shown for given value val
48 | var v;
49 | if(val < 1)
50 | v = val.toFixed(2);
51 | else if(val < 10)
52 | v = val.toFixed(1);
53 | else
54 | v = Math.round(val);
55 | return v + "°C";
56 | }
57 | };
58 | ```
59 |
60 | ### Methods
61 |
62 | ```js
63 | //constructor
64 | temperature_map_gl(image_element[, options]);
65 |
66 | //update some options, not all constructor options will have an effect..
67 | temperature_map.update_options(options);
68 |
69 | //sets points in the format mentioned above
70 | //the optional arguments determine what is min = blue, what is max = red, normal = green
71 | temperature_map.set_points(points[, min, max, normal_val]);
72 |
73 | //performs a calculation and draw given the points set
74 | temperature_map.draw();
75 |
76 | //explicitly draw markers on points, like using the show_points option
77 | temperature_map.draw_points()
78 |
79 | //explicitly hide markers
80 | temperature_map.hide_points();
81 |
82 | //resize canvas
83 | temperature_map.resize(width, height);
84 |
85 | //removes all created elements
86 | temperature_map.destroy();
87 |
88 | //returns if all the requirements are met (webgl and OES_texture_float), if this returns false, draws are no-ops
89 | is_supported();
90 | ```
91 |
92 | ### Examples
93 |
94 | You can check it out live at [chpetrou.net](http://chpetrou.net/temperature-map-js/)
95 |
96 | ### Technical explanation
97 |
98 | Values are calculated using 'Inverse Distance Weighting (IDW)' algorithm:
99 |
100 | [Wikipedia - Inverse Distance Weighting](https://en.wikipedia.org/wiki/Inverse_distance_weighting)
101 |
102 | The rest of the explanation makes sense only in the context of the wikipedia article above...
103 |
104 | For every point, we perform a render pass to a texture. Using IDW, we calculate the point "influence" to every fragment using a fragment shader. We store the ui*wi at the r channel of the texture and w_i at the g channel. Using blending with "accumulator" configuration, we end end up with a texture, where we have the top sum of IDW in r channel, and the bottom sum at the g channel. Since channels WebGL are clamped in [0,1], we multiply both channels with range_factor to avoid clamping.
105 |
106 | At last, we perform a last pass where we get the IDW value by reading the calculation texture and do a r/g at every fragment. We then use this value to determine the color of the fragment.
107 |
108 | More on the technical side on [my website](http://chpetrou.net/en/temperature-map-gl-js-minimalist-pure-javascript-heat-map-library-using-webgl-shaders/)
109 |
110 | Used on [HAM Systems IoT platform](https://hamsystems.eu) for heatmaps over floorplans for visualization
111 |
--------------------------------------------------------------------------------
/temperature-map-gl.min.js:
--------------------------------------------------------------------------------
1 | //! temperature-map-gl.js
2 | //! version : 0.5.0
3 | //! authors : Lefteris Chatzipetrou
4 | //! license : MIT
5 | //! http://chpetrou.net
6 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):t.temperature_map_gl=i()}(this,function(){"use strict";function t(t,i,e){if("fragment"==e)e=t.FRAGMENT_SHADER;else{if("vertex"!=e)return null;e=t.VERTEX_SHADER}var r=t.createShader(e);return(t.shaderSource(r,i),t.compileShader(r),t.getShaderParameter(r,t.COMPILE_STATUS))?r:(logger.info("An error occurred compiling the shaders: "+t.getShaderInfoLog(r)),t.deleteShader(r),null)}function i(t,i,e){var r=t.createProgram();return t.attachShader(r,i),t.attachShader(r,e),t.linkProgram(r),t.getProgramParameter(r,t.LINK_STATUS)||logger.info("Unable to initialize the shader program: "+t.getProgramInfoLog(r)),r}var e={p:1,canvas:null,opacity:.85,range_factor:.00390625,gamma:1,show_points:!1,framebuffer_factor:1,image_zindex:0,dist_factor:1,unit:"\xb0C",id:"",point_text:function(t){var i;return 1>Math.abs(t)?t.toFixed(2):10>Math.abs(t)?t.toFixed(1):Math.round(t)},color_map:[[-50,"#cbecff"],[-40,"#8998c7"],[-32,"#875aa7"],[-25,"#821d7c"],[-18,"#002258"],[-14,"#193b95"],[-10,"#124c9f"],[-6,"#0a60a8"],[-2,"#0078b5"],[2,"#33b6c6"],[6,"#5ac8c6"],[10,"#96dba6"],[14,"#76db8e"],[18,"#5ddb7a"],[22,"#4cdb6d"],[24,"#8bdb4c"],[26,"#bedb4c"],[28,"#eed371"],[30,"#eec42b"],[32,"#eea02b"],[35,"#ee810f"],[38,"#ee590f"],[40,"#ff3c1a"],[43,"#ff3800"],[47,"#ff1700"],[50,"#db0000"],[55,"#ad0000"],[60,"#6c0000"],[70,"#380000"],[80,"#1c0000"],[90,"#8700ff"],[100,"#ff00ed"],]},r=0;function o(t,i){var o={};for(var a in e)o[a]=e[a];if("object"==typeof i)for(var a in i)o[a]=i[a];this.instance=r++,this.image_element=t,t.style.zIndex=o.image_zindex;var n=o.canvas||document.createElement("canvas");n.style.position="absolute",n.style.top="0px",n.style.left="0px",n.style.zIndex=t.style.zIndex?""+(Number(t.style.zIndex)+1):"10",n.style.opacity=o.opacity,n.style.pointerEvents="none",n.id=o.id,o.canvas?this.own_canvas=!1:(t.parentNode.insertBefore(n,t.nextSibling),this.own_canvas=!0),this.canvas=n,this.context=this.canvas.getContext("webgl")||this.canvas.getContext("experimental-webgl"),this.context||logger.info("Your browser does not support webgl"),this.context&&this.context.getExtension("OES_texture_half_float")||(logger.info("Your browser does not support float textures"),this.context=null),this.ext=this.context?this.context.getExtension("OES_texture_half_float"):{},this.color_map=o.color_map,this.color_map_texture=this.generate_color_map_texture(this.color_map),this.points=[],this.translated_points=[],this.square_vertices_buffer=null,this.computation_program=null,this.draw_program=null,this.position_attribute=null,this.computation_framebuffer=null,this.computation_texture=null,this.ui_uniform=null,this.xi_uniform=null,this.c_screen_size_uniform=null,this.d_screen_size_uniform=null,this.range_factor_uniform=null,this.dist_factor_uniform=null,this.p_uniform=null,this.computation_texture_uniform=null,this.gamma_uniform=null,this.color_map_uniform=null,this.point_text=o.point_text,this.p=o.p,this.range_factor=o.range_factor,this.dist_factor=o.dist_factor,this.gamma=o.gamma,this.show_points=o.show_points,this.unit=o.unit,this.framebuffer_factor=o.framebuffer_factor,this.computation_framebuffer_width=0,this.computation_framebuffer_height=0,this.init_shaders(),this.resize(this.image_element.clientWidth,this.image_element.clientHeight)}function a(t,i){for(var e=0;en&&(n=h[2]),h[2]a?n-a:1,u=this.canvas.width,m=this.canvas.height,f=0;f"+this.point_text(i[2])+""+this.unit+"",this.canvas.parentNode.insertBefore(e,this.canvas.nextSibling),e.style.left=i[0]-e.clientWidth/2+"px",e.style.top=i[1]-e.clientHeight/2+"px"}},o.prototype.resize=function(t,i){this.canvas.height=i,this.canvas.width=t,this.canvas.style.height=this.canvas.height+"px",this.canvas.style.width=this.canvas.width+"px",this.init_buffers()},o.prototype.destroy=function(){this.own_canvas&&this.canvas.parentNode.removeChild(this.canvas),this.hide_points()},o.prototype.generate_color_map_texture=function(t){if(this.context){for(var i=this.context,e=[],r=t[0][0],o=t[t.length-1][0],s=0;s<128;++s){var f=n(a(r+(o-r)*s/128,t));e.push(f.r),e.push(f.g),e.push(f.b),e.push(1)}var h=i.createTexture();i.bindTexture(i.TEXTURE_2D,h);var c=new Uint8Array(e),u=e.length/4;return i.texImage2D(i.TEXTURE_2D,0,i.RGBA,u,1,0,i.RGBA,i.UNSIGNED_BYTE,c),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.NEAREST),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),h}},o});
--------------------------------------------------------------------------------
/temperature-map-gl.js:
--------------------------------------------------------------------------------
1 | //! temperature-map-gl.js
2 | //! version : 0.5.0
3 | //! authors : Lefteris Chatzipetrou
4 | //! license : MIT
5 | //! http://chpetrou.net
6 |
7 | ;(function (global, factory) {
8 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
9 | typeof define === 'function' && define.amd ? define(factory) :
10 | global.temperature_map_gl = factory()
11 | }(this, (function () { 'use strict';
12 |
13 | var vertex_shader_source = "\
14 | attribute vec2 position; \
15 | \
16 | void main(void) { \
17 | gl_Position = vec4(position.x*2.0-1.0, position.y*2.0-1.0, 1.0, 1.0); \
18 | } \
19 | ";
20 |
21 | var computation_fragment_shader_source = "\
22 | precision highp float; \
23 | \
24 | uniform float ui; \
25 | uniform vec2 xi; \
26 | uniform float p; \
27 | uniform float dist_factor; \
28 | uniform float range_factor; \
29 | uniform vec2 screen_size; \
30 | void main(void) { \
31 | vec2 x = vec2(gl_FragCoord.x/screen_size.x, gl_FragCoord.y/screen_size.y); \
32 | float dist = distance(x, xi)/dist_factor; \
33 | float wi = 1.0/pow(dist, p); \
34 | gl_FragColor = vec4(ui*wi*range_factor, wi*range_factor, 0.0, 1.0); \
35 | } \
36 | ";
37 |
38 | var draw_fragment_shader_source = "\
39 | precision highp float; \
40 | \
41 | uniform sampler2D color_map; \
42 | uniform sampler2D computation_texture; \
43 | uniform vec2 screen_size; \
44 | uniform float gamma; \
45 | void main(void) { \
46 | vec4 data = texture2D(computation_texture, vec2(gl_FragCoord.x/screen_size.x, 1.0-gl_FragCoord.y/screen_size.y)); \
47 | float val = data.x/data.y; \
48 | vec4 color = texture2D(color_map, vec2(val, 0.5));\
49 | gl_FragColor.rgba = pow(color, vec4(1.0/gamma)); \
50 | } \
51 | ";
52 |
53 | function get_shader(gl, source, type){
54 | if(type == 'fragment'){
55 | type = gl.FRAGMENT_SHADER;
56 | }
57 | else if(type == 'vertex'){
58 | type = gl.VERTEX_SHADER;
59 | }
60 | else {
61 | return null;
62 | }
63 |
64 | var shader = gl.createShader(type);
65 | gl.shaderSource(shader, source);
66 | gl.compileShader(shader);
67 |
68 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
69 | logger.info('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
70 | gl.deleteShader(shader);
71 | return null;
72 | }
73 |
74 | return shader;
75 | }
76 |
77 | function get_program(gl, vertex_shader, fragment_shader){
78 | var program = gl.createProgram();
79 | gl.attachShader(program, vertex_shader);
80 | gl.attachShader(program, fragment_shader);
81 | gl.linkProgram(program);
82 |
83 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
84 | logger.info('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program));
85 | }
86 |
87 | return program;
88 | }
89 |
90 | var default_options = {
91 | p: 1,
92 | canvas: null,
93 | opacity: 0.85,
94 | range_factor: 0.00390625,
95 | gamma: 1.0,
96 | show_points: false,
97 | framebuffer_factor: 1,
98 | image_zindex: 0,
99 | dist_factor: 1,
100 | unit: "°C",
101 | id: "",
102 | point_text: function(val) {
103 | var v;
104 | if(Math.abs(val) < 1)
105 | v = val.toFixed(2);
106 | else if(Math.abs(val) < 10)
107 | v = val.toFixed(1);
108 | else
109 | v = Math.round(val);
110 | return v;
111 | },
112 | color_map : [
113 | [-50, "#cbecff"],
114 | [-40, "#8998c7"],
115 | [-32, "#875aa7"],
116 | [-25, "#821d7c"],
117 | [-18, "#002258"],
118 | [-14, "#193b95"],
119 | [-10, "#124c9f"],
120 | [ -6, "#0a60a8"],
121 | [ -2, "#0078b5"],
122 | [ 2, "#33b6c6"],
123 | [ 6, "#5ac8c6"],
124 | [ 10, "#96dba6"],
125 | [ 14, "#76db8e"],
126 | [ 18, "#5ddb7a"],
127 | [ 22, "#4cdb6d"],
128 | [ 24, "#8bdb4c"],
129 | [ 26, "#bedb4c"],
130 | [ 28, "#eed371"],
131 | [ 30, "#eec42b"],
132 | [ 32, "#eea02b"],
133 | [ 35, "#ee810f"],
134 | [ 38, "#ee590f"],
135 | [ 40, "#ff3c1a"],
136 | [ 43, "#ff3800"],
137 | [ 47, "#ff1700"],
138 | [ 50, "#db0000"],
139 | [ 55, "#ad0000"],
140 | [ 60, "#6c0000"],
141 | [ 70, "#380000"],
142 | [ 80, "#1c0000"],
143 | [ 90, "#8700ff"],
144 | [100, "#ff00ed"],
145 | ]
146 | };
147 |
148 | var instance = 0;
149 | function temperature_map_gl(image_element, options){
150 | var _options = {};
151 | for(var k in default_options)
152 | _options[k] = default_options[k];
153 |
154 | if(typeof options === 'object'){
155 | for(var k in options)
156 | _options[k] = options[k];
157 | }
158 |
159 | this.instance = instance++;
160 |
161 | this.image_element = image_element;
162 | image_element.style.zIndex = _options.image_zindex;
163 |
164 | var canvas = _options.canvas || document.createElement('canvas');
165 |
166 | canvas.style.position = 'absolute';
167 | canvas.style.top = '0px';
168 | canvas.style.left = '0px';
169 | canvas.style.zIndex = image_element.style.zIndex?''+(Number(image_element.style.zIndex)+1):'10';
170 | canvas.style.opacity = _options.opacity;
171 | canvas.style.pointerEvents = 'none';
172 | canvas.id = _options.id;
173 |
174 | if(!_options.canvas) {
175 | image_element.parentNode.insertBefore(canvas, image_element.nextSibling);
176 | this.own_canvas = true;
177 | }
178 | else
179 | this.own_canvas = false;
180 | this.canvas = canvas;
181 |
182 | this.context = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
183 | if(!this.context)
184 | logger.info('Your browser does not support webgl');
185 |
186 | if(!this.context || !this.context.getExtension('OES_texture_half_float')){
187 | logger.info('Your browser does not support float textures');
188 | this.context = null;
189 | }
190 |
191 | this.ext = this.context?this.context.getExtension('OES_texture_half_float'):{};
192 | this.color_map = _options.color_map;
193 | this.color_map_texture = this.generate_color_map_texture(this.color_map);
194 |
195 | this.points = [];
196 | this.translated_points = [];
197 | this.square_vertices_buffer = null;
198 | this.computation_program = null;
199 | this.draw_program = null;
200 | this.position_attribute = null;
201 | this.computation_framebuffer = null;
202 | this.computation_texture = null;
203 |
204 | this.ui_uniform = null;
205 | this.xi_uniform = null;
206 | this.c_screen_size_uniform = null;
207 | this.d_screen_size_uniform = null;
208 | this.range_factor_uniform = null;
209 | this.dist_factor_uniform = null;
210 |
211 | this.p_uniform = null;
212 | this.computation_texture_uniform = null;
213 | this.gamma_uniform = null;
214 | this.color_map_uniform = null;
215 |
216 | this.point_text = _options.point_text;
217 | this.p = _options.p;
218 | this.range_factor = _options.range_factor;
219 | this.dist_factor = _options.dist_factor;
220 | this.gamma = _options.gamma;
221 |
222 | this.show_points = _options.show_points;
223 | this.unit = _options.unit;
224 | this.framebuffer_factor = _options.framebuffer_factor;
225 | this.computation_framebuffer_width = 0;
226 | this.computation_framebuffer_height = 0;
227 |
228 | this.init_shaders();
229 | this.resize(this.image_element.clientWidth, this.image_element.clientHeight);
230 | }
231 |
232 | temperature_map_gl.prototype.update_options = function(options){
233 | if(options.p) this.p = options.p;
234 | if(options.range_factor) this.range_factor = options.range_factor;
235 | if(options.dist_factor) this.dist_factor = options.dist_factor;
236 |
237 | if(typeof options.unit !== 'undefined') this.unit = options.unit;
238 | if(options.gamma) this.gamma = options.gamma;
239 | if(options.show_points) this.show_points = options.show_points;
240 | if(options.color_map) {
241 | this.context.deleteTexture(this.color_map_texture);
242 | this.color_map = options.color_map;
243 | this.color_map_texture = this.generate_color_map_texture(this.color_map);
244 | }
245 |
246 | this.draw();
247 | }
248 |
249 | temperature_map_gl.is_supported = function(){
250 | var canvas = document.createElement('canvas');
251 | var context = canvas.getContext('webgl');
252 | return canvas && context && context.getExtension('OES_texture_half_float');
253 | }
254 |
255 | temperature_map_gl.prototype.set_points = function(points, low_val, high_val, normal_val){
256 | this.points = points;
257 | if(!this.context)
258 | return;
259 |
260 | var translated_points = [];
261 | if(points.length){
262 | var min = (typeof low_val !== 'undefined')?Math.min(points[0][2], low_val):points[0][2];
263 | var max = (typeof high_val !== 'undefined')?Math.max(points[0][2], high_val):points[0][2];
264 | var avg = (typeof normal_val !== 'undefined')?normal_val:0;
265 | for(var i=1;i < points.length; ++i){
266 | var p = [points[i][0], points[i][1], points[i][2]];
267 | if(p[2] > max)
268 | max = p[2];
269 | if(p[2] < min)
270 | min = p[2];
271 |
272 | if(typeof normal_val === 'undefined')
273 | avg += p[2]/points.length;
274 | }
275 | var d = max > min?max - min:1;
276 |
277 | var w = this.canvas.width;
278 | var h = this.canvas.height;
279 | for(var i=0;i < points.length; ++i){
280 | var p = [points[i][0], points[i][1], points[i][2]];
281 | p[0] = p[0]/w;
282 | p[1] = p[1]/h;
283 |
284 | if(typeof low_val !== 'undefined' && typeof high_val !== 'undefined' && typeof normal_val !== 'undefined')
285 | p[2] = Math.max(Math.min(1., Math.pow((2*p[2] - low_val - normal_val)/(high_val - low_val),1)), 0.);
286 | else if(typeof low_val !== 'undefined' && typeof high_val !== 'undefined')
287 | p[2] = Math.max(Math.min(1., Math.pow((p[2] - low_val)/(high_val - low_val),1)), 0.);
288 | else
289 | p[2] = Math.max(Math.min(1., Math.pow((2*p[2] - avg - min)/d,1) + 0.5), 0.);
290 |
291 |
292 | translated_points.push(p);
293 | }
294 | }
295 | this.translated_points = translated_points;
296 | }
297 |
298 |
299 | temperature_map_gl.prototype.init_buffers = function() {
300 | if(!this.context)
301 | return;
302 |
303 | var gl = this.context;
304 | this.square_vertices_buffer = gl.createBuffer();
305 | gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer);
306 |
307 | var vertices = [
308 | 1.0, 1.0,
309 | -1.0, 1.0,
310 | 1.0, -1.0,
311 | -1.0, -1.0
312 | ];
313 |
314 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
315 |
316 | this.computation_framebuffer_width = Math.ceil(this.canvas.width*this.framebuffer_factor);
317 | this.computation_framebuffer_height = Math.ceil(this.canvas.height*this.framebuffer_factor);
318 |
319 | this.computation_texture = gl.createTexture();
320 | gl.bindTexture(gl.TEXTURE_2D, this.computation_texture);
321 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
322 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
323 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
324 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
325 |
326 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.computation_framebuffer_width, this.computation_framebuffer_height, 0, gl.RGBA, this.ext.HALF_FLOAT_OES, null);
327 |
328 | this.computation_framebuffer = gl.createFramebuffer();
329 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.computation_framebuffer);
330 | gl.viewport(0, 0, this.computation_framebuffer_width, this.computation_framebuffer_height);
331 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.computation_texture, 0);
332 |
333 | gl.bindTexture(gl.TEXTURE_2D, null);
334 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
335 | }
336 |
337 |
338 | temperature_map_gl.prototype.init_shaders = function(){
339 | if(!this.context)
340 | return;
341 |
342 | var gl = this.context;
343 | var vertex_shader = get_shader(gl, vertex_shader_source, 'vertex');
344 | var computation_fragment_shader = get_shader(gl, computation_fragment_shader_source, 'fragment');
345 | var draw_fragment_shader = get_shader(gl, draw_fragment_shader_source, 'fragment');
346 |
347 | this.computation_program = get_program(gl, vertex_shader, computation_fragment_shader);
348 | this.position_attribute = gl.getAttribLocation(this.computation_program, 'position');
349 | this.ui_uniform = gl.getUniformLocation(this.computation_program, "ui");
350 | this.xi_uniform = gl.getUniformLocation(this.computation_program, "xi");
351 | this.c_screen_size_uniform = gl.getUniformLocation(this.computation_program, "screen_size");
352 | this.range_factor_uniform = gl.getUniformLocation(this.computation_program, "range_factor");
353 | this.dist_factor_uniform = gl.getUniformLocation(this.computation_program, "dist_factor");
354 |
355 | this.p_uniform = gl.getUniformLocation(this.computation_program, "p");
356 | gl.enableVertexAttribArray(this.position_attribute);
357 |
358 | this.draw_program = get_program(gl, vertex_shader, draw_fragment_shader);
359 | this.d_screen_size_uniform = gl.getUniformLocation(this.draw_program, "screen_size");
360 | this.computation_texture_uniform = gl.getUniformLocation(this.draw_program, 'computation_texture');
361 | this.gamma_uniform = gl.getUniformLocation(this.draw_program, 'gamma');
362 | this.color_map_uniform = gl.getUniformLocation(this.computation_program, "color_map");
363 | }
364 |
365 |
366 | temperature_map_gl.prototype.draw = function(){
367 | if(!this.context)
368 | return;
369 |
370 | var gl = this.context;
371 |
372 | gl.disable(gl.DEPTH_TEST);
373 |
374 | gl.enable(gl.BLEND);
375 | gl.blendEquation(gl.FUNC_ADD);
376 | gl.blendFunc(gl.ONE, gl.ONE);
377 |
378 | gl.clearColor(0.0, 0.0, 0.0, 1.0);
379 |
380 | gl.useProgram(this.computation_program);
381 | gl.uniform2f(this.c_screen_size_uniform, this.computation_framebuffer_width, this.computation_framebuffer_height);
382 | gl.uniform1f(this.p_uniform, this.p);
383 | gl.uniform1f(this.range_factor_uniform, this.range_factor);
384 | gl.uniform1f(this.dist_factor_uniform, this.dist_factor);
385 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.computation_framebuffer);
386 | gl.viewport(0, 0, this.computation_framebuffer_width, this.computation_framebuffer_height);
387 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
388 |
389 | for(var i=0; i < this.translated_points.length; ++i){
390 | var p = this.translated_points[i];
391 | gl.uniform2f(this.xi_uniform, p[0], p[1]);
392 | gl.uniform1f(this.ui_uniform, p[2]);
393 |
394 | gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer);
395 | gl.vertexAttribPointer(this.position_attribute, 2, gl.FLOAT, false, 0, 0);
396 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
397 | }
398 |
399 |
400 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
401 | gl.clearColor(0.0, 0.0, 0.0, 1.0);
402 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
403 |
404 | gl.useProgram(this.draw_program);
405 |
406 | gl.uniform1i(this.color_map_uniform, 1);
407 | gl.activeTexture(gl.TEXTURE0);
408 | gl.bindTexture(gl.TEXTURE_2D, this.color_map_texture);
409 |
410 | gl.uniform1i(this.computation_texture_uniform, 1);
411 | gl.activeTexture(gl.TEXTURE1);
412 | gl.bindTexture(gl.TEXTURE_2D, this.computation_texture);
413 |
414 | gl.uniform1f(this.gamma_uniform, this.gamma);
415 | gl.uniform2f(this.d_screen_size_uniform, this.canvas.width, this.canvas.height);
416 | gl.viewport(0, 0, this.canvas.width, this.canvas.height);
417 | gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer);
418 | gl.vertexAttribPointer(this.position_attribute, 2, gl.FLOAT, false, 0, 0);
419 |
420 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
421 |
422 | if(this.show_points)
423 | this.draw_points();
424 | else
425 | this.hide_points();
426 | }
427 |
428 | temperature_map_gl.prototype.hide_points = function(){
429 | var elements = document.getElementsByClassName('tmg-point-'+this.instance);
430 | while(elements[0]) {
431 | elements[0].parentNode.removeChild(elements[0]);
432 | }
433 | }
434 |
435 | temperature_map_gl.prototype.draw_points = function(){
436 | this.hide_points();
437 |
438 | for(var i=0; i < this.points.length; ++i){
439 | var p = this.points[i];
440 | var dot = document.createElement('p');
441 | dot.style.position = 'absolute';
442 | dot.style.zIndex = ''+(Number(this.canvas.style.zIndex)+1);
443 | dot.className = 'tmg-point '+'tmg-point-'+this.instance;
444 | dot.innerHTML = ""+this.point_text(p[2])+""+this.unit+"";
445 | this.canvas.parentNode.insertBefore(dot, this.canvas.nextSibling);
446 | dot.style.left = (p[0]-dot.clientWidth/2) +'px';
447 | dot.style.top = (p[1]-dot.clientHeight/2) +'px';
448 | }
449 | }
450 |
451 |
452 | temperature_map_gl.prototype.resize = function(width, height){
453 | this.canvas.height = height;
454 | this.canvas.width = width;
455 | this.canvas.style.height = this.canvas.height+'px';
456 | this.canvas.style.width = this.canvas.width+'px';
457 | this.init_buffers();
458 | }
459 |
460 |
461 | temperature_map_gl.prototype.destroy = function(){
462 | if(this.own_canvas)
463 | this.canvas.parentNode.removeChild(this.canvas);
464 | this.hide_points();
465 | }
466 |
467 |
468 | function celsius_to_rgb(c, temperature_map) {
469 | for(var i=0; i < temperature_map.length; ++i){
470 | var max_temp = Number(temperature_map[i][0]);
471 | var color = temperature_map[i][1];
472 | if(c <= max_temp)
473 | return color;
474 | }
475 | return temperature_map[temperature_map.length-1][1];
476 | }
477 |
478 | function hex_to_rgb(hex) {
479 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
480 | hex = hex.replace(shorthandRegex, function(m, r, g, b) {
481 | return r + r + g + g + b + b;
482 | });
483 |
484 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
485 | return result ? {
486 | r: parseInt(result[1], 16),
487 | g: parseInt(result[2], 16),
488 | b: parseInt(result[3], 16)
489 | } : null;
490 | }
491 |
492 |
493 | temperature_map_gl.prototype.generate_color_map_texture = function(color_map){
494 | if(!this.context)
495 | return;
496 |
497 | var gl = this.context;
498 | var sample_size = 128;
499 | var ret = [];
500 | var a = color_map[0][0];
501 | var b = color_map[color_map.length-1][0];
502 | for(var i=0; i < sample_size; ++i){
503 | var c = a + (b-a)*i/sample_size;
504 | var color = hex_to_rgb(celsius_to_rgb(c, color_map));
505 | ret.push(color.r);
506 | ret.push(color.g);
507 | ret.push(color.b);
508 | ret.push(1);
509 | }
510 |
511 | var tex = gl.createTexture();
512 | gl.bindTexture(gl.TEXTURE_2D, tex);
513 |
514 | var oneDTextureTexels = new Uint8Array(ret);
515 | var width = ret.length/4;
516 | var height = 1;
517 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, oneDTextureTexels);
518 |
519 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
520 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
521 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
522 |
523 | return tex;
524 | }
525 |
526 | return temperature_map_gl;
527 | })));
--------------------------------------------------------------------------------