├── _config.yml ├── img └── demo.png ├── README.md ├── index.html ├── js ├── maptalks.three.min.js ├── maptalks.three.js ├── TrackballControls.js └── heatmap.js └── css └── maptalks.css /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-merlot -------------------------------------------------------------------------------- /img/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiangLiLiang/3dheatmap/HEAD/img/demo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome to GitHub Pages 2 | 3 | ### Using three.js ,heatmap.js and maptalks.js to build 3D heatmap 4 | ![Image text](https://github.com/XiangLiLiang/XiangLiLiang.github.io/blob/master/img/demo.png) 5 | 6 | ### Online Demo 7 | 3dheatmap(https://xiangliliang.github.io/3dheatmap/) 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 122 | 123 | 124 | 125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /js/maptalks.three.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("maptalks"),require("three")):"function"==typeof define&&define.amd?define(["exports","maptalks","three"],t):t(e.maptalks=e.maptalks||{},e.maptalks,e.THREE)}(this,function(e,p,f){"use strict";function n(e,t){e.prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t}var a=Math.PI/180,t=function(e){function t(){return e.apply(this,arguments)||this}n(t,e);var r=t.prototype;return r.draw=function(){this.renderScene()},r.drawOnInteracting=function(){this.renderScene()},r.coordinateToVector3=function(e,t){void 0===t&&(t=0);var r=this.getMap();if(!r)return null;var n=r.coordinateToPoint(e,l(r));return new f.Vector3(n.x,n.y,t)},r.distanceToVector3=function(e,t,r){var n=this.getMap(),a=l(n),i=r||n.getCenter(),o=n.locate(i,e,t),s=n.coordinateToPoint(i,a),c=n.coordinateToPoint(o,a),h=Math.abs(c.x-s.x)*p.Util.sign(e),u=Math.abs(c.y-s.y)*p.Util.sign(t);return new f.Vector3(h,u,0)},r.toShape=function(e){var r=this;if(!e)return null;if(e instanceof p.MultiPolygon)return e.getGeometries().map(function(e){return r.toShape(e)});var t=e.getCenter(),n=this.coordinateToVector3(t),a=e.getShell().map(function(e){return r.coordinateToVector3(e).sub(n)}).reverse(),i=new f.Shape(a),o=e.getHoles();return o&&0=0.39.0.")}); 2 | -------------------------------------------------------------------------------- /css/maptalks.css: -------------------------------------------------------------------------------- 1 | .maptalks-bc-1{width:57px;height:58px;background:url(images/control/2.png) no-repeat}.maptalks-bc-2{width:49px;height:49px;background:url(images/control/2_2.png) no-repeat;position:absolute;left:75px;top:5px}.maptalks-zoom{text-align:center}.maptalks-zoom .maptalks-zoom-zoomlevel{display:block;width:23px;height:23px;background:#172029;color:#fff;line-height:23px;font-size:12px}.maptalks-zoom-slider{margin-top:6px}.maptalks-zoom-slider a.maptalks-zoom-zoomin,.maptalks-zoom-slider a.maptalks-zoom-zoomout{display:block;font-size:16px;width:21px;height:21px;border:1px solid #363539;background:#172029;color:#fff;line-height:19px;text-decoration:none}.maptalks-zoom-slider-box{width:21px;height:124px;background:#34495e;background:url(images/control/kedu.png) repeat-y;border:1px solid #35383b;position:relative}.maptalks-zoom-slider-box .maptalks-zoom-slider-ruler{width:5px;height:112px;background:#372e2b;border-radius:2px;-moz-border-radius:2px;-webkit-border-radius:2px;margin:4px auto;position:relative}.maptalks-zoom-slider-box .maptalks-zoom-slider-ruler .maptalks-zoom-slider-reading{width:5px;height:50%;position:absolute;bottom:0;left:0;background:#1bbc9b;border-radius:2px;-moz-border-radius:2px;-webkit-border-radius:2px}.maptalks-zoom-slider-box .maptalks-zoom-slider-dot{width:15px;height:15px;background:url(images/control/3.png) no-repeat;position:absolute;top:50%;left:3px;cursor:pointer}.maptalks-toolbar-vertical{margin:0;overflow:visible}.maptalks-toolbar-horizonal ul,.maptalks-toolbar-vertical ul{margin:0;padding:0}.maptalks-toolbar-vertical ul li+li{border-top:1px solid #425568}.maptalks-toolbar-vertical li{text-align:center;list-style:none;line-height:28px;color:#fff;background:#34495e;min-width:10px;min-height:28px;position:relative;padding:0 10px}.maptalks-toolbar-vertical li:hover{background:#1bbc9b}.maptalks-toolbar-vertical li .maptalks-dropMenu{padding:0;position:absolute;top:0;overflow:visible}.maptalks-toolbar-vertical li .maptalks-dropMenu li{list-style:none;min-width:95px;background:#223140;height:27px}.maptalks-toolbar-vertical li .maptalks-dropMenu li a{color:#fff;display:block;line-height:27px;background:url(images/control/ico-dot.png) no-repeat 14px 10px;text-indent:33px;text-decoration:none;font-size:12px}.maptalks-toolbar-vertical li .maptalks-dropMenu em.maptalks-ico{display:block;width:5px;height:6px;position:absolute;top:12px;right:-4px}.maptalks-toolbar-vertical li .maptalks-dropMenu li.maptalks-on,.maptalks-toolbar-vertical li .maptalks-dropMenu li:hover{background:#0e595e}.maptalks-toolbar-horizonal{margin:0;overflow:visible}.maptalks-toolbar-horizonal li{text-align:left;line-height:28px;color:#fff;padding:0 10px;list-style:none;min-width:28px;min-height:28px;float:left;background:#34495e;position:relative}.maptalks-toolbar-horizonal ul li+li{border-left:1px solid #425568}.maptalks-toolbar-horizonal li:hover{background:#1bbc9b}.maptalks-toolbar-horizonal li .maptalks-dropMenu{display:block;position:absolute;left:0;overflow:visible}.maptalks-toolbar-horizonal li .maptalks-dropMenu li{list-style:none;min-width:95px;background:#223140;height:27px}.maptalks-toolbar-horizonal li .maptalks-dropMenu li+li{border-left:none;border-top:1px solid #425568}.maptalks-toolbar-horizonal li .maptalks-dropMenu li a{color:#fff;display:block;line-height:27px;background:url(images/control/ico-dot.png) no-repeat 5px 10px;text-indent:20px;text-decoration:none;font-size:12px}.maptalks-toolbar-horizonal li .maptalks-dropMenu em.maptalks-ico{display:block;width:5px;height:6px;position:absolute;top:-4px;left:12px}.maptalks-toolbar-horizonal .maptalks-dropMenu li:hover{background:#0e595e}.maptalks-menu{background:#fff;padding:1px;width:172px;border:1px solid #b4b3b3}.maptalks-menu em.maptalks-ico{display:block;width:17px;height:10px;background:url(images/control/5.png) no-repeat;position:absolute;top:-10px;left:8px}.maptalks-menu .maptalks-menu-items{color:#5a5756;margin:0;padding:0;font-size:12px}.maptalks-menu .maptalks-menu-items li{list-style:none;height:30px;line-height:30px;text-indent:16px}.maptalks-menu .maptalks-menu-items li:hover{background:#007fbe;color:#fff;cursor:pointer}.maptalks-menu .maptalks-menu-items li.maptalks-menu-splitter{list-style:none;height:2px;background:#ddd}.maptalks-msgBox{background:#fff;border:1px solid #b4b3b3;border-radius:3px}.maptalks-msgBox em.maptalks-ico{display:block;width:17px;height:10px;background:url(images/control/5_1.png) no-repeat;position:absolute;left:50%;margin-left:-5px;bottom:-10px}.maptalks-msgBox h2{display:block;height:30px;line-height:30px;font-weight:700;font-size:14px;padding:0 10px;margin:0}.maptalks-msgBox a.maptalks-close{display:block;width:13px;height:13px;background:url(images/control/infownd-close.png) no-repeat;position:absolute;top:8px;right:10px}.maptalks-msgBox a.maptalks-close:hover{background:url(images/control/infownd-close-hover.png) no-repeat}.maptalks-msgBox .maptalks-msgContent{font-size:12px;padding:10px;min-width:200px}.maptalks-panel{background:#fff;border:1px solid #b4b3b3;border-radius:3px}.maptalks-panel .maptalks-panel-content{padding:10px;min-width:200px;min-height:60px}.maptalks-panel a.maptalks-close{display:block;width:6px;height:7px;background:url(images/control/close-2.png) no-repeat;position:absolute;top:10px;right:10px}.maptalks-panel a.maptalks-close:hover{opacity:.5}.maptalks-attribution{display:inline-block;opacity:1;background:#fff;background-color:hsla(0,0%,100%,.7);padding:0;font-size:13px;font-family:microsoft yahei,Helvetica Neue,Helvetica,sans-serif}.maptalks-attribution a{text-decoration:none;color:#0078a8}.maptalks-attribution a:hover{text-decoration:underline}.maptalks-overview{background:#fff;border:1px solid #b4b3b3;width:100%;height:100%}.maptalks-overview-button{cursor:pointer;background:#fff;width:18px;height:18px;position:absolute;bottom:1px;right:1px;font:16px sans-serif;text-align:center;line-height:16px;border:1px solid #b4b3b3;color:#363539}.maptalks-layer-switcher ul{list-style:none}.maptalks-layer-switcher .panel>ul{padding-left:1em}.maptalks-layer-switcher .group>ul{padding-left:10px}.maptalks-layer-switcher .group+.group{padding-top:1em}.maptalks-layer-switcher label{text-overflow:ellipsis;overflow:hidden;display:inline-block;font-size:14px;white-space:nowrap;color:#bbb}.maptalks-layer-switcher .group>label{font-weight:700;color:#ddd;width:100%}.maptalks-layer-switcher .layer label{padding-top:5px;width:92%}.maptalks-layer-switcher input{margin:0 5px;position:relative;top:-2px}.maptalks-layer-switcher input[disabled=disabled]{cursor:not-allowed}.maptalks-layer-switcher input[disabled=disabled]+label{color:#666}.maptalks-layer-switcher .panel,.maptalks-layer-switcher button{border-radius:4px}.maptalks-layer-switcher button{width:28px;height:28px;background:url(images/control/layer.png) no-repeat 4px 4px;background-color:#172029;border:none}.maptalks-layer-switcher.shown button{display:none}.maptalks-layer-switcher .panel{background-color:#172029;display:none;overflow-y:auto;overflow-x:hidden;min-width:120px;max-width:400px;max-height:500px}.maptalks-layer-switcher li{white-space:nowrap}.maptalks-layer-switcher li.group{margin-right:1em}.maptalks-layer-switcher.shown .panel{display:block}.maptalks-layer-switcher ::-webkit-scrollbar{width:6px}.maptalks-layer-switcher ::-webkit-scrollbar-track{background-color:#1f1f1f}.maptalks-layer-switcher ::-webkit-scrollbar-thumb{border-radius:5px;background-color:#777}.maptalks-tooltip{display:block;background:#fff;border:1px solid #b4b3b3;padding:0 4px;height:24px;line-height:24px;font-size:14px;white-space:nowrap} -------------------------------------------------------------------------------- /js/maptalks.three.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * maptalks.three v0.6.1 3 | * LICENSE : MIT 4 | * (c) 2016-2018 maptalks.org 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('maptalks'), require('three')) : 8 | typeof define === 'function' && define.amd ? define(['exports', 'maptalks', 'three'], factory) : 9 | (factory((global.maptalks = global.maptalks || {}),global.maptalks,global.THREE)); 10 | }(this, (function (exports,maptalks,THREE) { 'use strict'; 11 | 12 | function _inheritsLoose(subClass, superClass) { 13 | subClass.prototype = Object.create(superClass.prototype); 14 | subClass.prototype.constructor = subClass; 15 | subClass.__proto__ = superClass; 16 | } 17 | 18 | var options = { 19 | 'renderer': 'gl', 20 | 'doubleBuffer': false, 21 | 'glOptions': null 22 | }; 23 | var RADIAN = Math.PI / 180; 24 | /** 25 | * A Layer to render with THREE.JS (http://threejs.org), the most popular library for WebGL.
26 | * 27 | * @classdesc 28 | * A layer to render with THREE.JS 29 | * @example 30 | * var layer = new maptalks.ThreeLayer('three'); 31 | * 32 | * layer.prepareToDraw = function (gl, scene, camera) { 33 | * var size = map.getSize(); 34 | * return [size.width, size.height] 35 | * }; 36 | * 37 | * layer.draw = function (gl, scene, camera, width, height) { 38 | * //... 39 | * }; 40 | * layer.addTo(map); 41 | * @class 42 | * @category layer 43 | * @extends {maptalks.CanvasLayer} 44 | * @param {String|Number} id - layer's id 45 | * @param {Object} options - options defined in [options]{@link maptalks.ThreeLayer#options} 46 | */ 47 | 48 | var ThreeLayer = 49 | /*#__PURE__*/ 50 | function (_maptalks$CanvasLayer) { 51 | _inheritsLoose(ThreeLayer, _maptalks$CanvasLayer); 52 | 53 | function ThreeLayer() { 54 | return _maptalks$CanvasLayer.apply(this, arguments) || this; 55 | } 56 | 57 | var _proto = ThreeLayer.prototype; 58 | 59 | /** 60 | * Draw method of ThreeLayer 61 | * In default, it calls renderScene, refresh the camera and the scene 62 | */ 63 | _proto.draw = function draw() { 64 | this.renderScene(); 65 | 66 | }; 67 | /** 68 | * Draw method of ThreeLayer when map is interacting 69 | * In default, it calls renderScene, refresh the camera and the scene 70 | */ 71 | 72 | 73 | _proto.drawOnInteracting = function drawOnInteracting() { 74 | this.renderScene(); 75 | }; 76 | /** 77 | * Convert a geographic coordinate to THREE Vector3 78 | * @param {maptalks.Coordinate} coordinate - coordinate 79 | * @param {Number} [z=0] z value 80 | * @return {THREE.Vector3} 81 | */ 82 | 83 | 84 | _proto.coordinateToVector3 = function coordinateToVector3(coordinate, z) { 85 | if (z === void 0) { 86 | z = 0; 87 | } 88 | 89 | var map = this.getMap(); 90 | 91 | if (!map) { 92 | return null; 93 | } 94 | 95 | var p = map.coordinateToPoint(coordinate, getTargetZoom(map)); 96 | return new THREE.Vector3(p.x, p.y, z); 97 | }; 98 | /** 99 | * Convert geographic distance to THREE Vector3 100 | * @param {Number} w - width 101 | * @param {Number} h - height 102 | * @return {THREE.Vector3} 103 | */ 104 | 105 | 106 | _proto.distanceToVector3 = function distanceToVector3(w, h, coord) { 107 | var map = this.getMap(); 108 | var zoom = getTargetZoom(map), 109 | center = coord || map.getCenter(), 110 | target = map.locate(center, w, h); 111 | var p0 = map.coordinateToPoint(center, zoom), 112 | p1 = map.coordinateToPoint(target, zoom); 113 | var x = Math.abs(p1.x - p0.x) * maptalks.Util.sign(w); 114 | var y = Math.abs(p1.y - p0.y) * maptalks.Util.sign(h); 115 | return new THREE.Vector3(x, y, 0); 116 | }; 117 | /** 118 | * Convert a Polygon or a MultiPolygon to THREE shape 119 | * @param {maptalks.Polygon|maptalks.MultiPolygon} polygon - polygon or multipolygon 120 | * @return {THREE.Shape} 121 | */ 122 | 123 | 124 | _proto.toShape = function toShape(polygon) { 125 | var _this = this; 126 | 127 | if (!polygon) { 128 | return null; 129 | } 130 | 131 | if (polygon instanceof maptalks.MultiPolygon) { 132 | return polygon.getGeometries().map(function (c) { 133 | return _this.toShape(c); 134 | }); 135 | } 136 | 137 | var center = polygon.getCenter(); 138 | var centerPt = this.coordinateToVector3(center); 139 | var shell = polygon.getShell(); 140 | var outer = shell.map(function (c) { 141 | return _this.coordinateToVector3(c).sub(centerPt); 142 | }).reverse(); 143 | var shape = new THREE.Shape(outer); 144 | var holes = polygon.getHoles(); 145 | 146 | if (holes && holes.length > 0) { 147 | shape.holes = holes.map(function (item) { 148 | var pts = item.map(function (c) { 149 | return _this.coordinateToVector3(c).sub(centerPt); 150 | }); 151 | return new THREE.Shape(pts); 152 | }); 153 | } 154 | 155 | return shape; 156 | }; 157 | 158 | _proto.toExtrudeMesh = function toExtrudeMesh(polygon, altitude, material, height) { 159 | var _this2 = this; 160 | 161 | if (!polygon) { 162 | return null; 163 | } 164 | 165 | if (polygon instanceof maptalks.MultiPolygon) { 166 | return polygon.getGeometries().map(function (c) { 167 | return _this2.toExtrudeGeometry(c, altitude, material, height); 168 | }); 169 | } 170 | 171 | var rings = polygon.getCoordinates(); 172 | rings.forEach(function (ring) { 173 | var length = ring.length; 174 | 175 | for (var i = length - 1; i >= 1; i--) { 176 | if (ring[i].equals(ring[i - 1])) { 177 | ring.splice(i, 1); 178 | } 179 | } 180 | }); 181 | polygon.setCoordinates(rings); 182 | var shape = this.toShape(polygon); 183 | var center = this.coordinateToVector3(polygon.getCenter()); 184 | height = maptalks.Util.isNumber(height) ? height : altitude; 185 | height = this.distanceToVector3(height, height).x; 186 | var amount = this.distanceToVector3(altitude, altitude).x; //{ amount: extrudeH, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1 }; 187 | 188 | var config = { 189 | 'bevelEnabled': false, 190 | 'bevelSize': 1 191 | }; 192 | var name = parseInt(THREE.REVISION) >= 93 ? 'depth' : 'amount'; 193 | config[name] = height; 194 | var geom = new THREE.ExtrudeGeometry(shape, config); 195 | var buffGeom = new THREE.BufferGeometry(); 196 | buffGeom.fromGeometry(geom); 197 | var mesh = new THREE.Mesh(geom, material); 198 | mesh.position.set(center.x, center.y, amount - height); 199 | return mesh; 200 | }; 201 | 202 | _proto.clearMesh = function clearMesh() { 203 | var scene = this.getScene(); 204 | 205 | if (!scene) { 206 | return this; 207 | } 208 | 209 | for (var i = scene.children.length - 1; i >= 0; i--) { 210 | if (scene.children[i] instanceof THREE.Mesh) { 211 | scene.remove(scene.children[i]); 212 | } 213 | } 214 | 215 | return this; 216 | }; 217 | 218 | _proto.lookAt = function lookAt(vector) { 219 | var renderer = this._getRenderer(); 220 | 221 | if (renderer) { 222 | renderer.context.lookAt(vector); 223 | } 224 | 225 | return this; 226 | }; 227 | 228 | _proto.getCamera = function getCamera() { 229 | var renderer = this._getRenderer(); 230 | 231 | if (renderer) { 232 | return renderer.camera; 233 | } 234 | 235 | return null; 236 | }; 237 | 238 | _proto.getScene = function getScene() { 239 | var renderer = this._getRenderer(); 240 | 241 | if (renderer) { 242 | return renderer.scene; 243 | } 244 | 245 | return null; 246 | }; 247 | 248 | _proto.renderScene = function renderScene() { 249 | var renderer = this._getRenderer(); 250 | 251 | if (renderer) { 252 | return renderer.renderScene(); 253 | } 254 | 255 | return this; 256 | }; 257 | 258 | _proto.getThreeRenderer = function getThreeRenderer() { 259 | var renderer = this._getRenderer(); 260 | 261 | if (renderer) { 262 | return renderer.context; 263 | } 264 | 265 | return null; 266 | }; 267 | /** 268 | * To make map's 2d point's 1 pixel euqal with 1 pixel on XY plane in THREE's scene: 269 | * 1. fov is 90 and camera's z is height / 2 * scale, 270 | * 2. if fov is not 90, a ratio is caculated to transfer z to the equivalent when fov is 90 271 | * @return {Number} fov ratio on z axis 272 | */ 273 | 274 | 275 | _proto._getFovRatio = function _getFovRatio() { 276 | var map = this.getMap(); 277 | var fov = map.getFov(); 278 | return Math.tan(fov / 2 * RADIAN); 279 | }; 280 | 281 | return ThreeLayer; 282 | }(maptalks.CanvasLayer); 283 | ThreeLayer.mergeOptions(options); 284 | var ThreeRenderer = 285 | /*#__PURE__*/ 286 | function (_maptalks$renderer$Ca) { 287 | _inheritsLoose(ThreeRenderer, _maptalks$renderer$Ca); 288 | 289 | function ThreeRenderer() { 290 | return _maptalks$renderer$Ca.apply(this, arguments) || this; 291 | } 292 | 293 | var _proto2 = ThreeRenderer.prototype; 294 | 295 | _proto2.getPrepareParams = function getPrepareParams() { 296 | return [this.scene, this.camera]; 297 | }; 298 | 299 | _proto2.getDrawParams = function getDrawParams() { 300 | return [this.scene, this.camera]; 301 | }; 302 | 303 | _proto2._drawLayer = function _drawLayer() { 304 | _maptalks$renderer$Ca.prototype._drawLayer.apply(this, arguments); 305 | 306 | this.renderScene(); 307 | }; 308 | 309 | _proto2.hitDetect = function hitDetect() { 310 | return false; 311 | }; 312 | 313 | _proto2.createCanvas = function createCanvas() { 314 | _maptalks$renderer$Ca.prototype.createCanvas.call(this); 315 | 316 | this.createContext(); 317 | }; 318 | 319 | _proto2.createContext = function createContext() { 320 | if (this.canvas.gl && this.canvas.gl.wrap) { 321 | this.gl = this.canvas.gl.wrap(); 322 | } else { 323 | var layer = this.layer; 324 | var attributes = layer.options.glOptions || { 325 | alpha: true, 326 | depth: true, 327 | antialias: true, 328 | stencil: true 329 | }; 330 | attributes.preserveDrawingBuffer = true; 331 | this.gl = this.gl || this._createGLContext(this.canvas, attributes); 332 | } 333 | 334 | this._initThreeRenderer(); 335 | 336 | this.layer.onCanvasCreate(this.context, this.scene, this.camera); 337 | }; 338 | 339 | _proto2._initThreeRenderer = function _initThreeRenderer() { 340 | var renderer = new THREE.WebGLRenderer({ 341 | 'context': this.gl, 342 | alpha: true 343 | }); 344 | renderer.autoClear = false; 345 | renderer.setClearColor(new THREE.Color(1, 1, 1), 0); 346 | renderer.setSize(this.canvas.width, this.canvas.height); 347 | renderer.clear(); 348 | renderer.canvas = this.canvas; 349 | this.context = renderer; 350 | var scene = this.scene = new THREE.Scene(); 351 | var map = this.layer.getMap(); 352 | var fov = map.getFov() * Math.PI / 180; 353 | var camera = this.camera = new THREE.PerspectiveCamera(fov, map.width / map.height, map.cameraNear, map.cameraFar); 354 | camera.matrixAutoUpdate = false; 355 | 356 | this._syncCamera(); 357 | 358 | scene.add(camera); 359 | }; 360 | 361 | _proto2.onCanvasCreate = function onCanvasCreate() { 362 | _maptalks$renderer$Ca.prototype.onCanvasCreate.call(this); 363 | }; 364 | 365 | _proto2.resizeCanvas = function resizeCanvas(canvasSize) { 366 | if (!this.canvas) { 367 | return; 368 | } 369 | 370 | var size; 371 | 372 | if (!canvasSize) { 373 | size = this.getMap().getSize(); 374 | } else { 375 | size = canvasSize; 376 | } 377 | 378 | var r = maptalks.Browser.retina ? 2 : 1; 379 | var canvas = this.canvas; //retina support 380 | 381 | canvas.height = r * size['height']; 382 | canvas.width = r * size['width']; 383 | this.context.setSize(canvas.width, canvas.height); 384 | }; 385 | 386 | _proto2.clearCanvas = function clearCanvas() { 387 | if (!this.canvas) { 388 | return; 389 | } 390 | 391 | this.context.clear(); 392 | }; 393 | 394 | _proto2.prepareCanvas = function prepareCanvas() { 395 | if (!this.canvas) { 396 | this.createCanvas(); 397 | } else { 398 | this.clearCanvas(); 399 | } 400 | 401 | this.layer.fire('renderstart', { 402 | 'context': this.context 403 | }); 404 | return null; 405 | }; 406 | 407 | _proto2.renderScene = function renderScene() { 408 | this._syncCamera(); 409 | 410 | this.context.render(this.scene, this.camera); 411 | this.completeRender(); 412 | }; 413 | 414 | _proto2.remove = function remove() { 415 | delete this._drawContext; 416 | 417 | _maptalks$renderer$Ca.prototype.remove.call(this); 418 | }; 419 | 420 | _proto2._syncCamera = function _syncCamera() { 421 | var map = this.getMap(); 422 | this.camera.matrix.elements = map.cameraWorldMatrix; 423 | this.camera.projectionMatrix.elements = map.projMatrix; 424 | }; 425 | 426 | _proto2._createGLContext = function _createGLContext(canvas, options) { 427 | var names = ['webgl', 'experimental-webgl']; 428 | var context = null; 429 | /* eslint-disable no-empty */ 430 | 431 | for (var i = 0; i < names.length; ++i) { 432 | try { 433 | context = canvas.getContext(names[i], options); 434 | } catch (e) {} 435 | 436 | if (context) { 437 | break; 438 | } 439 | } 440 | 441 | return context; 442 | /* eslint-enable no-empty */ 443 | }; 444 | 445 | return ThreeRenderer; 446 | }(maptalks.renderer.CanvasLayerRenderer); 447 | ThreeLayer.registerRenderer('gl', ThreeRenderer); 448 | 449 | function getTargetZoom(map) { 450 | return map.getGLZoom(); 451 | } 452 | 453 | exports.ThreeLayer = ThreeLayer; 454 | exports.ThreeRenderer = ThreeRenderer; 455 | 456 | Object.defineProperty(exports, '__esModule', { value: true }); 457 | 458 | typeof console !== 'undefined' && console.log('maptalks.three v0.6.1, requires maptalks@>=0.39.0.'); 459 | 460 | }))); 461 | -------------------------------------------------------------------------------- /js/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | * @author Simone Manini / http://daron1337.github.io 5 | * @author Luca Antiga / http://lantiga.github.io 6 | */ 7 | 8 | THREE.TrackballControls = function ( object, domElement ) { 9 | 10 | var _this = this; 11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 12 | 13 | this.object = object; 14 | this.domElement = ( domElement !== undefined ) ? domElement : document; 15 | 16 | // API 17 | 18 | this.enabled = true; 19 | 20 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 21 | 22 | this.rotateSpeed = 1.0; 23 | this.zoomSpeed = 1.2; 24 | this.panSpeed = 0.3; 25 | 26 | this.noRotate = false; 27 | this.noZoom = false; 28 | this.noPan = false; 29 | 30 | this.staticMoving = false; 31 | this.dynamicDampingFactor = 0.2; 32 | 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 37 | 38 | // internals 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | var EPS = 0.000001; 43 | 44 | var lastPosition = new THREE.Vector3(); 45 | 46 | var _state = STATE.NONE, 47 | _prevState = STATE.NONE, 48 | 49 | _eye = new THREE.Vector3(), 50 | 51 | _movePrev = new THREE.Vector2(), 52 | _moveCurr = new THREE.Vector2(), 53 | 54 | _lastAxis = new THREE.Vector3(), 55 | _lastAngle = 0, 56 | 57 | _zoomStart = new THREE.Vector2(), 58 | _zoomEnd = new THREE.Vector2(), 59 | 60 | _touchZoomDistanceStart = 0, 61 | _touchZoomDistanceEnd = 0, 62 | 63 | _panStart = new THREE.Vector2(), 64 | _panEnd = new THREE.Vector2(); 65 | 66 | // for reset 67 | 68 | this.target0 = this.target.clone(); 69 | this.position0 = this.object.position.clone(); 70 | this.up0 = this.object.up.clone(); 71 | 72 | // events 73 | 74 | var changeEvent = { type: 'change' }; 75 | var startEvent = { type: 'start' }; 76 | var endEvent = { type: 'end' }; 77 | 78 | 79 | // methods 80 | 81 | this.handleResize = function () { 82 | 83 | if ( this.domElement === document ) { 84 | 85 | this.screen.left = 0; 86 | this.screen.top = 0; 87 | this.screen.width = window.innerWidth; 88 | this.screen.height = window.innerHeight; 89 | 90 | } else { 91 | 92 | var box = this.domElement.getBoundingClientRect(); 93 | // adjustments come from similar code in the jquery offset() function 94 | var d = this.domElement.ownerDocument.documentElement; 95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 96 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 97 | this.screen.width = box.width; 98 | this.screen.height = box.height; 99 | 100 | } 101 | 102 | }; 103 | 104 | var getMouseOnScreen = ( function () { 105 | 106 | var vector = new THREE.Vector2(); 107 | 108 | return function getMouseOnScreen( pageX, pageY ) { 109 | 110 | vector.set( 111 | ( pageX - _this.screen.left ) / _this.screen.width, 112 | ( pageY - _this.screen.top ) / _this.screen.height 113 | ); 114 | 115 | return vector; 116 | 117 | }; 118 | 119 | }() ); 120 | 121 | var getMouseOnCircle = ( function () { 122 | 123 | var vector = new THREE.Vector2(); 124 | 125 | return function getMouseOnCircle( pageX, pageY ) { 126 | 127 | vector.set( 128 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ), 129 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional 130 | ); 131 | 132 | return vector; 133 | 134 | }; 135 | 136 | }() ); 137 | 138 | this.rotateCamera = ( function () { 139 | 140 | var axis = new THREE.Vector3(), 141 | quaternion = new THREE.Quaternion(), 142 | eyeDirection = new THREE.Vector3(), 143 | objectUpDirection = new THREE.Vector3(), 144 | objectSidewaysDirection = new THREE.Vector3(), 145 | moveDirection = new THREE.Vector3(), 146 | angle; 147 | 148 | return function rotateCamera() { 149 | 150 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); 151 | angle = moveDirection.length(); 152 | 153 | if ( angle ) { 154 | 155 | _eye.copy( _this.object.position ).sub( _this.target ); 156 | 157 | eyeDirection.copy( _eye ).normalize(); 158 | objectUpDirection.copy( _this.object.up ).normalize(); 159 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); 160 | 161 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); 162 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); 163 | 164 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); 165 | 166 | axis.crossVectors( moveDirection, _eye ).normalize(); 167 | 168 | angle *= _this.rotateSpeed; 169 | quaternion.setFromAxisAngle( axis, angle ); 170 | 171 | _eye.applyQuaternion( quaternion ); 172 | _this.object.up.applyQuaternion( quaternion ); 173 | 174 | _lastAxis.copy( axis ); 175 | _lastAngle = angle; 176 | 177 | } else if ( ! _this.staticMoving && _lastAngle ) { 178 | 179 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor ); 180 | _eye.copy( _this.object.position ).sub( _this.target ); 181 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); 182 | _eye.applyQuaternion( quaternion ); 183 | _this.object.up.applyQuaternion( quaternion ); 184 | 185 | } 186 | 187 | _movePrev.copy( _moveCurr ); 188 | 189 | }; 190 | 191 | }() ); 192 | 193 | 194 | this.zoomCamera = function () { 195 | 196 | var factor; 197 | 198 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 199 | 200 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 201 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 202 | _eye.multiplyScalar( factor ); 203 | 204 | } else { 205 | 206 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 207 | 208 | if ( factor !== 1.0 && factor > 0.0 ) { 209 | 210 | _eye.multiplyScalar( factor ); 211 | 212 | } 213 | 214 | if ( _this.staticMoving ) { 215 | 216 | _zoomStart.copy( _zoomEnd ); 217 | 218 | } else { 219 | 220 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 221 | 222 | } 223 | 224 | } 225 | 226 | }; 227 | 228 | this.panCamera = ( function () { 229 | 230 | var mouseChange = new THREE.Vector2(), 231 | objectUp = new THREE.Vector3(), 232 | pan = new THREE.Vector3(); 233 | 234 | return function panCamera() { 235 | 236 | mouseChange.copy( _panEnd ).sub( _panStart ); 237 | 238 | if ( mouseChange.lengthSq() ) { 239 | 240 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 241 | 242 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 243 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 244 | 245 | _this.object.position.add( pan ); 246 | _this.target.add( pan ); 247 | 248 | if ( _this.staticMoving ) { 249 | 250 | _panStart.copy( _panEnd ); 251 | 252 | } else { 253 | 254 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 255 | 256 | } 257 | 258 | } 259 | 260 | }; 261 | 262 | }() ); 263 | 264 | this.checkDistances = function () { 265 | 266 | if ( ! _this.noZoom || ! _this.noPan ) { 267 | 268 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 269 | 270 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 271 | _zoomStart.copy( _zoomEnd ); 272 | 273 | } 274 | 275 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 276 | 277 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 278 | _zoomStart.copy( _zoomEnd ); 279 | 280 | } 281 | 282 | } 283 | 284 | }; 285 | 286 | this.update = function () { 287 | 288 | _eye.subVectors( _this.object.position, _this.target ); 289 | 290 | if ( ! _this.noRotate ) { 291 | 292 | _this.rotateCamera(); 293 | 294 | } 295 | 296 | if ( ! _this.noZoom ) { 297 | 298 | _this.zoomCamera(); 299 | 300 | } 301 | 302 | if ( ! _this.noPan ) { 303 | 304 | _this.panCamera(); 305 | 306 | } 307 | 308 | _this.object.position.addVectors( _this.target, _eye ); 309 | 310 | _this.checkDistances(); 311 | 312 | _this.object.lookAt( _this.target ); 313 | 314 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { 315 | 316 | _this.dispatchEvent( changeEvent ); 317 | 318 | lastPosition.copy( _this.object.position ); 319 | 320 | } 321 | 322 | }; 323 | 324 | this.reset = function () { 325 | 326 | _state = STATE.NONE; 327 | _prevState = STATE.NONE; 328 | 329 | _this.target.copy( _this.target0 ); 330 | _this.object.position.copy( _this.position0 ); 331 | _this.object.up.copy( _this.up0 ); 332 | 333 | _eye.subVectors( _this.object.position, _this.target ); 334 | 335 | _this.object.lookAt( _this.target ); 336 | 337 | _this.dispatchEvent( changeEvent ); 338 | 339 | lastPosition.copy( _this.object.position ); 340 | 341 | }; 342 | 343 | // listeners 344 | 345 | function keydown( event ) { 346 | 347 | if ( _this.enabled === false ) return; 348 | 349 | window.removeEventListener( 'keydown', keydown ); 350 | 351 | _prevState = _state; 352 | 353 | if ( _state !== STATE.NONE ) { 354 | 355 | return; 356 | 357 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { 358 | 359 | _state = STATE.ROTATE; 360 | 361 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { 362 | 363 | _state = STATE.ZOOM; 364 | 365 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { 366 | 367 | _state = STATE.PAN; 368 | 369 | } 370 | 371 | } 372 | 373 | function keyup( event ) { 374 | 375 | if ( _this.enabled === false ) return; 376 | 377 | _state = _prevState; 378 | 379 | window.addEventListener( 'keydown', keydown, false ); 380 | 381 | } 382 | 383 | function mousedown( event ) { 384 | 385 | if ( _this.enabled === false ) return; 386 | 387 | event.preventDefault(); 388 | event.stopPropagation(); 389 | 390 | if ( _state === STATE.NONE ) { 391 | 392 | _state = event.button; 393 | 394 | } 395 | 396 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 397 | 398 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 399 | _movePrev.copy( _moveCurr ); 400 | 401 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 402 | 403 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 404 | _zoomEnd.copy( _zoomStart ); 405 | 406 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 407 | 408 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 409 | _panEnd.copy( _panStart ); 410 | 411 | } 412 | 413 | document.addEventListener( 'mousemove', mousemove, false ); 414 | document.addEventListener( 'mouseup', mouseup, false ); 415 | 416 | _this.dispatchEvent( startEvent ); 417 | 418 | } 419 | 420 | function mousemove( event ) { 421 | 422 | if ( _this.enabled === false ) return; 423 | 424 | event.preventDefault(); 425 | event.stopPropagation(); 426 | 427 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 428 | 429 | _movePrev.copy( _moveCurr ); 430 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 431 | 432 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 433 | 434 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 435 | 436 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 437 | 438 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 439 | 440 | } 441 | 442 | } 443 | 444 | function mouseup( event ) { 445 | 446 | if ( _this.enabled === false ) return; 447 | 448 | event.preventDefault(); 449 | event.stopPropagation(); 450 | 451 | _state = STATE.NONE; 452 | 453 | document.removeEventListener( 'mousemove', mousemove ); 454 | document.removeEventListener( 'mouseup', mouseup ); 455 | _this.dispatchEvent( endEvent ); 456 | 457 | } 458 | 459 | function mousewheel( event ) { 460 | 461 | if ( _this.enabled === false ) return; 462 | 463 | if ( _this.noZoom === true ) return; 464 | 465 | event.preventDefault(); 466 | event.stopPropagation(); 467 | 468 | switch ( event.deltaMode ) { 469 | 470 | case 2: 471 | // Zoom in pages 472 | _zoomStart.y -= event.deltaY * 0.025; 473 | break; 474 | 475 | case 1: 476 | // Zoom in lines 477 | _zoomStart.y -= event.deltaY * 0.01; 478 | break; 479 | 480 | default: 481 | // undefined, 0, assume pixels 482 | _zoomStart.y -= event.deltaY * 0.00025; 483 | break; 484 | 485 | } 486 | 487 | _this.dispatchEvent( startEvent ); 488 | _this.dispatchEvent( endEvent ); 489 | 490 | } 491 | 492 | function touchstart( event ) { 493 | 494 | if ( _this.enabled === false ) return; 495 | 496 | event.preventDefault(); 497 | 498 | switch ( event.touches.length ) { 499 | 500 | case 1: 501 | _state = STATE.TOUCH_ROTATE; 502 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 503 | _movePrev.copy( _moveCurr ); 504 | break; 505 | 506 | default: // 2 or more 507 | _state = STATE.TOUCH_ZOOM_PAN; 508 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 509 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 510 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 511 | 512 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 513 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 514 | _panStart.copy( getMouseOnScreen( x, y ) ); 515 | _panEnd.copy( _panStart ); 516 | break; 517 | 518 | } 519 | 520 | _this.dispatchEvent( startEvent ); 521 | 522 | } 523 | 524 | function touchmove( event ) { 525 | 526 | if ( _this.enabled === false ) return; 527 | 528 | event.preventDefault(); 529 | event.stopPropagation(); 530 | 531 | switch ( event.touches.length ) { 532 | 533 | case 1: 534 | _movePrev.copy( _moveCurr ); 535 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 536 | break; 537 | 538 | default: // 2 or more 539 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 540 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 541 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 542 | 543 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 544 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 545 | _panEnd.copy( getMouseOnScreen( x, y ) ); 546 | break; 547 | 548 | } 549 | 550 | } 551 | 552 | function touchend( event ) { 553 | 554 | if ( _this.enabled === false ) return; 555 | 556 | switch ( event.touches.length ) { 557 | 558 | case 0: 559 | _state = STATE.NONE; 560 | break; 561 | 562 | case 1: 563 | _state = STATE.TOUCH_ROTATE; 564 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 565 | _movePrev.copy( _moveCurr ); 566 | break; 567 | 568 | } 569 | 570 | _this.dispatchEvent( endEvent ); 571 | 572 | } 573 | 574 | function contextmenu( event ) { 575 | 576 | if ( _this.enabled === false ) return; 577 | 578 | event.preventDefault(); 579 | 580 | } 581 | 582 | this.dispose = function () { 583 | 584 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 585 | this.domElement.removeEventListener( 'mousedown', mousedown, false ); 586 | this.domElement.removeEventListener( 'wheel', mousewheel, false ); 587 | 588 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 589 | this.domElement.removeEventListener( 'touchend', touchend, false ); 590 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 591 | 592 | document.removeEventListener( 'mousemove', mousemove, false ); 593 | document.removeEventListener( 'mouseup', mouseup, false ); 594 | 595 | window.removeEventListener( 'keydown', keydown, false ); 596 | window.removeEventListener( 'keyup', keyup, false ); 597 | 598 | }; 599 | 600 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 601 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 602 | this.domElement.addEventListener( 'wheel', mousewheel, false ); 603 | 604 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 605 | this.domElement.addEventListener( 'touchend', touchend, false ); 606 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 607 | 608 | window.addEventListener( 'keydown', keydown, false ); 609 | window.addEventListener( 'keyup', keyup, false ); 610 | 611 | this.handleResize(); 612 | 613 | // force an update at start 614 | this.update(); 615 | 616 | }; 617 | 618 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 619 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; 620 | -------------------------------------------------------------------------------- /js/heatmap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * heatmap.js v2.0.5 | JavaScript Heatmap Library 3 | * 4 | * Copyright 2008-2016 Patrick Wied - All rights reserved. 5 | * Dual licensed under MIT and Beerware license 6 | * 7 | * :: 2016-09-05 01:16 8 | */ 9 | ;(function (name, context, factory) { 10 | 11 | // Supports UMD. AMD, CommonJS/Node.js and browser context 12 | if (typeof module !== "undefined" && module.exports) { 13 | module.exports = factory(); 14 | } else if (typeof define === "function" && define.amd) { 15 | define(factory); 16 | } else { 17 | context[name] = factory(); 18 | } 19 | 20 | })("h337", this, function () { 21 | 22 | // Heatmap Config stores default values and will be merged with instance config 23 | var HeatmapConfig = { 24 | defaultRadius: 40, 25 | defaultRenderer: 'canvas2d', 26 | defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"}, 27 | defaultMaxOpacity: 1, 28 | defaultMinOpacity: 0, 29 | defaultBlur: .85, 30 | defaultXField: 'x', 31 | defaultYField: 'y', 32 | defaultValueField: 'value', 33 | plugins: {} 34 | }; 35 | var Store = (function StoreClosure() { 36 | 37 | var Store = function Store(config) { 38 | this._coordinator = {}; 39 | this._data = []; 40 | this._radi = []; 41 | this._min = 10; 42 | this._max = 1; 43 | this._xField = config['xField'] || config.defaultXField; 44 | this._yField = config['yField'] || config.defaultYField; 45 | this._valueField = config['valueField'] || config.defaultValueField; 46 | 47 | if (config["radius"]) { 48 | this._cfgRadius = config["radius"]; 49 | } 50 | }; 51 | 52 | var defaultRadius = HeatmapConfig.defaultRadius; 53 | 54 | Store.prototype = { 55 | // when forceRender = false -> called from setData, omits renderall event 56 | _organiseData: function(dataPoint, forceRender) { 57 | var x = dataPoint[this._xField]; 58 | var y = dataPoint[this._yField]; 59 | var radi = this._radi; 60 | var store = this._data; 61 | var max = this._max; 62 | var min = this._min; 63 | var value = dataPoint[this._valueField] || 1; 64 | var radius = dataPoint.radius || this._cfgRadius || defaultRadius; 65 | 66 | if (!store[x]) { 67 | store[x] = []; 68 | radi[x] = []; 69 | } 70 | 71 | if (!store[x][y]) { 72 | store[x][y] = value; 73 | radi[x][y] = radius; 74 | } else { 75 | store[x][y] += value; 76 | } 77 | var storedVal = store[x][y]; 78 | 79 | if (storedVal > max) { 80 | if (!forceRender) { 81 | this._max = storedVal; 82 | } else { 83 | this.setDataMax(storedVal); 84 | } 85 | return false; 86 | } else if (storedVal < min) { 87 | if (!forceRender) { 88 | this._min = storedVal; 89 | } else { 90 | this.setDataMin(storedVal); 91 | } 92 | return false; 93 | } else { 94 | return { 95 | x: x, 96 | y: y, 97 | value: value, 98 | radius: radius, 99 | min: min, 100 | max: max 101 | }; 102 | } 103 | }, 104 | _unOrganizeData: function() { 105 | var unorganizedData = []; 106 | var data = this._data; 107 | var radi = this._radi; 108 | 109 | for (var x in data) { 110 | for (var y in data[x]) { 111 | 112 | unorganizedData.push({ 113 | x: x, 114 | y: y, 115 | radius: radi[x][y], 116 | value: data[x][y] 117 | }); 118 | 119 | } 120 | } 121 | return { 122 | min: this._min, 123 | max: this._max, 124 | data: unorganizedData 125 | }; 126 | }, 127 | _onExtremaChange: function() { 128 | this._coordinator.emit('extremachange', { 129 | min: this._min, 130 | max: this._max 131 | }); 132 | }, 133 | addData: function() { 134 | if (arguments[0].length > 0) { 135 | var dataArr = arguments[0]; 136 | var dataLen = dataArr.length; 137 | while (dataLen--) { 138 | this.addData.call(this, dataArr[dataLen]); 139 | } 140 | } else { 141 | // add to store 142 | var organisedEntry = this._organiseData(arguments[0], true); 143 | if (organisedEntry) { 144 | // if it's the first datapoint initialize the extremas with it 145 | if (this._data.length === 0) { 146 | this._min = this._max = organisedEntry.value; 147 | } 148 | this._coordinator.emit('renderpartial', { 149 | min: this._min, 150 | max: this._max, 151 | data: [organisedEntry] 152 | }); 153 | } 154 | } 155 | return this; 156 | }, 157 | setData: function(data) { 158 | var dataPoints = data.data; 159 | var pointsLen = dataPoints.length; 160 | 161 | 162 | // reset data arrays 163 | this._data = []; 164 | this._radi = []; 165 | 166 | for(var i = 0; i < pointsLen; i++) { 167 | this._organiseData(dataPoints[i], false); 168 | } 169 | this._max = data.max; 170 | this._min = data.min || 0; 171 | 172 | this._onExtremaChange(); 173 | this._coordinator.emit('renderall', this._getInternalData()); 174 | return this; 175 | }, 176 | removeData: function() { 177 | // TODO: implement 178 | }, 179 | setDataMax: function(max) { 180 | this._max = max; 181 | this._onExtremaChange(); 182 | this._coordinator.emit('renderall', this._getInternalData()); 183 | return this; 184 | }, 185 | setDataMin: function(min) { 186 | this._min = min; 187 | this._onExtremaChange(); 188 | this._coordinator.emit('renderall', this._getInternalData()); 189 | return this; 190 | }, 191 | setCoordinator: function(coordinator) { 192 | this._coordinator = coordinator; 193 | }, 194 | _getInternalData: function() { 195 | return { 196 | max: this._max, 197 | min: this._min, 198 | data: this._data, 199 | radi: this._radi 200 | }; 201 | }, 202 | getData: function() { 203 | return this._unOrganizeData(); 204 | }/*, 205 | 206 | TODO: rethink. 207 | 208 | getValueAt: function(point) { 209 | var value; 210 | var radius = 100; 211 | var x = point.x; 212 | var y = point.y; 213 | var data = this._data; 214 | 215 | if (data[x] && data[x][y]) { 216 | return data[x][y]; 217 | } else { 218 | var values = []; 219 | // radial search for datapoints based on default radius 220 | for(var distance = 1; distance < radius; distance++) { 221 | var neighbors = distance * 2 +1; 222 | var startX = x - distance; 223 | var startY = y - distance; 224 | 225 | for(var i = 0; i < neighbors; i++) { 226 | for (var o = 0; o < neighbors; o++) { 227 | if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) { 228 | if (data[startY+i] && data[startY+i][startX+o]) { 229 | values.push(data[startY+i][startX+o]); 230 | } 231 | } else { 232 | continue; 233 | } 234 | } 235 | } 236 | } 237 | if (values.length > 0) { 238 | return Math.max.apply(Math, values); 239 | } 240 | } 241 | return false; 242 | }*/ 243 | }; 244 | 245 | 246 | return Store; 247 | })(); 248 | 249 | var Canvas2dRenderer = (function Canvas2dRendererClosure() { 250 | 251 | var _getColorPalette = function(config) { 252 | var gradientConfig = config.gradient || config.defaultGradient; 253 | var paletteCanvas = document.createElement('canvas'); 254 | var paletteCtx = paletteCanvas.getContext('2d'); 255 | 256 | paletteCanvas.width = 256; 257 | paletteCanvas.height = 1; 258 | 259 | var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1); 260 | for (var key in gradientConfig) { 261 | gradient.addColorStop(key, gradientConfig[key]); 262 | } 263 | 264 | paletteCtx.fillStyle = gradient; 265 | paletteCtx.fillRect(0, 0, 256, 1); 266 | 267 | return paletteCtx.getImageData(0, 0, 256, 1).data; 268 | }; 269 | 270 | var _getPointTemplate = function(radius, blurFactor) { 271 | var tplCanvas = document.createElement('canvas'); 272 | var tplCtx = tplCanvas.getContext('2d'); 273 | var x = radius; 274 | var y = radius; 275 | tplCanvas.width = tplCanvas.height = radius*2; 276 | 277 | if (blurFactor == 1) { 278 | tplCtx.beginPath(); 279 | tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false); 280 | tplCtx.fillStyle = 'rgba(0,0,0,1)'; 281 | tplCtx.fill(); 282 | } else { 283 | var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius); 284 | gradient.addColorStop(0, 'rgba(0,0,0,1)'); 285 | gradient.addColorStop(1, 'rgba(0,0,0,0)'); 286 | tplCtx.fillStyle = gradient; 287 | tplCtx.fillRect(0, 0, 2*radius, 2*radius); 288 | } 289 | 290 | 291 | 292 | return tplCanvas; 293 | }; 294 | 295 | var _prepareData = function(data) { 296 | var renderData = []; 297 | var min = data.min; 298 | var max = data.max; 299 | var radi = data.radi; 300 | var data = data.data; 301 | 302 | var xValues = Object.keys(data); 303 | var xValuesLen = xValues.length; 304 | 305 | while(xValuesLen--) { 306 | var xValue = xValues[xValuesLen]; 307 | var yValues = Object.keys(data[xValue]); 308 | var yValuesLen = yValues.length; 309 | while(yValuesLen--) { 310 | var yValue = yValues[yValuesLen]; 311 | var value = data[xValue][yValue]; 312 | var radius = radi[xValue][yValue]; 313 | renderData.push({ 314 | x: xValue, 315 | y: yValue, 316 | value: value, 317 | radius: radius 318 | }); 319 | } 320 | } 321 | 322 | return { 323 | min: min, 324 | max: max, 325 | data: renderData 326 | }; 327 | }; 328 | 329 | 330 | function Canvas2dRenderer(config) { 331 | var container = config.container; 332 | var shadowCanvas = this.shadowCanvas = document.createElement('canvas'); 333 | var canvas = this.canvas = config.canvas || document.createElement('canvas'); 334 | var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0]; 335 | 336 | var computed = getComputedStyle(config.container) || {}; 337 | 338 | canvas.className = 'heatmap-canvas'; 339 | 340 | this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,'')); 341 | this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,'')); 342 | 343 | this.shadowCtx = shadowCanvas.getContext('2d'); 344 | this.ctx = canvas.getContext('2d'); 345 | 346 | // @TODO: 347 | // conditional wrapper 348 | 349 | canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;'; 350 | 351 | container.style.position = 'relative'; 352 | container.appendChild(canvas); 353 | 354 | this._palette = _getColorPalette(config); 355 | this._templates = {}; 356 | 357 | this._setStyles(config); 358 | }; 359 | 360 | Canvas2dRenderer.prototype = { 361 | renderPartial: function(data) { 362 | if (data.data.length > 0) { 363 | this._drawAlpha(data); 364 | this._colorize(); 365 | } 366 | }, 367 | renderAll: function(data) { 368 | // reset render boundaries 369 | this._clear(); 370 | if (data.data.length > 0) { 371 | this._drawAlpha(_prepareData(data)); 372 | this._colorize(); 373 | } 374 | }, 375 | _updateGradient: function(config) { 376 | this._palette = _getColorPalette(config); 377 | }, 378 | updateConfig: function(config) { 379 | if (config['gradient']) { 380 | this._updateGradient(config); 381 | } 382 | this._setStyles(config); 383 | }, 384 | setDimensions: function(width, height) { 385 | this._width = width; 386 | this._height = height; 387 | this.canvas.width = this.shadowCanvas.width = width; 388 | this.canvas.height = this.shadowCanvas.height = height; 389 | }, 390 | _clear: function() { 391 | this.shadowCtx.clearRect(0, 0, this._width, this._height); 392 | this.ctx.clearRect(0, 0, this._width, this._height); 393 | }, 394 | _setStyles: function(config) { 395 | this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur); 396 | 397 | if (config.backgroundColor) { 398 | this.canvas.style.backgroundColor = config.backgroundColor; 399 | } 400 | 401 | this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width; 402 | this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height; 403 | 404 | 405 | this._opacity = (config.opacity || 0) * 255; 406 | this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255; 407 | this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255; 408 | this._useGradientOpacity = !!config.useGradientOpacity; 409 | }, 410 | _drawAlpha: function(data) { 411 | var min = this._min = data.min; 412 | var max = this._max = data.max; 413 | var data = data.data || []; 414 | var dataLen = data.length; 415 | // on a point basis? 416 | var blur = 1 - this._blur; 417 | 418 | while(dataLen--) { 419 | 420 | var point = data[dataLen]; 421 | 422 | var x = point.x; 423 | var y = point.y; 424 | var radius = point.radius; 425 | // if value is bigger than max 426 | // use max as value 427 | var value = Math.min(point.value, max); 428 | var rectX = x - radius; 429 | var rectY = y - radius; 430 | var shadowCtx = this.shadowCtx; 431 | 432 | 433 | 434 | 435 | var tpl; 436 | if (!this._templates[radius]) { 437 | this._templates[radius] = tpl = _getPointTemplate(radius, blur); 438 | } else { 439 | tpl = this._templates[radius]; 440 | } 441 | // value from minimum / value range 442 | // => [0, 1] 443 | var templateAlpha = (value-min)/(max-min); 444 | // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData 445 | shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha; 446 | 447 | shadowCtx.drawImage(tpl, rectX, rectY); 448 | 449 | // update renderBoundaries 450 | if (rectX < this._renderBoundaries[0]) { 451 | this._renderBoundaries[0] = rectX; 452 | } 453 | if (rectY < this._renderBoundaries[1]) { 454 | this._renderBoundaries[1] = rectY; 455 | } 456 | if (rectX + 2*radius > this._renderBoundaries[2]) { 457 | this._renderBoundaries[2] = rectX + 2*radius; 458 | } 459 | if (rectY + 2*radius > this._renderBoundaries[3]) { 460 | this._renderBoundaries[3] = rectY + 2*radius; 461 | } 462 | 463 | } 464 | }, 465 | _colorize: function() { 466 | var x = this._renderBoundaries[0]; 467 | var y = this._renderBoundaries[1]; 468 | var width = this._renderBoundaries[2] - x; 469 | var height = this._renderBoundaries[3] - y; 470 | var maxWidth = this._width; 471 | var maxHeight = this._height; 472 | var opacity = this._opacity; 473 | var maxOpacity = this._maxOpacity; 474 | var minOpacity = this._minOpacity; 475 | var useGradientOpacity = this._useGradientOpacity; 476 | 477 | if (x < 0) { 478 | x = 0; 479 | } 480 | if (y < 0) { 481 | y = 0; 482 | } 483 | if (x + width > maxWidth) { 484 | width = maxWidth - x; 485 | } 486 | if (y + height > maxHeight) { 487 | height = maxHeight - y; 488 | } 489 | 490 | var img = this.shadowCtx.getImageData(x, y, width, height); 491 | var imgData = img.data; 492 | var len = imgData.length; 493 | var palette = this._palette; 494 | 495 | 496 | for (var i = 3; i < len; i+= 4) { 497 | var alpha = imgData[i]; 498 | var offset = alpha * 4; 499 | 500 | 501 | if (!offset) { 502 | continue; 503 | } 504 | 505 | var finalAlpha; 506 | if (opacity > 0) { 507 | finalAlpha = opacity; 508 | } else { 509 | if (alpha < maxOpacity) { 510 | if (alpha < minOpacity) { 511 | finalAlpha = minOpacity; 512 | } else { 513 | finalAlpha = alpha; 514 | } 515 | } else { 516 | finalAlpha = maxOpacity; 517 | } 518 | } 519 | 520 | imgData[i-3] = palette[offset]; 521 | imgData[i-2] = palette[offset + 1]; 522 | imgData[i-1] = palette[offset + 2]; 523 | imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha; 524 | 525 | } 526 | 527 | img.data = imgData; 528 | this.ctx.putImageData(img, x, y); 529 | 530 | this._renderBoundaries = [1000, 1000, 0, 0]; 531 | 532 | }, 533 | getValueAt: function(point) { 534 | var value; 535 | var shadowCtx = this.shadowCtx; 536 | var img = shadowCtx.getImageData(point.x, point.y, 1, 1); 537 | var data = img.data[3]; 538 | var max = this._max; 539 | var min = this._min; 540 | 541 | value = (Math.abs(max-min) * (data/255)) >> 0; 542 | 543 | return value; 544 | }, 545 | getDataURL: function() { 546 | return this.canvas.toDataURL(); 547 | } 548 | }; 549 | 550 | 551 | return Canvas2dRenderer; 552 | })(); 553 | 554 | 555 | var Renderer = (function RendererClosure() { 556 | 557 | var rendererFn = false; 558 | 559 | if (HeatmapConfig['defaultRenderer'] === 'canvas2d') { 560 | rendererFn = Canvas2dRenderer; 561 | } 562 | 563 | return rendererFn; 564 | })(); 565 | 566 | 567 | var Util = { 568 | merge: function() { 569 | var merged = {}; 570 | var argsLen = arguments.length; 571 | for (var i = 0; i < argsLen; i++) { 572 | var obj = arguments[i] 573 | for (var key in obj) { 574 | merged[key] = obj[key]; 575 | } 576 | } 577 | return merged; 578 | } 579 | }; 580 | // Heatmap Constructor 581 | var Heatmap = (function HeatmapClosure() { 582 | 583 | var Coordinator = (function CoordinatorClosure() { 584 | 585 | function Coordinator() { 586 | this.cStore = {}; 587 | }; 588 | 589 | Coordinator.prototype = { 590 | on: function(evtName, callback, scope) { 591 | var cStore = this.cStore; 592 | 593 | if (!cStore[evtName]) { 594 | cStore[evtName] = []; 595 | } 596 | cStore[evtName].push((function(data) { 597 | return callback.call(scope, data); 598 | })); 599 | }, 600 | emit: function(evtName, data) { 601 | var cStore = this.cStore; 602 | if (cStore[evtName]) { 603 | var len = cStore[evtName].length; 604 | for (var i=0; i