Advertisement
Guest User

Untitled

a guest
May 22nd, 2015
217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.08 KB | None | 0 0
  1. /*
  2. * heatmap.js 1.0 - JavaScript Heatmap Library
  3. *
  4. * Copyright (c) 2011, Patrick Wied (http://www.patrick-wied.at)
  5. * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
  6. * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license.
  7. */
  8.  
  9. (function(w){
  10. // the heatmapFactory creates heatmap instances
  11. var heatmapFactory = (function(){
  12.  
  13. // store object constructor
  14. // a heatmap contains a store
  15. // the store has to know about the heatmap in order to trigger heatmap updates when datapoints get added
  16. var store = function store(hmap){
  17.  
  18. var _ = {
  19. // data is a two dimensional array
  20. // a datapoint gets saved as data[point-x-value][point-y-value]
  21. // the value at [point-x-value][point-y-value] is the occurrence of the datapoint
  22. data: [],
  23. // tight coupling of the heatmap object
  24. heatmap: hmap
  25. };
  26. // the max occurrence - the heatmaps radial gradient alpha transition is based on it
  27. this.max = 1;
  28.  
  29. this.get = function(key){
  30. return _[key];
  31. };
  32. this.set = function(key, value){
  33. _[key] = value;
  34. };
  35. }
  36.  
  37. store.prototype = {
  38. // function for adding datapoints to the store
  39. // datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence
  40. addDataPoint: function(x, y){
  41. if(x < 0 || y < 0)
  42. return;
  43.  
  44. var me = this,
  45. heatmap = me.get("heatmap"),
  46. data = me.get("data");
  47.  
  48. if(!data[x])
  49. data[x] = [];
  50.  
  51. if(!data[x][y])
  52. data[x][y] = 0;
  53.  
  54. // if count parameter is set increment by count otherwise by 1
  55. data[x][y]+=(arguments.length<3)?1:arguments[2];
  56.  
  57. me.set("data", data);
  58. // do we have a new maximum?
  59. if(me.max < data[x][y]){
  60. // max changed, we need to redraw all existing(lower) datapoints
  61. heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height"));
  62. me.setDataSet({ max: data[x][y], data: data }, true);
  63. return;
  64. }
  65. heatmap.drawAlpha(x, y, data[x][y], true);
  66. },
  67. setDataSet: function(obj, internal){
  68. var me = this,
  69. heatmap = me.get("heatmap"),
  70. data = [],
  71. d = obj.data,
  72. dlen = d.length;
  73. // clear the heatmap before the data set gets drawn
  74. heatmap.clear();
  75. this.max = obj.max;
  76. // if a legend is set, update it
  77. heatmap.get("legend") && heatmap.get("legend").update(obj.max);
  78.  
  79. if(internal != null && internal){
  80. for(var one in d){
  81. // jump over undefined indexes
  82. if(one === undefined)
  83. continue;
  84. for(var two in d[one]){
  85. if(two === undefined)
  86. continue;
  87. // if both indexes are defined, push the values into the array
  88. heatmap.drawAlpha(one, two, d[one][two], false);
  89. }
  90. }
  91. }else{
  92. while(dlen--){
  93. var point = d[dlen];
  94. heatmap.drawAlpha(point.x, point.y, point.count, false);
  95. if(!data[point.x])
  96. data[point.x] = [];
  97.  
  98. if(!data[point.x][point.y])
  99. data[point.x][point.y] = 0;
  100.  
  101. data[point.x][point.y] = point.count;
  102. }
  103. }
  104. heatmap.colorize();
  105. this.set("data", d);
  106. },
  107. exportDataSet: function(){
  108. var me = this,
  109. data = me.get("data"),
  110. exportData = [];
  111.  
  112. for(var one in data){
  113. // jump over undefined indexes
  114. if(one === undefined)
  115. continue;
  116. for(var two in data[one]){
  117. if(two === undefined)
  118. continue;
  119. // if both indexes are defined, push the values into the array
  120. exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]});
  121. }
  122. }
  123.  
  124. return { max: me.max, data: exportData };
  125. },
  126. generateRandomDataSet: function(points){
  127. var heatmap = this.get("heatmap"),
  128. w = heatmap.get("width"),
  129. h = heatmap.get("height");
  130. var randomset = {},
  131. max = Math.floor(Math.random()*1000+1);
  132. randomset.max = max;
  133. var data = [];
  134. while(points--){
  135. data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)});
  136. }
  137. randomset.data = data;
  138. this.setDataSet(randomset);
  139. }
  140. };
  141.  
  142. var legend = function legend(config){
  143. this.config = config;
  144.  
  145. var _ = {
  146. element: null,
  147. labelsEl: null,
  148. gradientCfg: null,
  149. ctx: null
  150. };
  151. this.get = function(key){
  152. return _[key];
  153. };
  154. this.set = function(key, value){
  155. _[key] = value;
  156. };
  157. this.init();
  158. };
  159. legend.prototype = {
  160. init: function(){
  161. var me = this,
  162. config = me.config,
  163. title = config.title || "Legend",
  164. position = config.position,
  165. offset = config.offset || 10,
  166. gconfig = config.gradient,
  167. labelsEl = document.createElement("ul"),
  168. labelsHtml = "",
  169. grad, element, gradient, positionCss = "";
  170.  
  171. me.processGradientObject();
  172.  
  173. // Positioning
  174.  
  175. // top or bottom
  176. if(position.indexOf('t') > -1){
  177. positionCss += 'top:'+offset+'px;';
  178. }else{
  179. positionCss += 'bottom:'+offset+'px;';
  180. }
  181.  
  182. // left or right
  183. if(position.indexOf('l') > -1){
  184. positionCss += 'left:'+offset+'px;';
  185. }else{
  186. positionCss += 'right:'+offset+'px;';
  187. }
  188.  
  189. element = document.createElement("div");
  190. element.style.cssText = "border-radius:5px;position:absolute;"+positionCss+"font-family:Helvetica; width:256px;z-index:10000000000; background:rgba(255,255,255,1);padding:10px;border:1px solid black;margin:0;";
  191. element.innerHTML = "<h3 style='padding:0;margin:0;text-align:center;font-size:16px;'>"+title+"</h3>";
  192. // create gradient in canvas
  193. labelsEl.style.cssText = "position:relative;font-size:12px;display:block;list-style:none;list-style-type:none;margin:0;height:15px;";
  194.  
  195.  
  196. // create gradient element
  197. gradient = document.createElement("div");
  198. gradient.style.cssText = ["position:relative;display:block;width:256px;height:15px;border-bottom:1px solid black; background-image:url(",me.createGradientImage(),");"].join("");
  199.  
  200. element.appendChild(labelsEl);
  201. element.appendChild(gradient);
  202.  
  203. me.set("element", element);
  204. me.set("labelsEl", labelsEl);
  205.  
  206. me.update(1);
  207. },
  208. processGradientObject: function(){
  209. // create array and sort it
  210. var me = this,
  211. gradientConfig = this.config.gradient,
  212. gradientArr = [];
  213.  
  214. for(var key in gradientConfig){
  215. if(gradientConfig.hasOwnProperty(key)){
  216. gradientArr.push({ stop: key, value: gradientConfig[key] });
  217. }
  218. }
  219. gradientArr.sort(function(a, b){
  220. return (a.stop - b.stop);
  221. });
  222. gradientArr.unshift({ stop: 0, value: 'rgba(0,0,0,0)' });
  223.  
  224. me.set("gradientArr", gradientArr);
  225. },
  226. createGradientImage: function(){
  227. var me = this,
  228. gradArr = me.get("gradientArr"),
  229. length = gradArr.length,
  230. canvas = document.createElement("canvas"),
  231. ctx = canvas.getContext("2d"),
  232. grad;
  233. // the gradient in the legend including the ticks will be 256x15px
  234. canvas.width = "256";
  235. canvas.height = "15";
  236.  
  237. grad = ctx.createLinearGradient(0,5,256,10);
  238.  
  239. for(var i = 0; i < length; i++){
  240. grad.addColorStop(1/(length-1) * i, gradArr[i].value);
  241. }
  242.  
  243. ctx.fillStyle = grad;
  244. ctx.fillRect(0,5,256,10);
  245. ctx.strokeStyle = "black";
  246. ctx.beginPath();
  247.  
  248. for(var i = 0; i < length; i++){
  249. ctx.moveTo(((1/(length-1)*i*256) >> 0)+.5, 0);
  250. ctx.lineTo(((1/(length-1)*i*256) >> 0)+.5, (i==0)?15:5);
  251. }
  252. ctx.moveTo(255.5, 0);
  253. ctx.lineTo(255.5, 15);
  254. ctx.moveTo(255.5, 4.5);
  255. ctx.lineTo(0, 4.5);
  256.  
  257. ctx.stroke();
  258.  
  259. // we re-use the context for measuring the legends label widths
  260. me.set("ctx", ctx);
  261.  
  262. return canvas.toDataURL();
  263. },
  264. getElement: function(){
  265. return this.get("element");
  266. },
  267. update: function(max){
  268. var me = this,
  269. gradient = me.get("gradientArr"),
  270. ctx = me.get("ctx"),
  271. labels = me.get("labelsEl"),
  272. labelText, labelsHtml = "", offset;
  273.  
  274. for(var i = 0; i < gradient.length; i++){
  275.  
  276. labelText = max*gradient[i].stop >> 0;
  277. offset = (ctx.measureText(labelText).width/2) >> 0;
  278.  
  279. if(i == 0){
  280. offset = 0;
  281. }
  282. if(i == gradient.length-1){
  283. offset *= 2;
  284. }
  285. labelsHtml += '<li style="position:absolute;left:'+(((((1/(gradient.length-1)*i*256) || 0)) >> 0)-offset+.5)+'px">'+labelText+'</li>';
  286. }
  287. labels.innerHTML = labelsHtml;
  288. }
  289. };
  290.  
  291. // heatmap object constructor
  292. var heatmap = function heatmap(config){
  293. // private variables
  294. var _ = {
  295. radius : 40,
  296. element : {},
  297. canvas : {},
  298. acanvas: {},
  299. ctx : {},
  300. actx : {},
  301. legend: null,
  302. visible : true,
  303. width : 0,
  304. height : 0,
  305. max : false,
  306. gradient : false,
  307. opacity: 180,
  308. premultiplyAlpha: false,
  309. bounds: {
  310. l: 1000,
  311. r: 0,
  312. t: 1000,
  313. b: 0
  314. },
  315. debug: false
  316. };
  317. // heatmap store containing the datapoints and information about the maximum
  318. // accessible via instance.store
  319. this.store = new store(this);
  320.  
  321. this.get = function(key){
  322. return _[key];
  323. };
  324. this.set = function(key, value){
  325. _[key] = value;
  326. };
  327. // configure the heatmap when an instance gets created
  328. this.configure(config);
  329. // and initialize it
  330. this.init();
  331. };
  332.  
  333. // public functions
  334. heatmap.prototype = {
  335. configure: function(config){
  336. var me = this,
  337. rout, rin;
  338.  
  339. me.set("radius", config["radius"] || 40);
  340. me.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));
  341. me.set("visible", (config.visible != null)?config.visible:true);
  342. me.set("max", config.max || false);
  343. me.set("gradient", config.gradient || { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "yellow", 1.0: "rgb(255,0,0)"}); // default is the common blue to red gradient
  344. me.set("opacity", parseInt(255/(100/config.opacity), 10) || 180);
  345. me.set("width", config.width || 0);
  346. me.set("height", config.height || 0);
  347. me.set("debug", config.debug);
  348.  
  349. if(config.legend){
  350. var legendCfg = config.legend;
  351. legendCfg.gradient = me.get("gradient");
  352. me.set("legend", new legend(legendCfg));
  353. }
  354.  
  355. },
  356. resize: function () {
  357. var me = this,
  358. element = me.get("element"),
  359. canvas = me.get("canvas"),
  360. acanvas = me.get("acanvas");
  361. canvas.width = acanvas.width = me.get("width") || element.style.width.replace(/px/, "") || me.getWidth(element);
  362. this.set("width", canvas.width);
  363. canvas.height = acanvas.height = me.get("height") || element.style.height.replace(/px/, "") || me.getHeight(element);
  364. this.set("height", canvas.height);
  365. },
  366.  
  367. init: function(){
  368. var me = this,
  369. canvas = document.createElement("canvas"),
  370. acanvas = document.createElement("canvas"),
  371. ctx = canvas.getContext("2d"),
  372. actx = acanvas.getContext("2d"),
  373. element = me.get("element");
  374.  
  375.  
  376. me.initColorPalette();
  377.  
  378. me.set("canvas", canvas);
  379. me.set("ctx", ctx);
  380. me.set("acanvas", acanvas);
  381. me.set("actx", actx);
  382.  
  383. me.resize();
  384. canvas.style.cssText = acanvas.style.cssText = "position:absolute;top:0;left:0;z-index:10000000;";
  385.  
  386. if(!me.get("visible"))
  387. canvas.style.display = "none";
  388.  
  389. element.appendChild(canvas);
  390. if(me.get("legend")){
  391. element.appendChild(me.get("legend").getElement());
  392. }
  393.  
  394. // debugging purposes only
  395. if(me.get("debug"))
  396. document.body.appendChild(acanvas);
  397.  
  398. actx.shadowOffsetX = 15000;
  399. actx.shadowOffsetY = 15000;
  400. actx.shadowBlur = 15;
  401. },
  402. initColorPalette: function(){
  403.  
  404. var me = this,
  405. canvas = document.createElement("canvas"),
  406. gradient = me.get("gradient"),
  407. ctx, grad, testData;
  408.  
  409. canvas.width = "1";
  410. canvas.height = "256";
  411. ctx = canvas.getContext("2d");
  412. grad = ctx.createLinearGradient(0,0,1,256);
  413.  
  414. // Test how the browser renders alpha by setting a partially transparent pixel
  415. // and reading the result. A good browser will return a value reasonably close
  416. // to what was set. Some browsers (e.g. on Android) will return a ridiculously wrong value.
  417. testData = ctx.getImageData(0,0,1,1);
  418. testData.data[0] = testData.data[3] = 64; // 25% red & alpha
  419. testData.data[1] = testData.data[2] = 0; // 0% blue & green
  420. ctx.putImageData(testData, 0, 0);
  421. testData = ctx.getImageData(0,0,1,1);
  422. me.set("premultiplyAlpha", (testData.data[0] < 60 || testData.data[0] > 70));
  423.  
  424. for(var x in gradient){
  425. grad.addColorStop(x, gradient[x]);
  426. }
  427.  
  428. ctx.fillStyle = grad;
  429. ctx.fillRect(0,0,1,256);
  430.  
  431. me.set("gradient", ctx.getImageData(0,0,1,256).data);
  432. },
  433. getWidth: function(element){
  434. var width = element.offsetWidth;
  435. if(element.style.paddingLeft){
  436. width+=element.style.paddingLeft;
  437. }
  438. if(element.style.paddingRight){
  439. width+=element.style.paddingRight;
  440. }
  441.  
  442. return width;
  443. },
  444. getHeight: function(element){
  445. var height = element.offsetHeight;
  446. if(element.style.paddingTop){
  447. height+=element.style.paddingTop;
  448. }
  449. if(element.style.paddingBottom){
  450. height+=element.style.paddingBottom;
  451. }
  452.  
  453. return height;
  454. },
  455. colorize: function(x, y){
  456. // get the private variables
  457. var me = this,
  458. width = me.get("width"),
  459. radius = me.get("radius"),
  460. height = me.get("height"),
  461. actx = me.get("actx"),
  462. ctx = me.get("ctx"),
  463. x2 = radius * 3,
  464. premultiplyAlpha = me.get("premultiplyAlpha"),
  465. palette = me.get("gradient"),
  466. opacity = me.get("opacity"),
  467. bounds = me.get("bounds"),
  468. left, top, bottom, right,
  469. image, imageData, length, alpha, offset, finalAlpha;
  470.  
  471. if(x != null && y != null){
  472. if(x+x2>width){
  473. x=width-x2;
  474. }
  475. if(x<0){
  476. x=0;
  477. }
  478. if(y<0){
  479. y=0;
  480. }
  481. if(y+x2>height){
  482. y=height-x2;
  483. }
  484. left = x;
  485. top = y;
  486. right = x + x2;
  487. bottom = y + x2;
  488.  
  489. }else{
  490. if(bounds['l'] < 0){
  491. left = 0;
  492. }else{
  493. left = bounds['l'];
  494. }
  495. if(bounds['r'] > width){
  496. right = width;
  497. }else{
  498. right = bounds['r'];
  499. }
  500. if(bounds['t'] < 0){
  501. top = 0;
  502. }else{
  503. top = bounds['t'];
  504. }
  505. if(bounds['b'] > height){
  506. bottom = height;
  507. }else{
  508. bottom = bounds['b'];
  509. }
  510. }
  511.  
  512. image = actx.getImageData(left, top, right-left, bottom-top);
  513. imageData = image.data;
  514. length = imageData.length;
  515. // loop thru the area
  516. for(var i=3; i < length; i+=4){
  517.  
  518. // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha
  519. alpha = imageData[i],
  520. offset = alpha*4;
  521.  
  522. if(!offset)
  523. continue;
  524.  
  525. // we ve started with i=3
  526. // set the new r, g and b values
  527. finalAlpha = (alpha < opacity)?alpha:opacity;
  528. imageData[i-3]=palette[offset];
  529. imageData[i-2]=palette[offset+1];
  530. imageData[i-1]=palette[offset+2];
  531.  
  532. if (premultiplyAlpha) {
  533. // To fix browsers that premultiply incorrectly, we'll pass in a value scaled
  534. // appropriately so when the multiplication happens the correct value will result.
  535. imageData[i-3] /= 255/finalAlpha;
  536. imageData[i-2] /= 255/finalAlpha;
  537. imageData[i-1] /= 255/finalAlpha;
  538. }
  539.  
  540. // we want the heatmap to have a gradient from transparent to the colors
  541. // as long as alpha is lower than the defined opacity (maximum), we'll use the alpha value
  542. imageData[i] = finalAlpha;
  543. }
  544. // the rgb data manipulation didn't affect the ImageData object(defined on the top)
  545. // after the manipulation process we have to set the manipulated data to the ImageData object
  546. image.data = imageData;
  547. ctx.putImageData(image, left, top);
  548. },
  549. drawAlpha: function(x, y, count, colorize){
  550. // storing the variables because they will be often used
  551. var me = this,
  552. radius = me.get("radius"),
  553. ctx = me.get("actx"),
  554. max = me.get("max"),
  555. bounds = me.get("bounds"),
  556. xb = x - (1.5 * radius) >> 0, yb = y - (1.5 * radius) >> 0,
  557. xc = x + (1.5 * radius) >> 0, yc = y + (1.5 * radius) >> 0;
  558.  
  559. ctx.shadowColor = ('rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')');
  560.  
  561. ctx.shadowOffsetX = 15000;
  562. ctx.shadowOffsetY = 15000;
  563. ctx.shadowBlur = 15;
  564.  
  565. ctx.beginPath();
  566. ctx.arc(x - 15000, y - 15000, radius, 0, Math.PI * 2, true);
  567. ctx.closePath();
  568. ctx.fill();
  569. if(colorize){
  570. // finally colorize the area
  571. me.colorize(xb,yb);
  572. }else{
  573. // or update the boundaries for the area that then should be colorized
  574. if(xb < bounds["l"]){
  575. bounds["l"] = xb;
  576. }
  577. if(yb < bounds["t"]){
  578. bounds["t"] = yb;
  579. }
  580. if(xc > bounds['r']){
  581. bounds['r'] = xc;
  582. }
  583. if(yc > bounds['b']){
  584. bounds['b'] = yc;
  585. }
  586. }
  587. },
  588. toggleDisplay: function(){
  589. var me = this,
  590. visible = me.get("visible"),
  591. canvas = me.get("canvas");
  592.  
  593. if(!visible)
  594. canvas.style.display = "block";
  595. else
  596. canvas.style.display = "none";
  597.  
  598. me.set("visible", !visible);
  599. },
  600. // dataURL export
  601. getImageData: function(){
  602. return this.get("canvas").toDataURL();
  603. },
  604. clear: function(){
  605. var me = this,
  606. w = me.get("width"),
  607. h = me.get("height");
  608.  
  609. me.store.set("data",[]);
  610. // @TODO: reset stores max to 1
  611. //me.store.max = 1;
  612. me.get("ctx").clearRect(0,0,w,h);
  613. me.get("actx").clearRect(0,0,w,h);
  614. },
  615. cleanup: function(){
  616. var me = this;
  617. me.get("element").removeChild(me.get("canvas"));
  618. }
  619. };
  620.  
  621. return {
  622. create: function(config){
  623. return new heatmap(config);
  624. },
  625. util: {
  626. mousePosition: function(ev){
  627. // this doesn't work right
  628. // rather use
  629. /*
  630. // this = element to observe
  631. var x = ev.pageX - this.offsetLeft;
  632. var y = ev.pageY - this.offsetTop;
  633.  
  634. */
  635. var x, y;
  636.  
  637. if (ev.layerX) { // Firefox
  638. x = ev.layerX;
  639. y = ev.layerY;
  640. } else if (ev.offsetX) { // Opera
  641. x = ev.offsetX;
  642. y = ev.offsetY;
  643. }
  644. if(typeof(x)=='undefined')
  645. return;
  646.  
  647. return [x,y];
  648. }
  649. }
  650. };
  651. })();
  652. w.h337 = w.heatmapFactory = heatmapFactory;
  653. })(window);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement