mirror of https://github.com/grafana/grafana
parent
569df6c88f
commit
9cf6a524df
@ -1,92 +0,0 @@ |
||||
/** |
||||
* Hexagonal binning |
||||
* Rendered as normally projected svg paths, which mean they *do not* |
||||
* clip on spheres appropriately. To fix this, we would need to translate |
||||
* the svg path into a geo-path |
||||
*/ |
||||
function displayBinning(scope, dr, dimensions) { |
||||
|
||||
var hexbin = d3.hexbin() |
||||
.size(dimensions) |
||||
.radius(scope.panel.display.binning.hexagonSize); |
||||
|
||||
|
||||
var binPoints = [], |
||||
binnedPoints = [], |
||||
binRange = 0; |
||||
|
||||
|
||||
if (scope.panel.display.binning.enabled) { |
||||
/** |
||||
* primary field is just binning raw counts |
||||
* |
||||
* Secondary field is binning some metric like mean/median/total. Hexbins doesn't support that, |
||||
* so we cheat a little and just add more points to compensate. |
||||
* However, we don't want to add a million points, so normalize against the largest value |
||||
*/ |
||||
if (scope.panel.display.binning.areaEncodingField === 'secondary') { |
||||
var max = Math.max.apply(Math, _.map(scope.data, function(k,v){return k;})), |
||||
scale = 50/max; |
||||
|
||||
_.map(scope.data, function (k, v) { |
||||
var decoded = geohash.decode(v); |
||||
return _.map(_.range(0, k*scale), function(a,b) { |
||||
binPoints.push(dr.projection([decoded.longitude, decoded.latitude])); |
||||
}) |
||||
}); |
||||
|
||||
} else { |
||||
binPoints = dr.projectedPoints; |
||||
} |
||||
|
||||
//bin and sort the points, so we can set the various ranges appropriately
|
||||
binnedPoints = hexbin(binPoints).sort(function(a, b) { return b.length - a.length; }); |
||||
binRange = binnedPoints[0].length; |
||||
|
||||
//clean up some memory
|
||||
binPoints = []; |
||||
} else { |
||||
|
||||
//not enabled, so just set an empty array. D3.exit will take care of the rest
|
||||
binnedPoints = []; |
||||
binRange = 0; |
||||
} |
||||
|
||||
|
||||
|
||||
var radius = d3.scale.sqrt() |
||||
.domain([0, binRange]) |
||||
.range([0, scope.panel.display.binning.hexagonSize]); |
||||
|
||||
var color = d3.scale.linear() |
||||
.domain([0,binRange]) |
||||
.range(["white", "steelblue"]) |
||||
.interpolate(d3.interpolateLab); |
||||
|
||||
|
||||
var hex = dr.g.selectAll(".hexagon") |
||||
.data(binnedPoints); |
||||
|
||||
hex.enter().append("path") |
||||
.attr("d", function (d) { |
||||
if (scope.panel.display.binning.areaEncoding === false) { |
||||
return hexbin.hexagon(); |
||||
} else { |
||||
return hexbin.hexagon(radius(d.length)); |
||||
} |
||||
}) |
||||
.attr("class", "hexagon") |
||||
.attr("transform", function (d) { |
||||
return "translate(" + d.x + "," + d.y + ")"; |
||||
}) |
||||
.style("fill", function (d) { |
||||
if (scope.panel.display.binning.colorEncoding === false) { |
||||
return color(binnedPoints[0].length / 2); |
||||
} else { |
||||
return color(d.length); |
||||
} |
||||
}) |
||||
.attr("opacity", scope.panel.display.binning.hexagonAlpha); |
||||
|
||||
hex.exit().remove(); |
||||
} |
@ -1,28 +0,0 @@ |
||||
/** |
||||
* Renders bullseyes as geo-json poly gon entities |
||||
* Allows for them to clip on spheres correctly |
||||
*/ |
||||
function displayBullseye(scope, dr) { |
||||
|
||||
var degrees = 180 / Math.PI |
||||
var circle = d3.geo.circle(); |
||||
var data = []; |
||||
|
||||
if (scope.panel.display.bullseye.enabled) { |
||||
data = [ |
||||
circle.origin(parseFloat(scope.panel.display.bullseye.coord.lat), parseFloat(scope.panel.display.bullseye.coord.lon)).angle(1000 / 6371 * degrees)() |
||||
]; |
||||
} |
||||
|
||||
var arcs = dr.g.selectAll(".arc") |
||||
.data(data); |
||||
|
||||
arcs.enter().append("path") |
||||
|
||||
.attr("d", dr.path) |
||||
.attr("class", "arc"); |
||||
|
||||
arcs.exit().remove(); |
||||
|
||||
|
||||
} |
@ -1,44 +0,0 @@ |
||||
/** |
||||
* Renders geopoints as geo-json poly gon entities |
||||
* Allows for them to clip on spheres correctly |
||||
*/ |
||||
function displayGeopoints(scope, dr) { |
||||
|
||||
var points = []; |
||||
var circle = d3.geo.circle(); |
||||
var degrees = 180 / Math.PI |
||||
|
||||
if (scope.panel.display.geopoints.enabled) { |
||||
//points = dr.points;
|
||||
|
||||
points = _.map(dr.points, function(v) { |
||||
return { |
||||
type: "Point", |
||||
coordinates: [v[0], v[1]] |
||||
}; |
||||
}); |
||||
|
||||
} |
||||
|
||||
|
||||
dr.geopoints = dr.g.selectAll("path.geopoint") |
||||
.data(points); |
||||
|
||||
|
||||
|
||||
dr.geopoints.enter().append("path") |
||||
/* |
||||
.datum(function(d) { |
||||
return circle.origin([d[0], d[1]]).angle(scope.panel.display.geopoints.pointSize / 6371 * degrees)(); |
||||
}) |
||||
*/ |
||||
.attr("class", "geopoint") |
||||
.attr("d", dr.path); |
||||
|
||||
dr.geopoints.exit().remove(); |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} |
@ -1,287 +0,0 @@ |
||||
<style> |
||||
.tabDetails { |
||||
border-bottom: 1px solid #ddd; |
||||
padding-bottom:20px; |
||||
} |
||||
.tabDetails td { |
||||
padding-right: 10px; |
||||
padding-bottom:10px; |
||||
} |
||||
</style> |
||||
<div ng-controller="map2"> |
||||
<div class="row-fluid" style="margin-bottom:20px"> |
||||
<div class="span11"> |
||||
The map panel is compatible with either Geopoints or two-letter country codes, depending on the graphing options. Left click to drag/pan map, scroll wheel (or double click) to zoom. Globes can be spun using ctrl-key + drag. |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
<div class="row-fluid"> |
||||
<div class="span10"> |
||||
<form class="form-horizontal"> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="panelfield">Primary Field</label> |
||||
<div class="controls"> |
||||
<input type="text" id="panelfield" class="input" |
||||
ng-model="panel.field" |
||||
ng-change="get_data()" /> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="panelsecondaryfield">Secondary Field</label> |
||||
<div class="controls"> |
||||
<input type="text" id="panelsecondaryfield" class="input" |
||||
ng-model="panel.secondaryfield" |
||||
ng-change="get_data()" |
||||
data-placement="right" |
||||
placeholder="Optional" |
||||
bs-tooltip="'Allows aggregating on Primary field, while counting stats on a secondary (e.g. Group By user_id, Sum(purchase_price)).'" /> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="panelquery">Query</label> |
||||
<div class="controls"> |
||||
<input type="text" id="panelquery" class="input" ng-model="panel.query"> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
<div class="row-fluid"> |
||||
<div class="span11"> |
||||
<h4>Display Options</h4> |
||||
</div> |
||||
<!-- |
||||
Rolling our own tab control here because the Angular-Strap Tab directive doesn't allow |
||||
updates to components inside, which is quite bizarre. Or I just can't figure it out... |
||||
--> |
||||
|
||||
<div class="span11"> |
||||
<ul class="nav nav-tabs" ng-cloak=""> |
||||
<li ng-repeat="tab in ['Geopoints', 'Binning', 'Choropleth', 'Bullseye', 'Data']" ng-class="{active:isActive(tab)}"> |
||||
<a ng-click="tabClick(tab)">{{tab}}</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid tabDetails" ng-show="isActive('geopoints')"> |
||||
<div class="span8 offset1"> |
||||
<table> |
||||
<tbody > |
||||
<tr> |
||||
<td>Geopoints</td> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
data-placement="right" |
||||
bs-tooltip="'Compatible with Geopoint Type'" |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.geopoints.enabled}" |
||||
ng-model="panel.display.geopoints.enabled">{{panel.display.geopoints.enabled|enabledText}}</button> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Point size</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
bs-tooltip="'Controls the size of the geopoints on the map.'" |
||||
ng-model="panel.display.geopoints.pointSize" |
||||
value="{{panel.display.geopoints.pointSize}}" /> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Point Transparency</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
bs-tooltip="'Controls the transparency of geopoints. Valid numbers are between 0.0 and 1.0'" |
||||
ng-model="panel.display.geopoints.pointAlpha" |
||||
value="{{panel.display.geopoints.pointAlpha}}" /> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Autosizing</td> |
||||
<td> |
||||
<input type="checkbox" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
ng-model="panel.display.geopoints.autosize" |
||||
bs-tooltip="'Allows point sizes to scale as you zoom in and out of the map.'" /> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid tabDetails" ng-show="isActive('binning')"> |
||||
<div class="span8 offset1"> |
||||
<table> |
||||
<tbody > |
||||
<tr> |
||||
<td>Binning</td> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
data-placement="right" |
||||
bs-tooltip="'Compatible with Geopoint Type'" |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.binning.enabled}" |
||||
ng-model="panel.display.binning.enabled">{{panel.display.binning.enabled|enabledText}}</button> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Hexagon size</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
bs-tooltip="'Controls the size of the hexagonal binning'" |
||||
ng-model="panel.display.binning.hexagonSize" |
||||
value="{{panel.display.binning.hexagonSize}}" /> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Hexagon Transparency</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
bs-tooltip="'Controls the transparency of hexagonal bins. Valid numbers are between 0.0 and 1.0'" |
||||
ng-model="panel.display.binning.hexagonAlpha" |
||||
value="{{panel.display.binning.hexagonAlpha}}" /> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.binning.areaEncoding}" |
||||
ng-model="panel.display.binning.areaEncoding">Area</button> |
||||
</td> |
||||
<td> |
||||
<div class="btn-group" ng-model="panel.display.binning.areaEncodingField" bs-buttons-radio ng-change="$emit('render')"> |
||||
<button type="button" class="btn" value="primary">Primary Field</button> |
||||
<button type="button" class="btn" value="secondary">Secondary Field</button> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.binning.colorEncoding}" |
||||
ng-model="panel.display.binning.colorEncoding">Color</button> |
||||
</td> |
||||
<td> |
||||
<div class="btn-group" ng-model="panel.display.binning.colorEncodingField" bs-buttons-radio ng-change="$emit('render')"> |
||||
<button type="button" class="btn" value="primary">Primary Field</button> |
||||
<button type="button" class="btn" value="secondary">Secondary Field</button> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid tabDetails" ng-show="isActive('choropleth')"> |
||||
<div class="span8 offset1"> |
||||
<table> |
||||
<tbody > |
||||
<tr> |
||||
<td>Choropleth</td> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
data-placement="right" |
||||
bs-tooltip="'Choropleths color country regions according to your selected field. Compatible with Country-Coded fields'" |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.choropleth.enabled}" |
||||
ng-model="panel.display.choropleth.enabled">{{panel.display.choropleth.enabled|enabledText}}</button> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid tabDetails" ng-show="isActive('bullseye')"> |
||||
<div class="span8 offset1"> |
||||
<table> |
||||
<tbody > |
||||
<tr> |
||||
<td>Bullseye</td> |
||||
<td> |
||||
<button type="button" class="btn" bs-button |
||||
ng-change="$emit('render')" |
||||
ng-class="{'btn-success': panel.display.bullseye.enabled}" |
||||
ng-model="panel.display.bullseye.enabled">{{panel.display.bullseye.enabled|enabledText}}</button> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Bullseye Coordinates</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="$emit('render')" |
||||
placeholder="Latitude" |
||||
data-placement="right" |
||||
bs-tooltip="'Latitude of Bullseye'" |
||||
ng-model="panel.display.bullseye.coord.lat" |
||||
value="{{panel.display.bullseye.coord.lat}}" /> |
||||
|
||||
<input type="text" style="width:100px" |
||||
placeholder="Longitude" |
||||
ng-change="$emit('render')" |
||||
data-placement="right" |
||||
bs-tooltip="'Longitude of Bullseye'" |
||||
ng-model="panel.display.bullseye.coord.lon" |
||||
value="{{panel.display.bullseye.coord.lon}}" /> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid tabDetails" ng-show="isActive('data')"> |
||||
<div class="span8 offset1"> |
||||
<table> |
||||
<tbody > |
||||
<tr> |
||||
<td>Data Points</td> |
||||
<td> |
||||
<input type="text" style="width:100px" |
||||
ng-change="get_data()" |
||||
data-placement="right" |
||||
bs-tooltip="'Controls the number of samples used in the map. Be careful with this value!'" |
||||
ng-model="panel.display.data.samples" |
||||
value="{{panel.display.data.samples}}" /> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Map Projection</td> |
||||
<td> |
||||
<select ng-model="panel.display.data.type" ng-options="option.id as option.text for option in panel.display.data.dropdown"></select> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<h5>Panel Spy</h5> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="span2"> |
||||
<label class="small">Spyable</label> <input type="checkbox" ng-model= |
||||
"panel.spyable" ng-checked="panel.spyable"> |
||||
</div> |
||||
|
||||
<div class="span9 small"> |
||||
The panel spy shows 'behind the scenes' information about a panel. It can be |
||||
accessed by clicking the in the top right of the panel. |
||||
</div> |
||||
</div> |
||||
</div> |
@ -1 +0,0 @@ |
||||
(function(){d3.hexbin=function(){function u(n){var r={};return n.forEach(function(n,t){var a=s.call(u,n,t)/o,e=Math.round(a),h=f.call(u,n,t)/i-(1&e?.5:0),c=Math.round(h),l=a-e;if(3*Math.abs(l)>1){var g=h-c,v=c+(c>h?-1:1)/2,M=e+(e>a?-1:1),m=h-v,d=a-M;g*g+l*l>m*m+d*d&&(c=v+(1&e?1:-1)/2,e=M)}var x=c+"-"+e,j=r[x];j?j.push(n):(j=r[x]=[n],j.x=(c+(1&e?.5:0))*i,j.y=e*o)}),d3.values(r)}function a(r){var t=0,u=0;return n.map(function(n){var a=Math.sin(n)*r,e=-Math.cos(n)*r,i=a-t,o=e-u;return t=a,u=e,[i,o]})}var e,i,o,h=1,c=1,f=r,s=t;return u.x=function(n){return arguments.length?(f=n,u):f},u.y=function(n){return arguments.length?(s=n,u):s},u.hexagon=function(n){return 1>arguments.length&&(n=e),"m"+a(n).join("l")+"z"},u.mesh=function(){for(var n=[],r=a(e).slice(0,4).join("l"),t=0,u=!1;c+e>t;t+=o,u=!u)for(var f=u?i/2:0;h>f;f+=i)n.push("M",f,",",t,"m",r);return n.join("")},u.size=function(n){return arguments.length?(h=+n[0],c=+n[1],u):[h,c]},u.radius=function(n){return arguments.length?(e=+n,i=2*e*Math.sin(Math.PI/3),o=1.5*e,u):e},u.radius(1)};var n=d3.range(0,2*Math.PI,Math.PI/3),r=function(n){return n[0]},t=function(n){return n[1]}})(); |
@ -1,137 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2011, Sun Ning. |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all |
||||
* copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
* SOFTWARE. |
||||
* |
||||
*/ |
||||
|
||||
var BASE32_CODES = "0123456789bcdefghjkmnpqrstuvwxyz"; |
||||
var BASE32_CODES_DICT = {}; |
||||
for(var i=0; i<BASE32_CODES.length; i++) { |
||||
BASE32_CODES_DICT[BASE32_CODES.charAt(i)]=i; |
||||
} |
||||
|
||||
var encode = function(latitude, longitude, numberOfChars){ |
||||
numberOfChars = numberOfChars || 9; |
||||
var chars = [], bits = 0; |
||||
var hash_value = 0; |
||||
|
||||
var maxlat = 90, minlat = -90; |
||||
var maxlon = 180, minlon = -180; |
||||
|
||||
var mid; |
||||
var islon = true; |
||||
while(chars.length < numberOfChars) { |
||||
if (islon){ |
||||
mid = (maxlon+minlon)/2; |
||||
if(longitude > mid){ |
||||
hash_value = (hash_value << 1) + 1; |
||||
minlon=mid; |
||||
} else { |
||||
hash_value = (hash_value << 1) + 0; |
||||
maxlon=mid; |
||||
} |
||||
} else { |
||||
mid = (maxlat+minlat)/2; |
||||
if(latitude > mid ){ |
||||
hash_value = (hash_value << 1) + 1; |
||||
minlat = mid; |
||||
} else { |
||||
hash_value = (hash_value << 1) + 0; |
||||
maxlat = mid; |
||||
} |
||||
} |
||||
islon = !islon; |
||||
|
||||
bits++; |
||||
if (bits == 5) { |
||||
var code = BASE32_CODES[hash_value]; |
||||
chars.push(code); |
||||
bits = 0; |
||||
hash_value = 0; |
||||
} |
||||
} |
||||
return chars.join('') |
||||
}; |
||||
|
||||
var decode_bbox = function(hash_string){ |
||||
var islon = true; |
||||
var maxlat = 90, minlat = -90; |
||||
var maxlon = 180, minlon = -180; |
||||
|
||||
var hash_value = 0; |
||||
for(var i=0,l=hash_string.length; i<l; i++) { |
||||
var code = hash_string[i].toLowerCase(); |
||||
hash_value = BASE32_CODES_DICT[code]; |
||||
|
||||
for (var bits=4; bits>=0; bits--) { |
||||
var bit = (hash_value >> bits) & 1; |
||||
if (islon){ |
||||
var mid = (maxlon+minlon)/2; |
||||
if(bit == 1){ |
||||
minlon = mid; |
||||
} else { |
||||
maxlon = mid; |
||||
} |
||||
} else { |
||||
var mid = (maxlat+minlat)/2; |
||||
if(bit == 1){ |
||||
minlat = mid; |
||||
} else { |
||||
maxlat = mid; |
||||
} |
||||
} |
||||
islon = !islon; |
||||
} |
||||
} |
||||
return [minlat, minlon, maxlat, maxlon]; |
||||
} |
||||
|
||||
var decode = function(hash_string){ |
||||
var bbox = decode_bbox(hash_string); |
||||
var lat = (bbox[0]+bbox[2])/2; |
||||
var lon = (bbox[1]+bbox[3])/2; |
||||
var laterr = bbox[2]-lat; |
||||
var lonerr = bbox[3]-lon; |
||||
return {latitude:lat, longitude:lon, |
||||
error:{latitude:laterr, longitude:lonerr}}; |
||||
}; |
||||
|
||||
/** |
||||
* direction [lat, lon], i.e. |
||||
* [1,0] - north |
||||
* [1,1] - northeast |
||||
* ... |
||||
*/ |
||||
var neighbor = function(hashstring, direction) { |
||||
var lonlat = decode(hashstring); |
||||
var neighbor_lat = lonlat.latitude |
||||
+ direction[0] * lonlat.error.latitude * 2; |
||||
var neighbor_lon = lonlat.longitude |
||||
+ direction[1] * lonlat.error.longitude * 2; |
||||
return encode(neighbor_lat, neighbor_lon, hashstring.length); |
||||
} |
||||
|
||||
var geohash = { |
||||
'encode': encode, |
||||
'decode': decode, |
||||
'decode_bbox': decode_bbox, |
||||
'neighbor': neighbor, |
||||
} |
||||
module.exports = geohash; |
@ -1 +0,0 @@ |
||||
(function(){function n(n){function t(){for(;f=a<c.length&&n>p;){var u=a++,t=c[u],r=l.call(t,1);r.push(e(u)),++p,t[0].apply(null,r)}}function e(n){return function(u,l){--p,null==d&&(null!=u?(d=u,a=s=0/0,r()):(c[n]=l,--s?f||t():r()))}}function r(){null!=d?v(d):i?v(d,c):v.apply(null,[d].concat(c))}var o,f,i,c=[],a=0,p=0,s=0,d=null,v=u;return n||(n=1/0),o={defer:function(){return d||(c.push(arguments),++s,t()),o},await:function(n){return v=n,i=!1,s||r(),o},awaitAll:function(n){return v=n,i=!0,s||r(),o}}}function u(){}"undefined"==typeof module?self.queue=n:module.exports=n,n.version="1.0.4";var l=[].slice})(); |
@ -1 +0,0 @@ |
||||
topojson=function(){function t(t,e){function n(e){var n=t.arcs[e],r=n[0],o=[0,0];return n.forEach(function(t){o[0]+=t[0],o[1]+=t[1]}),[r,o]}var r={},o={},a={};e.forEach(function(t){var e=n(t);(r[e[0]]||(r[e[0]]=[])).push(t),(r[e[1]]||(r[e[1]]=[])).push(~t)}),e.forEach(function(t){var e,r,i=n(t),u=i[0],c=i[1];if(e=a[u])if(delete a[e.end],e.push(t),e.end=c,r=o[c]){delete o[r.start];var s=r===e?e:e.concat(r);o[s.start=e.start]=a[s.end=r.end]=s}else if(r=a[c]){delete o[r.start],delete a[r.end];var s=e.concat(r.map(function(t){return~t}).reverse());o[s.start=e.start]=a[s.end=r.start]=s}else o[e.start]=a[e.end]=e;else if(e=o[c])if(delete o[e.start],e.unshift(t),e.start=u,r=a[u]){delete a[r.end];var f=r===e?e:r.concat(e);o[f.start=r.start]=a[f.end=e.end]=f}else if(r=o[u]){delete o[r.start],delete a[r.end];var f=r.map(function(t){return~t}).reverse().concat(e);o[f.start=r.end]=a[f.end=e.end]=f}else o[e.start]=a[e.end]=e;else if(e=o[u])if(delete o[e.start],e.unshift(~t),e.start=c,r=a[c]){delete a[r.end];var f=r===e?e:r.concat(e);o[f.start=r.start]=a[f.end=e.end]=f}else if(r=o[c]){delete o[r.start],delete a[r.end];var f=r.map(function(t){return~t}).reverse().concat(e);o[f.start=r.end]=a[f.end=e.end]=f}else o[e.start]=a[e.end]=e;else if(e=a[c])if(delete a[e.end],e.push(~t),e.end=u,r=a[u]){delete o[r.start];var s=r===e?e:e.concat(r);o[s.start=e.start]=a[s.end=r.end]=s}else if(r=o[u]){delete o[r.start],delete a[r.end];var s=e.concat(r.map(function(t){return~t}).reverse());o[s.start=e.start]=a[s.end=r.start]=s}else o[e.start]=a[e.end]=e;else e=[t],o[e.start=u]=a[e.end=c]=e});var i=[];for(var u in a)i.push(a[u]);return i}function e(e,n,r){function a(t){0>t&&(t=~t),(l[t]||(l[t]=[])).push(f)}function i(t){t.forEach(a)}function u(t){t.forEach(i)}function c(t){t.type==="GeometryCollection"?t.geometries.forEach(c):t.type in d&&(f=t,d[t.type](t.arcs))}var s=[];if(arguments.length>1){var f,l=[],d={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(t){t.forEach(u)}};c(n),l.forEach(arguments.length<3?function(t,e){s.push([e])}:function(t,e){r(t[0],t[t.length-1])&&s.push([e])})}else for(var p=0,h=e.arcs.length;h>p;++p)s.push([p]);return o(e,{type:"MultiLineString",arcs:t(e,s)})}function n(t,e){return e.type==="GeometryCollection"?{type:"FeatureCollection",features:e.geometries.map(function(e){return r(t,e)})}:r(t,e)}function r(t,e){var n={type:"Feature",id:e.id,properties:e.properties||{},geometry:o(t,e)};return e.id==null&&delete n.id,n}function o(t,e){function n(t,e){e.length&&e.pop();for(var n,r=h[0>t?~t:t],o=0,i=r.length,u=0,c=0;i>o;++o)e.push([(u+=(n=r[o])[0])*f+d,(c+=n[1])*l+p]);0>t&&a(e,i)}function r(t){return[t[0]*f+d,t[1]*l+p]}function o(t){for(var e=[],r=0,o=t.length;o>r;++r)n(t[r],e);return e.length<2&&e.push(e[0]),e}function i(t){for(var e=o(t);e.length<4;)e.push(e[0]);return e}function u(t){return t.map(i)}function c(t){var e=t.type;return"GeometryCollection"===e?{type:e,geometries:t.geometries.map(c)}:e in v?{type:e,coordinates:v[e](t)}:null}var s=t.transform,f=s.scale[0],l=s.scale[1],d=s.translate[0],p=s.translate[1],h=t.arcs,v={Point:function(t){return r(t.coordinates)},MultiPoint:function(t){return t.coordinates.map(r)},LineString:function(t){return o(t.arcs)},MultiLineString:function(t){return t.arcs.map(o)},Polygon:function(t){return u(t.arcs)},MultiPolygon:function(t){return t.arcs.map(u)}};return c(e)}function a(t,e){for(var n,r=t.length,o=r-e;o<--r;)n=t[o],t[o++]=t[r],t[r]=n}function i(t,e){for(var n=0,r=t.length;r>n;){var o=n+r>>>1;t[o]<e?n=o+1:r=o}return n}function u(t){function e(t,e){t.forEach(function(t){0>t&&(t=~t);var n=o[t]||(o[t]=[]);n[e]||(n.forEach(function(t){var n,r;r=i(n=a[e],t),n[r]!==t&&n.splice(r,0,t),r=i(n=a[t],e),n[r]!==e&&n.splice(r,0,e)}),n[e]=e)})}function n(t,n){t.forEach(function(t){e(t,n)})}function r(t,e){t.type==="GeometryCollection"?t.geometries.forEach(function(t){r(t,e)}):t.type in u&&u[t.type](t.arcs,e)}var o=[],a=t.map(function(){return[]}),u={LineString:e,MultiLineString:n,Polygon:n,MultiPolygon:function(t,e){t.forEach(function(t){n(t,e)})}};return t.forEach(r),a}return{version:"1.0.0pre",mesh:e,feature:n,neighbors:u}}(); |
File diff suppressed because one or more lines are too long
|
@ -1,58 +0,0 @@ |
||||
<kibana-panel ng-controller='map2' ng-init="init()"> |
||||
<style> |
||||
.overlay { |
||||
fill: none; |
||||
pointer-events: all; |
||||
} |
||||
|
||||
.land { |
||||
fill: #D1D1D1; |
||||
stroke: #595959; |
||||
stroke-linejoin: round; |
||||
stroke-linecap: round; |
||||
stroke-width: .1px; |
||||
} |
||||
|
||||
.boundary { |
||||
fill: none; |
||||
stroke: #000; |
||||
stroke-linejoin: round; |
||||
stroke-linecap: round; |
||||
} |
||||
|
||||
.hexagon { |
||||
fill: none; |
||||
stroke: #000; |
||||
stroke-width: .1px; |
||||
} |
||||
|
||||
.q1 { fill:rgb(247,251,255); } |
||||
.q2 { fill:rgb(222,235,247); } |
||||
.q3 { fill:rgb(198,219,239); } |
||||
.q4 { fill:rgb(158,202,225); } |
||||
.q5 { fill:rgb(107,174,214); } |
||||
.q6 { fill:rgb(66,146,198); } |
||||
.q7 { fill:rgb(33,113,181); } |
||||
.q8 { fill:rgb(8,81,156); } |
||||
.q9 { fill:rgb(8,48,107); } |
||||
|
||||
.arc { |
||||
stroke: #f00; |
||||
stroke-width: .5px; |
||||
fill: none; |
||||
} |
||||
|
||||
.geopoint { |
||||
stroke: #000; |
||||
stroke-width: .5px; |
||||
fill: #000; |
||||
} |
||||
|
||||
.dropdown-menu{position:absolute;top:auto;left:auto;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;} |
||||
</style> |
||||
<span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'> |
||||
<i bs-modal="'partials/modal.html'" class="icon-eye-open"></i> |
||||
</span> |
||||
|
||||
<div map2 params="{{panel}}" style="height:{{panel.height || row.height}}"></div> |
||||
</kibana-panel> |
@ -1,452 +0,0 @@ |
||||
angular.module('kibana.map2', []) |
||||
.controller('map2', function ($scope, eventBus, keylistener) { |
||||
|
||||
// Set and populate defaults
|
||||
var _d = { |
||||
status : "Broken", |
||||
query: "*", |
||||
map: "world", |
||||
colors: ['#C8EEFF', '#0071A4'], |
||||
size: 100, |
||||
exclude: [], |
||||
spyable: true, |
||||
group: "default", |
||||
index_limit: 0, |
||||
display: { |
||||
translate:[0, 0], |
||||
scale:-1, |
||||
data: { |
||||
samples: 1000, |
||||
type: "mercator", |
||||
dropdown:[ |
||||
{ |
||||
"text": "Mercator (Flat)", |
||||
id: "mercator" |
||||
}, |
||||
{ |
||||
text: "Orthographic (Sphere)", |
||||
id: "orthographic" |
||||
} |
||||
] |
||||
}, |
||||
geopoints: { |
||||
enabled: false, |
||||
enabledText: "Enabled", |
||||
pointSize: 0.3, |
||||
pointAlpha: 0.6 |
||||
}, |
||||
binning: { |
||||
enabled: false, |
||||
hexagonSize: 2, |
||||
hexagonAlpha: 1.0, |
||||
areaEncoding: true, |
||||
areaEncodingField: "primary", |
||||
colorEncoding: true, |
||||
colorEncodingField: "primary" |
||||
}, |
||||
choropleth: { |
||||
enabled: false |
||||
}, |
||||
bullseye: { |
||||
enabled: false, |
||||
coord: { |
||||
lat: 0, |
||||
lon: 0 |
||||
} |
||||
} |
||||
}, |
||||
activeDisplayTab:"Geopoints" |
||||
}; |
||||
|
||||
_.defaults($scope.panel, _d) |
||||
|
||||
$scope.init = function () { |
||||
eventBus.register($scope, 'time', function (event, time) { |
||||
set_time(time) |
||||
}); |
||||
eventBus.register($scope, 'query', function (event, query) { |
||||
$scope.panel.query = _.isArray(query) ? query[0] : query; |
||||
$scope.get_data(); |
||||
}); |
||||
// Now that we're all setup, request the time from our group
|
||||
eventBus.broadcast($scope.$id, $scope.panel.group, 'get_time'); |
||||
|
||||
$scope.keylistener = keylistener; |
||||
|
||||
}; |
||||
|
||||
$scope.get_data = function () { |
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if (_.isUndefined($scope.index) || _.isUndefined($scope.time)) |
||||
return |
||||
|
||||
$scope.panel.loading = true; |
||||
var request = $scope.ejs.Request().indices($scope.index); |
||||
|
||||
|
||||
var metric = 'count'; |
||||
|
||||
//Use a regular term facet if there is no secondary field
|
||||
if (typeof $scope.panel.secondaryfield === "undefined") { |
||||
var facet = $scope.ejs.TermsFacet('map') |
||||
.field($scope.panel.field) |
||||
.size($scope.panel.display.data.samples) |
||||
.exclude($scope.panel.exclude) |
||||
.facetFilter(ejs.QueryFilter( |
||||
ejs.FilteredQuery( |
||||
ejs.QueryStringQuery($scope.panel.query || '*'), |
||||
ejs.RangeFilter($scope.time.field) |
||||
.from($scope.time.from) |
||||
.to($scope.time.to)))); |
||||
|
||||
metric = 'count'; |
||||
} else { |
||||
//otherwise, use term stats
|
||||
//NOTE: this will break if valueField is a geo_point
|
||||
// need to put in checks for that
|
||||
var facet = $scope.ejs.TermStatsFacet('map') |
||||
.keyField($scope.panel.field) |
||||
.valueField($scope.panel.secondaryfield) |
||||
.size($scope.panel.display.data.samples) |
||||
.facetFilter(ejs.QueryFilter( |
||||
ejs.FilteredQuery( |
||||
ejs.QueryStringQuery($scope.panel.query || '*'), |
||||
ejs.RangeFilter($scope.time.field) |
||||
.from($scope.time.from) |
||||
.to($scope.time.to)))); |
||||
|
||||
metric = 'total'; |
||||
} |
||||
|
||||
|
||||
// Then the insert into facet and make the request
|
||||
var request = request.facet(facet).size(0); |
||||
|
||||
$scope.populate_modal(request); |
||||
|
||||
var results = request.doSearch(); |
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function (results) { |
||||
$scope.panel.loading = false; |
||||
$scope.hits = results.hits.total; |
||||
$scope.data = {}; |
||||
|
||||
_.each(results.facets.map.terms, function (v) { |
||||
|
||||
if (!_.isNumber(v.term)) { |
||||
$scope.data[v.term.toUpperCase()] = v[metric]; |
||||
} else { |
||||
$scope.data[v.term] = v[metric]; |
||||
} |
||||
}); |
||||
|
||||
$scope.$emit('render') |
||||
}); |
||||
}; |
||||
|
||||
// I really don't like this function, too much dom manip. Break out into directive?
|
||||
$scope.populate_modal = function (request) { |
||||
$scope.modal = { |
||||
title: "Inspector", |
||||
body: "<h5>Last Elasticsearch Query</h5><pre>" + 'curl -XGET ' + config.elasticsearch + '/' + $scope.index + "/_search?pretty -d'\n" + angular.toJson(JSON.parse(request.toString()), true) + "'</pre>" |
||||
} |
||||
}; |
||||
|
||||
function set_time(time) { |
||||
$scope.time = time; |
||||
$scope.index = _.isUndefined(time.index) ? $scope.index : time.index |
||||
$scope.get_data(); |
||||
} |
||||
|
||||
$scope.build_search = function (field, value) { |
||||
$scope.panel.query = add_to_query($scope.panel.query, field, value, false) |
||||
$scope.get_data(); |
||||
eventBus.broadcast($scope.$id, $scope.panel.group, 'query', $scope.panel.query); |
||||
}; |
||||
|
||||
$scope.isActive = function(tab) { |
||||
return (tab.toLowerCase() === $scope.panel.activeDisplayTab.toLowerCase()); |
||||
} |
||||
|
||||
$scope.tabClick = function(tab) { |
||||
$scope.panel.activeDisplayTab = tab; |
||||
} |
||||
|
||||
}) |
||||
.filter('enabledText', function() { |
||||
return function (value) { |
||||
if (value === true) { |
||||
return "Enabled"; |
||||
} else { |
||||
return "Disabled"; |
||||
} |
||||
} |
||||
}) |
||||
.directive('map2', function () { |
||||
return { |
||||
restrict: 'A', |
||||
link: function (scope, elem, attrs) { |
||||
|
||||
//directive level variables related to d3
|
||||
var dr = {}; |
||||
|
||||
scope.initializing = false; |
||||
|
||||
|
||||
dr.worldData = null; |
||||
dr.worldNames = null; |
||||
|
||||
/** |
||||
* Initialize the panels if new, or render existing panels |
||||
*/ |
||||
scope.init_or_render = function() { |
||||
if (typeof dr.svg === 'undefined') { |
||||
console.log("init"); |
||||
|
||||
//prevent duplicate initialization steps, if render is called again
|
||||
//before the svg is setup
|
||||
if (!scope.initializing) { |
||||
init_panel(); |
||||
} |
||||
} else { |
||||
console.log("render"); |
||||
render_panel(); |
||||
} |
||||
}; |
||||
|
||||
|
||||
/** |
||||
* Receive render events |
||||
*/ |
||||
scope.$on('render', function () { |
||||
scope.init_or_render(); |
||||
}); |
||||
|
||||
/** |
||||
* On window resize, re-render the panel |
||||
*/ |
||||
angular.element(window).bind('resize', function () { |
||||
scope.init_or_render(); |
||||
}); |
||||
|
||||
|
||||
/** |
||||
* Load the various panel-specific scripts, map data, then initialize |
||||
* the svg and set appropriate D3 settings |
||||
*/ |
||||
function init_panel() { |
||||
|
||||
scope.initializing = true; |
||||
// Using LABjs, wait until all scripts are loaded before rendering panel
|
||||
var scripts = $LAB.script("common/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/lib/topojson.v1.min.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/lib/node-geohash.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/lib/d3.hexbin.v0.min.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/lib/queue.v1.min.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/display/binning.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/display/geopoints.js?rand="+Math.floor(Math.random()*10000)) |
||||
.script("panels/map2/display/bullseye.js?rand="+Math.floor(Math.random()*10000)); |
||||
|
||||
// Populate element. Note that jvectormap appends, does not replace.
|
||||
scripts.wait(function () { |
||||
|
||||
queue() |
||||
.defer(d3.json, "panels/map2/lib/world-110m.json") |
||||
.defer(d3.tsv, "panels/map2/lib/world-country-names.tsv") |
||||
.await(function(error, world, names) { |
||||
dr.worldData = world; |
||||
dr.worldNames = names; |
||||
|
||||
//Better way to get these values? Seems kludgy to use jQuery on the div...
|
||||
var width = $(elem[0]).width(), |
||||
height = $(elem[0]).height(); |
||||
|
||||
//scale to whichever dimension is smaller, helps to ensure the whole map is displayed
|
||||
dr.scale = (width > height) ? (height/5) : (width/5); |
||||
|
||||
dr.zoom = d3.behavior.zoom() |
||||
.scaleExtent([1, 20]) |
||||
.on("zoom", translate_map); |
||||
|
||||
//used by choropleth
|
||||
//@todo change domain so that it reflects the domain of the data
|
||||
dr.quantize = d3.scale.quantize() |
||||
.domain([0, 1000]) |
||||
.range(d3.range(9).map(function(i) { return "q" + (i+1); })); |
||||
|
||||
//Extract name and two-letter codes for our countries
|
||||
dr.countries = topojson.feature(dr.worldData, dr.worldData.objects.countries).features; |
||||
|
||||
dr.countries = dr.countries.filter(function(d) { |
||||
return dr.worldNames.some(function(n) { |
||||
if (d.id == n.id) { |
||||
d.name = n.name; |
||||
return d.short = n.short; |
||||
} |
||||
}); |
||||
}).sort(function(a, b) { |
||||
return a.name.localeCompare(b.name); |
||||
}); |
||||
|
||||
//create the new svg
|
||||
dr.svg = d3.select(elem[0]).append("svg") |
||||
.attr("width", "100%") |
||||
.attr("height", "100%") |
||||
.attr("viewBox", "0 0 " + width + " " + height) |
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") |
||||
.call(dr.zoom); |
||||
dr.g = dr.svg.append("g"); |
||||
|
||||
scope.initializing = false; |
||||
render_panel(); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Render updates to the SVG. Typically happens when the data changes (time, query) |
||||
* or when new options are selected |
||||
*/ |
||||
function render_panel() { |
||||
|
||||
var width = $(elem[0]).width(), |
||||
height = $(elem[0]).height(); |
||||
|
||||
//Projection is dependant on the map-type
|
||||
if (scope.panel.display.data.type === 'mercator') { |
||||
dr.projection = d3.geo.mercator() |
||||
.translate([width/2, height/2]) |
||||
.scale(dr.scale); |
||||
|
||||
} else if (scope.panel.display.data.type === 'orthographic') { |
||||
dr.projection = d3.geo.orthographic() |
||||
.translate([width/2, height/2]) |
||||
.scale(100) |
||||
.clipAngle(90); |
||||
|
||||
//recenters the sphere more towards the US...not really necessary
|
||||
dr.projection.rotate([100 / 2, 20 / 2, dr.projection.rotate()[2]]); |
||||
|
||||
} |
||||
|
||||
dr.path = d3.geo.path() |
||||
.projection(dr.projection).pointRadius(0.2); |
||||
|
||||
console.log(scope.data); |
||||
|
||||
//Geocoded points are decoded into lonlat
|
||||
dr.points = _.map(scope.data, function (k, v) { |
||||
//console.log(k,v);
|
||||
var decoded = geohash.decode(v); |
||||
return [decoded.longitude, decoded.latitude]; |
||||
}); |
||||
|
||||
//And also projected projected to x/y. Both sets of points are used
|
||||
//by different functions
|
||||
dr.projectedPoints = _.map(dr.points, function (coords) { |
||||
return dr.projection(coords); |
||||
}); |
||||
|
||||
dr.svg.select(".overlay").remove(); |
||||
|
||||
dr.svg.append("rect") |
||||
.attr("class", "overlay") |
||||
.attr("width", width) |
||||
.attr("height", height) |
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); |
||||
|
||||
|
||||
//Draw the countries, if this is a choropleth, draw with fancy colors
|
||||
var countryPath = dr.g.selectAll(".land") |
||||
.data(dr.countries); |
||||
|
||||
countryPath.enter().append("path") |
||||
.attr("class", function(d) { |
||||
if (scope.panel.display.choropleth.enabled) { |
||||
return 'land ' + dr.quantize(scope.data[d.short]); |
||||
} else { |
||||
return 'land'; |
||||
} |
||||
}) |
||||
.attr("d", dr.path); |
||||
|
||||
countryPath.exit().remove(); |
||||
|
||||
//If this is a sphere, set up drag and keypress listeners
|
||||
if (scope.panel.display.data.type === 'orthographic') { |
||||
dr.svg.style("cursor", "move") |
||||
.call(d3.behavior.drag() |
||||
.origin(function() { var rotate = dr.projection.rotate(); return {x: 2 * rotate[0], y: -2 * rotate[1]}; }) |
||||
.on("drag", function() { |
||||
if (scope.keylistener.keyActive(17)) { |
||||
dr.projection.rotate([d3.event.x / 2, -d3.event.y / 2, dr.projection.rotate()[2]]); |
||||
|
||||
//dr.svg.selectAll("path").attr("d", dr.path);
|
||||
dr.g.selectAll("path").attr("d", dr.path); |
||||
|
||||
} |
||||
})); |
||||
|
||||
|
||||
} |
||||
|
||||
//Special fix for when the user changes from mercator -> orthographic
|
||||
//The globe won't redraw automatically, we need to force it
|
||||
if (scope.panel.display.data.type === 'orthographic') { |
||||
dr.svg.selectAll("path").attr("d", dr.path); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Display option rendering |
||||
* Order is important to render order here! |
||||
*/ |
||||
|
||||
//@todo fix this
|
||||
var dimensions = [width, height]; |
||||
displayBinning(scope, dr, dimensions); |
||||
displayGeopoints(scope, dr); |
||||
displayBullseye(scope, dr); |
||||
|
||||
|
||||
|
||||
|
||||
//If the panel scale is not default (e.g. the user has moved the maps around)
|
||||
//set the scale and position to the last saved config
|
||||
if (scope.panel.display.scale != -1) { |
||||
dr.zoom.scale(scope.panel.display.scale).translate(scope.panel.display.translate); |
||||
dr.g.style("stroke-width", 1 / scope.panel.display.scale).attr("transform", "translate(" + scope.panel.display.translate + ") scale(" + scope.panel.display.scale + ")"); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* On D3 zoom events, pan/zoom the map |
||||
* Only applies if the ctrl-key is not pressed, so it doesn't clobber |
||||
* sphere dragging |
||||
*/ |
||||
function translate_map() { |
||||
|
||||
var width = $(elem[0]).width(), |
||||
height = $(elem[0]).height(); |
||||
|
||||
if (! scope.keylistener.keyActive(17)) { |
||||
var t = d3.event.translate, |
||||
s = d3.event.scale; |
||||
t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0])); |
||||
t[1] = Math.min(height / 2 * (s - 1) + 230 * s, Math.max(height / 2 * (1 - s) - 230 * s, t[1])); |
||||
dr.zoom.translate(t); |
||||
|
||||
scope.panel.display.translate = t; |
||||
scope.panel.display.scale = s; |
||||
dr.g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ") scale(" + s + ")"); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
}); |
Loading…
Reference in new issue