forked from rstudio/leaflet
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
use unminified pull to experiment with heatmap
- Loading branch information
1 parent
3823682
commit 787f623
Showing
2 changed files
with
350 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,346 @@ | ||
/* | ||
(c) 2014, Vladimir Agafonkin | ||
simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas | ||
https://github.com/mourner/simpleheat | ||
(c) 2014, Vladimir Agafonkin | ||
Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. | ||
https://github.com/Leaflet/Leaflet.heat | ||
*/ | ||
!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* | ||
(c) 2014, Vladimir Agafonkin | ||
Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. | ||
https://github.com/Leaflet/Leaflet.heat | ||
|
||
L.HeatLayer = (L.Layer ? L.Layer : L.Class).extend({ | ||
|
||
// options: { | ||
// minOpacity: 0.05, | ||
// maxZoom: 18, | ||
// radius: 25, | ||
// blur: 15, | ||
// max: 1.0 | ||
// }, | ||
|
||
initialize: function (latlngs, options) { | ||
this._latlngs = latlngs; | ||
L.setOptions(this, options); | ||
}, | ||
|
||
setLatLngs: function (latlngs) { | ||
this._latlngs = latlngs; | ||
return this.redraw(); | ||
}, | ||
|
||
addLatLng: function (latlng) { | ||
this._latlngs.push(latlng); | ||
return this.redraw(); | ||
}, | ||
|
||
setOptions: function (options) { | ||
L.setOptions(this, options); | ||
if (this._heat) { | ||
this._updateOptions(); | ||
} | ||
return this.redraw(); | ||
}, | ||
|
||
redraw: function () { | ||
if (this._heat && !this._frame && !this._map._animating) { | ||
this._frame = L.Util.requestAnimFrame(this._redraw, this); | ||
} | ||
return this; | ||
}, | ||
|
||
onAdd: function (map) { | ||
this._map = map; | ||
|
||
if (!this._canvas) { | ||
this._initCanvas(); | ||
} | ||
|
||
map._panes.overlayPane.appendChild(this._canvas); | ||
|
||
map.on('moveend', this._reset, this); | ||
|
||
if (map.options.zoomAnimation && L.Browser.any3d) { | ||
map.on('zoomanim', this._animateZoom, this); | ||
} | ||
|
||
this._reset(); | ||
}, | ||
|
||
onRemove: function (map) { | ||
map.getPanes().overlayPane.removeChild(this._canvas); | ||
|
||
map.off('moveend', this._reset, this); | ||
|
||
if (map.options.zoomAnimation) { | ||
map.off('zoomanim', this._animateZoom, this); | ||
} | ||
}, | ||
|
||
addTo: function (map) { | ||
map.addLayer(this); | ||
return this; | ||
}, | ||
|
||
_initCanvas: function () { | ||
var canvas = this._canvas = L.DomUtil.create('canvas', 'leaflet-heatmap-layer leaflet-layer'); | ||
|
||
var size = this._map.getSize(); | ||
canvas.width = size.x; | ||
canvas.height = size.y; | ||
|
||
var animated = this._map.options.zoomAnimation && L.Browser.any3d; | ||
L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')); | ||
|
||
this._heat = simpleheat(canvas); | ||
this._updateOptions(); | ||
}, | ||
|
||
_updateOptions: function () { | ||
this._heat.radius(this.options.radius || this._heat.defaultRadius, this.options.blur); | ||
|
||
if (this.options.gradient) { | ||
this._heat.gradient(this.options.gradient); | ||
} | ||
if (this.options.max) { | ||
this._heat.max(this.options.max); | ||
} | ||
}, | ||
|
||
_reset: function () { | ||
var topLeft = this._map.containerPointToLayerPoint([0, 0]); | ||
L.DomUtil.setPosition(this._canvas, topLeft); | ||
|
||
var size = this._map.getSize(); | ||
|
||
if (this._heat._width !== size.x) { | ||
this._canvas.width = this._heat._width = size.x; | ||
} | ||
if (this._heat._height !== size.y) { | ||
this._canvas.height = this._heat._height = size.y; | ||
} | ||
|
||
this._redraw(); | ||
}, | ||
|
||
_redraw: function () { | ||
var data = [], | ||
r = this._heat._r, | ||
size = this._map.getSize(), | ||
bounds = new L.LatLngBounds( | ||
this._map.containerPointToLatLng(L.point([-r, -r])), | ||
this._map.containerPointToLatLng(size.add([r, r]))), | ||
|
||
max = this.options.max === undefined ? 1 : this.options.max, | ||
maxZoom = this.options.maxZoom === undefined ? this._map.getMaxZoom() : this.options.maxZoom, | ||
v = 1, // vestigial | ||
cellSize = r / 2, | ||
grid = [], | ||
panePos = this._map._getMapPanePos(), | ||
offsetX = panePos.x % cellSize, | ||
offsetY = panePos.y % cellSize, | ||
i, len, p, cell, x, y, j, len2, k; | ||
|
||
// console.time('process'); | ||
for (i = 0, len = this._latlngs.length; i < len; i++) { | ||
if (bounds.contains(this._latlngs[i])) { | ||
p = this._map.latLngToContainerPoint(this._latlngs[i]); | ||
x = Math.floor((p.x - offsetX) / cellSize) + 2; | ||
y = Math.floor((p.y - offsetY) / cellSize) + 2; | ||
|
||
var alt = | ||
this._latlngs[i].alt !== undefined ? this._latlngs[i].alt : | ||
this._latlngs[i][2] !== undefined ? +this._latlngs[i][2] : 1; | ||
k = alt * v; | ||
|
||
grid[y] = grid[y] || []; | ||
cell = grid[y][x]; | ||
|
||
if (!cell) { | ||
grid[y][x] = [p.x, p.y, k]; | ||
|
||
} else { | ||
cell[0] = (cell[0] * cell[2] + p.x * k) / (cell[2] + k); // x | ||
cell[1] = (cell[1] * cell[2] + p.y * k) / (cell[2] + k); // y | ||
// Join multiple cell values using alpha blending | ||
cell[2] = (cell[2] * (1 - k/max)) + k; | ||
} | ||
} | ||
} | ||
|
||
for (i = 0, len = grid.length; i < len; i++) { | ||
if (grid[i]) { | ||
for (j = 0, len2 = grid[i].length; j < len2; j++) { | ||
cell = grid[i][j]; | ||
if (cell) { | ||
data.push([ | ||
Math.round(cell[0]), | ||
Math.round(cell[1]), | ||
Math.min(cell[2], max) | ||
]); | ||
} | ||
} | ||
} | ||
} | ||
// console.timeEnd('process'); | ||
|
||
// console.time('draw ' + data.length); | ||
this._heat.data(data).draw(this.options.minOpacity); | ||
// console.timeEnd('draw ' + data.length); | ||
|
||
this._frame = null; | ||
}, | ||
|
||
_animateZoom: function (e) { | ||
var scale = this._map.getZoomScale(e.zoom), | ||
offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos()); | ||
|
||
if (L.DomUtil.setTransform) { | ||
L.DomUtil.setTransform(this._canvas, offset, scale); | ||
|
||
} else { | ||
this._canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')'; | ||
} | ||
} | ||
}); | ||
|
||
L.heatLayer = function (latlngs, options) { | ||
return new L.HeatLayer(latlngs, options); | ||
}; | ||
|
||
/* | ||
(c) 2014, Vladimir Agafonkin | ||
simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas | ||
https://github.com/mourner/simpleheat | ||
*/ | ||
L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=this._map.getSize();t.width=i.x,t.height=i.y;var a=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(a?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,_=[],d=this._heat._r,l=this._map.getSize(),m=new L.LatLngBounds(this._map.containerPointToLatLng(L.point([-d,-d])),this._map.containerPointToLatLng(l.add([d,d]))),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,g=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),f=d/2,p=[],v=this._map._getMapPanePos(),w=v.x%f,y=v.y%f;for(t=0,i=this._latlngs.length;i>t;t++)if(m.contains(this._latlngs[t])){a=this._map.latLngToContainerPoint(this._latlngs[t]),e=Math.floor((a.x-w)/f)+2,n=Math.floor((a.y-y)/f)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*g,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&_.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(_).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)}; | ||
|
||
(function () { 'use strict'; | ||
|
||
function simpleheat(canvas) { | ||
// jshint newcap: false, validthis: true | ||
if (!(this instanceof simpleheat)) { return new simpleheat(canvas); } | ||
|
||
this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; | ||
|
||
this._ctx = canvas.getContext('2d'); | ||
this._width = canvas.width; | ||
this._height = canvas.height; | ||
|
||
this._max = 1; | ||
this._data = []; | ||
} | ||
|
||
simpleheat.prototype = { | ||
|
||
defaultRadius: 25, | ||
|
||
defaultGradient: { | ||
0.4: 'blue', | ||
0.6: 'cyan', | ||
0.7: 'lime', | ||
0.8: 'yellow', | ||
1.0: 'red' | ||
}, | ||
|
||
data: function (data) { | ||
this._data = data; | ||
return this; | ||
}, | ||
|
||
max: function (max) { | ||
this._max = max; | ||
return this; | ||
}, | ||
|
||
add: function (point) { | ||
this._data.push(point); | ||
return this; | ||
}, | ||
|
||
clear: function () { | ||
this._data = []; | ||
return this; | ||
}, | ||
|
||
radius: function (r, blur) { | ||
blur = blur === undefined ? 15 : blur; | ||
|
||
// create a grayscale blurred circle image that we'll use for drawing points | ||
var circle = this._circle = document.createElement('canvas'), | ||
ctx = circle.getContext('2d'), | ||
r2 = this._r = r + blur; | ||
|
||
circle.width = circle.height = r2 * 2; | ||
|
||
ctx.shadowOffsetX = ctx.shadowOffsetY = 200; | ||
ctx.shadowBlur = blur; | ||
ctx.shadowColor = 'black'; | ||
|
||
ctx.beginPath(); | ||
ctx.arc(r2 - 200, r2 - 200, r, 0, Math.PI * 2, true); | ||
ctx.closePath(); | ||
ctx.fill(); | ||
|
||
return this; | ||
}, | ||
|
||
gradient: function (grad) { | ||
// create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one | ||
var canvas = document.createElement('canvas'), | ||
ctx = canvas.getContext('2d'), | ||
gradient = ctx.createLinearGradient(0, 0, 0, 256); | ||
|
||
canvas.width = 1; | ||
canvas.height = 256; | ||
|
||
for (var i in grad) { | ||
gradient.addColorStop(i, grad[i]); | ||
} | ||
|
||
ctx.fillStyle = gradient; | ||
ctx.fillRect(0, 0, 1, 256); | ||
|
||
this._grad = ctx.getImageData(0, 0, 1, 256).data; | ||
|
||
return this; | ||
}, | ||
|
||
draw: function (minOpacity) { | ||
if (!this._circle) { | ||
this.radius(this.defaultRadius); | ||
} | ||
if (!this._grad) { | ||
this.gradient(this.defaultGradient); | ||
} | ||
|
||
var ctx = this._ctx; | ||
|
||
ctx.clearRect(0, 0, this._width, this._height); | ||
|
||
// draw a grayscale heatmap by putting a blurred circle at each data point | ||
for (var i = 0, len = this._data.length, p; i < len; i++) { | ||
p = this._data[i]; | ||
|
||
ctx.globalAlpha = Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity); | ||
ctx.drawImage(this._circle, p[0] - this._r, p[1] - this._r); | ||
} | ||
|
||
// colorize the heatmap, using opacity value of each pixel to get the right color from our gradient | ||
var colored = ctx.getImageData(0, 0, this._width, this._height); | ||
this._colorize(colored.data, this._grad); | ||
ctx.putImageData(colored, 0, 0); | ||
|
||
return this; | ||
}, | ||
|
||
_colorize: function (pixels, gradient) { | ||
for (var i = 3, len = pixels.length, j; i < len; i += 4) { | ||
j = pixels[i] * 4; // get gradient color from opacity value | ||
|
||
if (j) { | ||
pixels[i - 3] = gradient[j]; | ||
pixels[i - 2] = gradient[j + 1]; | ||
pixels[i - 1] = gradient[j + 2]; | ||
} | ||
} | ||
} | ||
}; | ||
|
||
window.simpleheat = simpleheat; | ||
|
||
})(); |