Skip to content

Commit

Permalink
use unminified pull to experiment with heatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
timelyportfolio committed Aug 31, 2015
1 parent 3823682 commit 787f623
Show file tree
Hide file tree
Showing 2 changed files with 350 additions and 8 deletions.
7 changes: 7 additions & 0 deletions inst/examples/heatmap.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ leaf <- leaflet( addressPoints ) %>%

leaf

# or create a dot map-like heatmap
# create our heatmap
leaflet( addressPoints ) %>% addTiles() %>%
setView( 175.475,-37.87, 12 ) %>%
addHeatmap(intensity=~value, radius = 2, blur = 1,
max = 1e-10, gradient = blues9[8:9] )

# customize our heatmap with options
leaf <- leaflet() %>%
addTiles() %>%
Expand Down
351 changes: 343 additions & 8 deletions inst/htmlwidgets/lib/leaflet-heat/leaflet-heat.js
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;

})();

0 comments on commit 787f623

Please sign in to comment.