diff --git a/CountryTweetMap/index.html b/CountryTweetMap/index.html
index 0862f081..8889edc6 100644
--- a/CountryTweetMap/index.html
+++ b/CountryTweetMap/index.html
@@ -23,6 +23,9 @@
crossorigin="">
+
+
diff --git a/CountryTweetMap/scripts/app.js b/CountryTweetMap/scripts/app.js
index 8ffdecdc..a7ff1478 100644
--- a/CountryTweetMap/scripts/app.js
+++ b/CountryTweetMap/scripts/app.js
@@ -14,6 +14,8 @@ app.controller("app", function ($scope, $http) {
$scope.plot = null;
$scope.isLoading = false;
$scope.error = "";
+ $scope.heatmapData = [];
+ $scope.totalTweets = 0;
$(".date").datepicker();
$(".date").datepicker( "option", "dateFormat", "yy-mm-dd");
var LeafIcon = L.Icon.extend({
@@ -91,6 +93,28 @@ app.controller("app", function ($scope, $http) {
});
}
+ $scope.setupHeatMapData = function () {
+ $scope.totalTweets = 0;
+ $scope.heatmapData = [];
+ /*for (var key in $scope.tweetFreq) {
+ $scope.heatmapData.push({
+ lat: $scope.tweetFreq[key][0].place_country_center[1],
+ lng: $scope.tweetFreq[key][0].place_country_center[0],
+ count: $scope.tweetFreq[key].length
+ });
+ $scope.totalTweets += $scope.tweetFreq[key].length;s
+ }*/
+
+ for (var key in $scope.tweetFreq) {
+ $scope.heatmapData.push([
+ $scope.tweetFreq[key][0].place_country_center[1],
+ $scope.tweetFreq[key][0].place_country_center[0],
+ $scope.tweetFreq[key].length
+ ]);
+ $scope.totalTweets += $scope.tweetFreq[key].length;
+ }
+ }
+
// Function to prepare frequency of tweets for individual countries
$scope.prepareFreq = function(data) {
data.forEach(function(status) {
@@ -105,8 +129,8 @@ app.controller("app", function ($scope, $http) {
var sumLon = 0.0;
for (var key in $scope.tweetFreq) {
- sumLat += $scope.tweetFreq[key][0].place_country_center[0];
- sumLon += $scope.tweetFreq[key][0].place_country_center[1];
+ sumLat += $scope.tweetFreq[key][0].place_country_center[1];
+ sumLon += $scope.tweetFreq[key][0].place_country_center[0];
$scope.modalList.push({
country_name: $scope.tweetFreq[key][0].place_country,
country_code: $scope.tweetFreq[key][0].place_country_code,
@@ -116,6 +140,7 @@ app.controller("app", function ($scope, $http) {
}
mapCenterLat = sumLat / $scope.countryCount;
mapCenterLon = sumLon / $scope.countryCount;
+ $scope.setupHeatMapData();
}
$scope.setUpInitialMap = function() {
@@ -175,15 +200,15 @@ app.controller("app", function ($scope, $http) {
var markerIcon = null;
var marker = null;
if (size === $scope.tweetMax) {
- markerIcon = L.marker(country.place_country_center, {icon: redIcon});
+ markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: redIcon});
marker = $scope.getMarker(markerIcon, country, key);
countriesHigh.push(marker);
} else if (size > rangeMid) {
- markerIcon = L.marker(country.place_country_center, {icon: blueIcon});
+ markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: blueIcon});
marker = $scope.getMarker(markerIcon, country, key);
countriesMedium.push(marker)
} else {
- markerIcon = L.marker(country.place_country_center, {icon: greenIcon});
+ markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: greenIcon});
marker = $scope.getMarker(markerIcon, country, key);
countriesLow.push(marker);
}
@@ -192,6 +217,40 @@ app.controller("app", function ($scope, $http) {
var countryMediumGroup = L.layerGroup(countriesMedium);
var countryLowGroup = L.layerGroup(countriesLow);
+ /*var heatMapPlotData = {
+ max: $scope.totalTweets,
+ data: $scope.heatmapData
+ }*/
+
+ /*var cfg = {
+ "radius": 4,
+ "maxOpacity": .8,
+ "scaleRadius": true,
+ "useLocalExtrema": true,
+ latField: 'lat',
+ lngField: 'lng',
+ valueField: 'count'
+ };*/
+
+ var gradientStart = (Math.round(($scope.totalTweets / 3) * 10) / 10).toFixed(1);
+ var gradientMiddle = (Math.round(($scope.totalTweets / 2) * 10) / 10).toFixed(1);
+ var gradientEnd = $scope.totalTweets;
+
+ var cfg = {
+ radius: 25,
+ max: $scope.totalTweets,
+ minOpacity: 0.6,
+ blur: 8,
+ gradient: {0.4: 'blue', 0.65: 'lime', 1.0: 'red'}
+ }
+
+ console.log($scope.heatmapData);
+ console.log(cfg);
+
+ //console.log(heatMapPlotData)
+
+ //var heatmapLayer = new HeatmapOverlay(cfg);
+
var backgroundLight = L.tileLayer(
'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
{
@@ -208,16 +267,20 @@ app.controller("app", function ($scope, $http) {
],
layers: [backgroundLight, countryHighGroup, countryMediumGroup, countryLowGroup]
});
+ //heatmapLayer.setData(heatMapPlotData);
+ var heat = L.heatLayer($scope.heatmapData, cfg);
var overlayMaps = {
"Maximum tweets": countryHighGroup,
"Medium tweets": countryMediumGroup,
- "Low tweets": countryLowGroup
+ "Low tweets": countryLowGroup,
+ "Heatmap": heat
};
var baseMaps = {
"basemap": backgroundLight
};
L.control.layers(baseMaps, overlayMaps).addTo($scope.map);
+
$scope.isLoading = false;
}
diff --git a/CountryTweetMap/scripts/heatmap.js b/CountryTweetMap/scripts/heatmap.js
new file mode 100644
index 00000000..3eee39ea
--- /dev/null
+++ b/CountryTweetMap/scripts/heatmap.js
@@ -0,0 +1,724 @@
+/*
+ * heatmap.js v2.0.5 | JavaScript Heatmap Library
+ *
+ * Copyright 2008-2016 Patrick Wied - All rights reserved.
+ * Dual licensed under MIT and Beerware license
+ *
+ * :: 2016-09-05 01:16
+ */
+;(function (name, context, factory) {
+
+ // Supports UMD. AMD, CommonJS/Node.js and browser context
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = factory();
+ } else if (typeof define === "function" && define.amd) {
+ define(factory);
+ } else {
+ context[name] = factory();
+ }
+
+})("h337", this, function () {
+
+// Heatmap Config stores default values and will be merged with instance config
+var HeatmapConfig = {
+ defaultRadius: 40,
+ defaultRenderer: 'canvas2d',
+ defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"},
+ defaultMaxOpacity: 1,
+ defaultMinOpacity: 0,
+ defaultBlur: .85,
+ defaultXField: 'x',
+ defaultYField: 'y',
+ defaultValueField: 'value',
+ plugins: {}
+};
+var Store = (function StoreClosure() {
+
+ var Store = function Store(config) {
+ this._coordinator = {};
+ this._data = [];
+ this._radi = [];
+ this._min = 10;
+ this._max = 1;
+ this._xField = config['xField'] || config.defaultXField;
+ this._yField = config['yField'] || config.defaultYField;
+ this._valueField = config['valueField'] || config.defaultValueField;
+
+ if (config["radius"]) {
+ this._cfgRadius = config["radius"];
+ }
+ };
+
+ var defaultRadius = HeatmapConfig.defaultRadius;
+
+ Store.prototype = {
+ // when forceRender = false -> called from setData, omits renderall event
+ _organiseData: function(dataPoint, forceRender) {
+ var x = dataPoint[this._xField];
+ var y = dataPoint[this._yField];
+ var radi = this._radi;
+ var store = this._data;
+ var max = this._max;
+ var min = this._min;
+ var value = dataPoint[this._valueField] || 1;
+ var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
+
+ if (!store[x]) {
+ store[x] = [];
+ radi[x] = [];
+ }
+
+ if (!store[x][y]) {
+ store[x][y] = value;
+ radi[x][y] = radius;
+ } else {
+ store[x][y] += value;
+ }
+ var storedVal = store[x][y];
+
+ if (storedVal > max) {
+ if (!forceRender) {
+ this._max = storedVal;
+ } else {
+ this.setDataMax(storedVal);
+ }
+ return false;
+ } else if (storedVal < min) {
+ if (!forceRender) {
+ this._min = storedVal;
+ } else {
+ this.setDataMin(storedVal);
+ }
+ return false;
+ } else {
+ return {
+ x: x,
+ y: y,
+ value: value,
+ radius: radius,
+ min: min,
+ max: max
+ };
+ }
+ },
+ _unOrganizeData: function() {
+ var unorganizedData = [];
+ var data = this._data;
+ var radi = this._radi;
+
+ for (var x in data) {
+ for (var y in data[x]) {
+
+ unorganizedData.push({
+ x: x,
+ y: y,
+ radius: radi[x][y],
+ value: data[x][y]
+ });
+
+ }
+ }
+ return {
+ min: this._min,
+ max: this._max,
+ data: unorganizedData
+ };
+ },
+ _onExtremaChange: function() {
+ this._coordinator.emit('extremachange', {
+ min: this._min,
+ max: this._max
+ });
+ },
+ addData: function() {
+ if (arguments[0].length > 0) {
+ var dataArr = arguments[0];
+ var dataLen = dataArr.length;
+ while (dataLen--) {
+ this.addData.call(this, dataArr[dataLen]);
+ }
+ } else {
+ // add to store
+ var organisedEntry = this._organiseData(arguments[0], true);
+ if (organisedEntry) {
+ // if it's the first datapoint initialize the extremas with it
+ if (this._data.length === 0) {
+ this._min = this._max = organisedEntry.value;
+ }
+ this._coordinator.emit('renderpartial', {
+ min: this._min,
+ max: this._max,
+ data: [organisedEntry]
+ });
+ }
+ }
+ return this;
+ },
+ setData: function(data) {
+ var dataPoints = data.data;
+ var pointsLen = dataPoints.length;
+
+
+ // reset data arrays
+ this._data = [];
+ this._radi = [];
+
+ for(var i = 0; i < pointsLen; i++) {
+ this._organiseData(dataPoints[i], false);
+ }
+ this._max = data.max;
+ this._min = data.min || 0;
+
+ this._onExtremaChange();
+ this._coordinator.emit('renderall', this._getInternalData());
+ return this;
+ },
+ removeData: function() {
+ // TODO: implement
+ },
+ setDataMax: function(max) {
+ this._max = max;
+ this._onExtremaChange();
+ this._coordinator.emit('renderall', this._getInternalData());
+ return this;
+ },
+ setDataMin: function(min) {
+ this._min = min;
+ this._onExtremaChange();
+ this._coordinator.emit('renderall', this._getInternalData());
+ return this;
+ },
+ setCoordinator: function(coordinator) {
+ this._coordinator = coordinator;
+ },
+ _getInternalData: function() {
+ return {
+ max: this._max,
+ min: this._min,
+ data: this._data,
+ radi: this._radi
+ };
+ },
+ getData: function() {
+ return this._unOrganizeData();
+ }/*,
+
+ TODO: rethink.
+
+ getValueAt: function(point) {
+ var value;
+ var radius = 100;
+ var x = point.x;
+ var y = point.y;
+ var data = this._data;
+
+ if (data[x] && data[x][y]) {
+ return data[x][y];
+ } else {
+ var values = [];
+ // radial search for datapoints based on default radius
+ for(var distance = 1; distance < radius; distance++) {
+ var neighbors = distance * 2 +1;
+ var startX = x - distance;
+ var startY = y - distance;
+
+ for(var i = 0; i < neighbors; i++) {
+ for (var o = 0; o < neighbors; o++) {
+ if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
+ if (data[startY+i] && data[startY+i][startX+o]) {
+ values.push(data[startY+i][startX+o]);
+ }
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+ if (values.length > 0) {
+ return Math.max.apply(Math, values);
+ }
+ }
+ return false;
+ }*/
+ };
+
+
+ return Store;
+})();
+
+var Canvas2dRenderer = (function Canvas2dRendererClosure() {
+
+ var _getColorPalette = function(config) {
+ var gradientConfig = config.gradient || config.defaultGradient;
+ var paletteCanvas = document.createElement('canvas');
+ var paletteCtx = paletteCanvas.getContext('2d');
+
+ paletteCanvas.width = 256;
+ paletteCanvas.height = 1;
+
+ var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
+ for (var key in gradientConfig) {
+ gradient.addColorStop(key, gradientConfig[key]);
+ }
+
+ paletteCtx.fillStyle = gradient;
+ paletteCtx.fillRect(0, 0, 256, 1);
+
+ return paletteCtx.getImageData(0, 0, 256, 1).data;
+ };
+
+ var _getPointTemplate = function(radius, blurFactor) {
+ var tplCanvas = document.createElement('canvas');
+ var tplCtx = tplCanvas.getContext('2d');
+ var x = radius;
+ var y = radius;
+ tplCanvas.width = tplCanvas.height = radius*2;
+
+ if (blurFactor == 1) {
+ tplCtx.beginPath();
+ tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ tplCtx.fillStyle = 'rgba(0,0,0,1)';
+ tplCtx.fill();
+ } else {
+ var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius);
+ gradient.addColorStop(0, 'rgba(0,0,0,1)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
+ tplCtx.fillStyle = gradient;
+ tplCtx.fillRect(0, 0, 2*radius, 2*radius);
+ }
+
+
+
+ return tplCanvas;
+ };
+
+ var _prepareData = function(data) {
+ var renderData = [];
+ var min = data.min;
+ var max = data.max;
+ var radi = data.radi;
+ var data = data.data;
+
+ var xValues = Object.keys(data);
+ var xValuesLen = xValues.length;
+
+ while(xValuesLen--) {
+ var xValue = xValues[xValuesLen];
+ var yValues = Object.keys(data[xValue]);
+ var yValuesLen = yValues.length;
+ while(yValuesLen--) {
+ var yValue = yValues[yValuesLen];
+ var value = data[xValue][yValue];
+ var radius = radi[xValue][yValue];
+ renderData.push({
+ x: xValue,
+ y: yValue,
+ value: value,
+ radius: radius
+ });
+ }
+ }
+
+ return {
+ min: min,
+ max: max,
+ data: renderData
+ };
+ };
+
+
+ function Canvas2dRenderer(config) {
+ var container = config.container;
+ var shadowCanvas = this.shadowCanvas = document.createElement('canvas');
+ var canvas = this.canvas = config.canvas || document.createElement('canvas');
+ var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0];
+
+ var computed = getComputedStyle(config.container) || {};
+
+ canvas.className = 'heatmap-canvas';
+
+ this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,''));
+ this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,''));
+
+ this.shadowCtx = shadowCanvas.getContext('2d');
+ this.ctx = canvas.getContext('2d');
+
+ // @TODO:
+ // conditional wrapper
+
+ canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;';
+
+ container.style.position = 'relative';
+ container.appendChild(canvas);
+
+ this._palette = _getColorPalette(config);
+ this._templates = {};
+
+ this._setStyles(config);
+ };
+
+ Canvas2dRenderer.prototype = {
+ renderPartial: function(data) {
+ if (data.data.length > 0) {
+ this._drawAlpha(data);
+ this._colorize();
+ }
+ },
+ renderAll: function(data) {
+ // reset render boundaries
+ this._clear();
+ if (data.data.length > 0) {
+ this._drawAlpha(_prepareData(data));
+ this._colorize();
+ }
+ },
+ _updateGradient: function(config) {
+ this._palette = _getColorPalette(config);
+ },
+ updateConfig: function(config) {
+ if (config['gradient']) {
+ this._updateGradient(config);
+ }
+ this._setStyles(config);
+ },
+ setDimensions: function(width, height) {
+ this._width = width;
+ this._height = height;
+ this.canvas.width = this.shadowCanvas.width = width;
+ this.canvas.height = this.shadowCanvas.height = height;
+ },
+ _clear: function() {
+ this.shadowCtx.clearRect(0, 0, this._width, this._height);
+ this.ctx.clearRect(0, 0, this._width, this._height);
+ },
+ _setStyles: function(config) {
+ this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur);
+
+ if (config.backgroundColor) {
+ this.canvas.style.backgroundColor = config.backgroundColor;
+ }
+
+ this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width;
+ this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height;
+
+
+ this._opacity = (config.opacity || 0) * 255;
+ this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
+ this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
+ this._useGradientOpacity = !!config.useGradientOpacity;
+ },
+ _drawAlpha: function(data) {
+ var min = this._min = data.min;
+ var max = this._max = data.max;
+ var data = data.data || [];
+ var dataLen = data.length;
+ // on a point basis?
+ var blur = 1 - this._blur;
+
+ while(dataLen--) {
+
+ var point = data[dataLen];
+
+ var x = point.x;
+ var y = point.y;
+ var radius = point.radius;
+ // if value is bigger than max
+ // use max as value
+ var value = Math.min(point.value, max);
+ var rectX = x - radius;
+ var rectY = y - radius;
+ var shadowCtx = this.shadowCtx;
+
+
+
+
+ var tpl;
+ if (!this._templates[radius]) {
+ this._templates[radius] = tpl = _getPointTemplate(radius, blur);
+ } else {
+ tpl = this._templates[radius];
+ }
+ // value from minimum / value range
+ // => [0, 1]
+ var templateAlpha = (value-min)/(max-min);
+ // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
+ shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha;
+
+ shadowCtx.drawImage(tpl, rectX, rectY);
+
+ // update renderBoundaries
+ if (rectX < this._renderBoundaries[0]) {
+ this._renderBoundaries[0] = rectX;
+ }
+ if (rectY < this._renderBoundaries[1]) {
+ this._renderBoundaries[1] = rectY;
+ }
+ if (rectX + 2*radius > this._renderBoundaries[2]) {
+ this._renderBoundaries[2] = rectX + 2*radius;
+ }
+ if (rectY + 2*radius > this._renderBoundaries[3]) {
+ this._renderBoundaries[3] = rectY + 2*radius;
+ }
+
+ }
+ },
+ _colorize: function() {
+ var x = this._renderBoundaries[0];
+ var y = this._renderBoundaries[1];
+ var width = this._renderBoundaries[2] - x;
+ var height = this._renderBoundaries[3] - y;
+ var maxWidth = this._width;
+ var maxHeight = this._height;
+ var opacity = this._opacity;
+ var maxOpacity = this._maxOpacity;
+ var minOpacity = this._minOpacity;
+ var useGradientOpacity = this._useGradientOpacity;
+
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ if (x + width > maxWidth) {
+ width = maxWidth - x;
+ }
+ if (y + height > maxHeight) {
+ height = maxHeight - y;
+ }
+
+ var img = this.shadowCtx.getImageData(x, y, width, height);
+ var imgData = img.data;
+ var len = imgData.length;
+ var palette = this._palette;
+
+
+ for (var i = 3; i < len; i+= 4) {
+ var alpha = imgData[i];
+ var offset = alpha * 4;
+
+
+ if (!offset) {
+ continue;
+ }
+
+ var finalAlpha;
+ if (opacity > 0) {
+ finalAlpha = opacity;
+ } else {
+ if (alpha < maxOpacity) {
+ if (alpha < minOpacity) {
+ finalAlpha = minOpacity;
+ } else {
+ finalAlpha = alpha;
+ }
+ } else {
+ finalAlpha = maxOpacity;
+ }
+ }
+
+ imgData[i-3] = palette[offset];
+ imgData[i-2] = palette[offset + 1];
+ imgData[i-1] = palette[offset + 2];
+ imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
+
+ }
+
+ img.data = imgData;
+ this.ctx.putImageData(img, x, y);
+
+ this._renderBoundaries = [1000, 1000, 0, 0];
+
+ },
+ getValueAt: function(point) {
+ var value;
+ var shadowCtx = this.shadowCtx;
+ var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
+ var data = img.data[3];
+ var max = this._max;
+ var min = this._min;
+
+ value = (Math.abs(max-min) * (data/255)) >> 0;
+
+ return value;
+ },
+ getDataURL: function() {
+ return this.canvas.toDataURL();
+ }
+ };
+
+
+ return Canvas2dRenderer;
+})();
+
+
+var Renderer = (function RendererClosure() {
+
+ var rendererFn = false;
+
+ if (HeatmapConfig['defaultRenderer'] === 'canvas2d') {
+ rendererFn = Canvas2dRenderer;
+ }
+
+ return rendererFn;
+})();
+
+
+var Util = {
+ merge: function() {
+ var merged = {};
+ var argsLen = arguments.length;
+ for (var i = 0; i < argsLen; i++) {
+ var obj = arguments[i]
+ for (var key in obj) {
+ merged[key] = obj[key];
+ }
+ }
+ return merged;
+ }
+};
+// Heatmap Constructor
+var Heatmap = (function HeatmapClosure() {
+
+ var Coordinator = (function CoordinatorClosure() {
+
+ function Coordinator() {
+ this.cStore = {};
+ };
+
+ Coordinator.prototype = {
+ on: function(evtName, callback, scope) {
+ var cStore = this.cStore;
+
+ if (!cStore[evtName]) {
+ cStore[evtName] = [];
+ }
+ cStore[evtName].push((function(data) {
+ return callback.call(scope, data);
+ }));
+ },
+ emit: function(evtName, data) {
+ var cStore = this.cStore;
+ if (cStore[evtName]) {
+ var len = cStore[evtName].length;
+ for (var i=0; is;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({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=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"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.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+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*f,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&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).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)};
\ No newline at end of file
diff --git a/CountryTweetMap/scripts/leaflet-heatmap.js b/CountryTweetMap/scripts/leaflet-heatmap.js
new file mode 100644
index 00000000..5f93f0b6
--- /dev/null
+++ b/CountryTweetMap/scripts/leaflet-heatmap.js
@@ -0,0 +1,246 @@
+/*
+* Leaflet Heatmap Overlay
+*
+* Copyright (c) 2008-2016, Patrick Wied (https://www.patrick-wied.at)
+* Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+* and the Beerware (http://en.wikipedia.org/wiki/Beerware) license.
+*/
+;(function (name, context, factory) {
+ // Supports UMD. AMD, CommonJS/Node.js and browser context
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = factory(
+ require('heatmap.js'),
+ require('leaflet')
+ );
+ } else if (typeof define === "function" && define.amd) {
+ define(['heatmap.js', 'leaflet'], factory);
+ } else {
+ // browser globals
+ if (typeof window.h337 === 'undefined') {
+ throw new Error('heatmap.js must be loaded before the leaflet heatmap plugin');
+ }
+ if (typeof window.L === 'undefined') {
+ throw new Error('Leaflet must be loaded before the leaflet heatmap plugin');
+ }
+ context[name] = factory(window.h337, window.L);
+ }
+
+})("HeatmapOverlay", this, function (h337, L) {
+ 'use strict';
+
+ // Leaflet < 0.8 compatibility
+ if (typeof L.Layer === 'undefined') {
+ L.Layer = L.Class;
+ }
+
+ var HeatmapOverlay = L.Layer.extend({
+
+ initialize: function (config) {
+ this.cfg = config;
+ this._el = L.DomUtil.create('div', 'leaflet-zoom-hide');
+ this._data = [];
+ this._max = 1;
+ this._min = 0;
+ this.cfg.container = this._el;
+ },
+
+ onAdd: function (map) {
+ var size = map.getSize();
+
+ this._map = map;
+
+ this._width = size.x;
+ this._height = size.y;
+
+ this._el.style.width = size.x + 'px';
+ this._el.style.height = size.y + 'px';
+ this._el.style.position = 'absolute';
+
+ this._origin = this._map.layerPointToLatLng(new L.Point(0, 0));
+
+ map.getPanes().overlayPane.appendChild(this._el);
+
+ if (!this._heatmap) {
+ this._heatmap = h337.create(this.cfg);
+ }
+
+ // this resets the origin and redraws whenever
+ // the zoom changed or the map has been moved
+ map.on('moveend', this._reset, this);
+ this._draw();
+ },
+
+ addTo: function (map) {
+ map.addLayer(this);
+ return this;
+ },
+
+ onRemove: function (map) {
+ // remove layer's DOM elements and listeners
+ map.getPanes().overlayPane.removeChild(this._el);
+
+ map.off('moveend', this._reset, this);
+ },
+ _draw: function() {
+ if (!this._map) { return; }
+
+ var mapPane = this._map.getPanes().mapPane;
+ var point = mapPane._leaflet_pos;
+
+ // reposition the layer
+ this._el.style[HeatmapOverlay.CSS_TRANSFORM] = 'translate(' +
+ -Math.round(point.x) + 'px,' +
+ -Math.round(point.y) + 'px)';
+
+ this._update();
+ },
+ _update: function() {
+ var bounds, zoom, scale;
+ var generatedData = { max: this._max, min: this._min, data: [] };
+
+ bounds = this._map.getBounds();
+ zoom = this._map.getZoom();
+ scale = Math.pow(2, zoom);
+
+ if (this._data.length == 0) {
+ if (this._heatmap) {
+ this._heatmap.setData(generatedData);
+ }
+ return;
+ }
+
+
+ var latLngPoints = [];
+ var radiusMultiplier = this.cfg.scaleRadius ? scale : 1;
+ var localMax = 0;
+ var localMin = 0;
+ var valueField = this.cfg.valueField;
+ var len = this._data.length;
+
+ while (len--) {
+ var entry = this._data[len];
+ var value = entry[valueField];
+ var latlng = entry.latlng;
+
+
+ // we don't wanna render points that are not even on the map ;-)
+ if (!bounds.contains(latlng)) {
+ continue;
+ }
+ // local max is the maximum within current bounds
+ localMax = Math.max(value, localMax);
+ localMin = Math.min(value, localMin);
+
+ var point = this._map.latLngToContainerPoint(latlng);
+ var latlngPoint = { x: Math.round(point.x), y: Math.round(point.y) };
+ latlngPoint[valueField] = value;
+
+ var radius;
+
+ if (entry.radius) {
+ radius = entry.radius * radiusMultiplier;
+ } else {
+ radius = (this.cfg.radius || 2) * radiusMultiplier;
+ }
+ latlngPoint.radius = radius;
+ latLngPoints.push(latlngPoint);
+ }
+ if (this.cfg.useLocalExtrema) {
+ generatedData.max = localMax;
+ generatedData.min = localMin;
+ }
+
+ generatedData.data = latLngPoints;
+
+ this._heatmap.setData(generatedData);
+ },
+ setData: function(data) {
+ this._max = data.max || this._max;
+ this._min = data.min || this._min;
+ var latField = this.cfg.latField || 'lat';
+ var lngField = this.cfg.lngField || 'lng';
+ var valueField = this.cfg.valueField || 'value';
+
+ // transform data to latlngs
+ var data = data.data;
+ var len = data.length;
+ var d = [];
+
+ while (len--) {
+ var entry = data[len];
+ var latlng = new L.LatLng(entry[latField], entry[lngField]);
+ var dataObj = { latlng: latlng };
+ dataObj[valueField] = entry[valueField];
+ if (entry.radius) {
+ dataObj.radius = entry.radius;
+ }
+ d.push(dataObj);
+ }
+ this._data = d;
+
+ this._draw();
+ },
+ // experimential... not ready.
+ addData: function(pointOrArray) {
+ if (pointOrArray.length > 0) {
+ var len = pointOrArray.length;
+ while(len--) {
+ this.addData(pointOrArray[len]);
+ }
+ } else {
+ var latField = this.cfg.latField || 'lat';
+ var lngField = this.cfg.lngField || 'lng';
+ var valueField = this.cfg.valueField || 'value';
+ var entry = pointOrArray;
+ var latlng = new L.LatLng(entry[latField], entry[lngField]);
+ var dataObj = { latlng: latlng };
+
+ dataObj[valueField] = entry[valueField];
+ this._max = Math.max(this._max, dataObj[valueField]);
+ this._min = Math.min(this._min, dataObj[valueField]);
+
+ if (entry.radius) {
+ dataObj.radius = entry.radius;
+ }
+ this._data.push(dataObj);
+ this._draw();
+ }
+ },
+ _reset: function () {
+ this._origin = this._map.layerPointToLatLng(new L.Point(0, 0));
+
+ var size = this._map.getSize();
+ if (this._width !== size.x || this._height !== size.y) {
+ this._width = size.x;
+ this._height = size.y;
+
+ this._el.style.width = this._width + 'px';
+ this._el.style.height = this._height + 'px';
+
+ this._heatmap._renderer.setDimensions(this._width, this._height);
+ }
+ this._draw();
+ }
+ });
+
+ HeatmapOverlay.CSS_TRANSFORM = (function() {
+ var div = document.createElement('div');
+ var props = [
+ 'transform',
+ 'WebkitTransform',
+ 'MozTransform',
+ 'OTransform',
+ 'msTransform'
+ ];
+
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ if (div.style[prop] !== undefined) {
+ return prop;
+ }
+ }
+ return props[0];
+ })();
+
+ return HeatmapOverlay;
+});
\ No newline at end of file