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