├── _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 | 
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