Commit bdd11db9 by Zachary Tong

Initial code dump of the new map panel

parent b01df5c2
......@@ -14,7 +14,7 @@ var config = new Settings(
{
elasticsearch: 'http://localhost:9200',
kibana_index: "kibana-int",
modules: ['histogram','map','pie','table','stringquery','sort',
modules: ['histogram','map','map2','pie','table','stringquery','sort',
'timepicker','text','fields','hits','dashcontrol',
'column'],
}
......
<div class="row-fluid" ng-controller="map">
<div class="span11">
The map panel uses 2 letter country or US state codes to plot concentrations on a map. Darker terroritories mean more records matched that area. If multiple queries are sent from a single panel the <strong>first query will be displayed</strong>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<form>
<h6>Field</h6>
<input type="text" class="input-small" ng-model="panel.field">
</form>
</div>
<div class="span6">
<form class="input-append">
<h6>Query</h6>
<input type="text" ng-model="panel.query">
<button class="btn" ng-click="get_data();"><i class="icon-search"></i></button>
</form>
</div>
<div class="span1"><h6>Map</h6>
<select ng-change="$emit('render')" class="input-small" ng-model="panel.map" ng-options="f for f in ['world','europe','usa']"></select>
</div>
<div class="span6">
<div data-fade="1" bs-tabs>
<div data-title="'Home'"><p>Static tab content A</p></div>
<div data-title="'Profile'"><p>Static tab content B</p></div>
</div>
</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 <i class='icon-eye-open'></i> in the top right
of the panel.
</div>
</div>
(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]}})();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* 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;
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),c=i[0],s=i[1];if(e=a[c])if(delete a[e.end],e.push(t),e.end=s,r=o[s]){delete o[r.start];var u=r===e?e:e.concat(r);o[u.start=e.start]=a[u.end=r.end]=u}else if(r=a[s]){delete o[r.start],delete a[r.end];var u=e.concat(r.map(function(t){return~t}).reverse());o[u.start=e.start]=a[u.end=r.start]=u}else o[e.start]=a[e.end]=e;else if(e=o[s])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=o[c])if(delete o[e.start],e.unshift(~t),e.start=s,r=a[s]){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[s]){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[s])if(delete a[e.end],e.push(~t),e.end=c,r=a[c]){delete o[r.start];var u=r===e?e:e.concat(r);o[u.start=e.start]=a[u.end=r.end]=u}else if(r=o[c]){delete o[r.start],delete a[r.end];var u=e.concat(r.map(function(t){return~t}).reverse());o[u.start=e.start]=a[u.end=r.start]=u}else o[e.start]=a[e.end]=e;else e=[t],o[e.start=c]=a[e.end=s]=e});var i=[];for(var c in a)i.push(a[c]);return i}function e(e,r,o){function a(t){0>t&&(t=~t),(l[t]||(l[t]=[])).push(f)}function i(t){t.forEach(a)}function c(t){t.forEach(i)}function s(t){"GeometryCollection"===t.type?t.geometries.forEach(s):t.type in d&&(f=t,d[t.type](t.arcs))}var u=[];if(arguments.length>1){var f,l=[],d={LineString:i,MultiLineString:c,Polygon:c,MultiPolygon:function(t){t.forEach(c)}};s(r),l.forEach(3>arguments.length?function(t,e){u.push([e])}:function(t,e){o(t[0],t[t.length-1])&&u.push([e])})}else for(var p=0,h=e.arcs.length;h>p;++p)u.push([p]);return n(e,{type:"MultiLineString",arcs:t(e,u)})}function n(t,e){function n(t,e){e.length&&e.pop();for(var n,o=h[0>t?~t:t],a=0,i=o.length,c=0,s=0;i>a;++a)e.push([(c+=(n=o[a])[0])*f+d,(s+=n[1])*l+p]);0>t&&r(e,i)}function o(t){return[t[0]*f+d,t[1]*l+p]}function a(t){for(var e=[],r=0,o=t.length;o>r;++r)n(t[r],e);return 2>e.length&&e.push(e[0]),e}function i(t){for(var e=a(t);4>e.length;)e.push(e[0]);return e}function c(t){return t.map(i)}function s(t){var e=t.type,n="GeometryCollection"===e?{type:e,geometries:t.geometries.map(s)}:e in v?{type:e,coordinates:v[e](t)}:{type:null};return"id"in t&&(n.id=t.id),"properties"in t&&(n.properties=t.properties),n}var u=t.transform,f=u.scale[0],l=u.scale[1],d=u.translate[0],p=u.translate[1],h=t.arcs,v={Point:function(t){return o(t.coordinates)},MultiPoint:function(t){return t.coordinates.map(o)},LineString:function(t){return a(t.arcs)},MultiLineString:function(t){return t.arcs.map(a)},Polygon:function(t){return c(t.arcs)},MultiPolygon:function(t){return t.arcs.map(c)}};return s(e)}function r(t,e){for(var n,r=t.length,o=r-e;--r>o;)n=t[o],t[o++]=t[r],t[r]=n}function o(t,e){for(var n=0,r=t.length;r>n;){var o=n+r>>>1;e>t[o]?n=o+1:r=o}return n}function a(t){function e(t,e){t.forEach(function(t){0>t&&(t=~t);var n=a[t]||(a[t]=[]);n[e]||(n.forEach(function(t){var n,r;r=o(n=i[e],t),n[r]!==t&&n.splice(r,0,t),r=o(n=i[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){"GeometryCollection"===t.type?t.geometries.forEach(function(t){r(t,e)}):t.type in c&&c[t.type](t.arcs,e)}var a=[],i=t.map(function(){return[]}),c={LineString:e,MultiLineString:n,Polygon:n,MultiPolygon:function(t,e){t.forEach(function(t){n(t,e)})}};return t.forEach(r),i}return{version:"0.0.32",mesh:e,object:n,neighbors:a}}();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<kibana-panel ng-controller='map2' ng-init="init()">
<style>
.overlay {
fill: none;
pointer-events: all;
}
.land {
fill: #D1D1D1;
}
.boundary {
fill: none;
stroke: #fff;
stroke-linejoin: round;
stroke-linecap: round;
}
.hexagon {
fill: none;
stroke: #000;
stroke-width: .5px;
}
</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>
\ No newline at end of file
angular.module('kibana.map2', [])
.controller('map2', function($scope, eventBus) {
// Set and populate defaults
var _d = {
query : "*",
map : "world",
colors : ['#C8EEFF', '#0071A4'],
size : 1000,
exclude : [],
spyable : true,
group : "default",
index_limit : 0
}
_.defaults($scope.panel,_d)
console.log("$scope.panel", $scope.panel);
console.log("_d", _d);
$scope.init = function() {
console.log("init");
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.isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
$scope.get_data = function() {
console.log("get_data");
// Make sure we have everything for the request to complete
if(_.isUndefined($scope.panel.index) || _.isUndefined($scope.time))
return
$scope.panel.loading = true;
var request = $scope.ejs.Request().indices($scope.panel.index);
var facet = $scope.ejs.TermsFacet('map')
.field($scope.panel.field)
.size(1000)
.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)
)));
// 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 = {};
console.log("results",results);
_.each(results.facets.map.terms, function(v) {
//FIX THIS
if (!$scope.isNumber(v.term)) {
$scope.data[v.term.toUpperCase()] = v.count;
} else {
$scope.data[v.term] = v.count;
}
});
console.log("emit render");
$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.panel.index+"/_search?pretty -d'\n"+
angular.toJson(JSON.parse(request.toString()),true)+
"'</pre>",
}
}
function set_time(time) {
$scope.time = time;
$scope.panel.index = _.isUndefined(time.index) ? $scope.panel.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);
}
})
.directive('map2', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.html('<center><img src="common/img/load_big.gif"></center>')
// Receive render events
scope.$on('render',function(){
console.log("render");
render_panel();
});
// Or if the window is resized
angular.element(window).bind('resize', function(){
console.log("resize");
render_panel();
});
function render_panel() {
console.log("render_panel");
console.log(scope.panel);
console.log(elem);
// Using LABjs, wait until all scripts are loaded before rendering panel
var scripts = $LAB.script("panels/map2/lib/d3.v3.min.js")
.script("panels/map2/lib/topojson.v0.min.js")
.script("panels/map2/lib/node-geohash.js")
.script("panels/map2/lib/d3.hexbin.v0.min.js");
// Populate element. Note that jvectormap appends, does not replace.
scripts.wait(function(){
elem.text('');
//Better way to get these values? Seems kludgy to use jQuery on the div...
var width = $(elem[0]).width(),
height = $(elem[0]).height();
console.log("draw map", width, height);
//Scale the map by whichever dimension is the smallest, helps to make sure the whole map is shown
var scale = (width > height) ? (height / 2 / Math.PI) : (width / 2 / Math.PI);
var projection = d3.geo.mercator()
.translate([0, 0])
.scale(scale);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", move);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select(elem[0]).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.call(zoom);
var g = svg.append("g");
svg.append("rect")
.attr("class", "overlay")
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height);
d3.json("panels/map2/lib/world-50m.json", function(error, world) {
g.append("path")
.datum(topojson.object(world, world.objects.countries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
var points = _.map(scope.data, function (k,v) {
var decoded = geohash.decode(v);
return projection([decoded.longitude, decoded.latitude]);
})
var color = d3.scale.linear()
.domain([0, 20])
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([width, height])
.radius(10);
g.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("d", function(d) { return hexbin.hexagon(); })
.attr("class", "hexagon")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("fill", function(d) { console.log(d); return color(d.length); })
.attr("opacity", 1);
/*
raw, ugly points
*/
var points = _.map(scope.data, function (k,v) {
var decoded = geohash.decode(v);
return {lat: decoded.latitude, lon: decoded.longitude};
})
g.selectAll("circles.points")
.data(points)
.enter()
.append("circle")
.attr("r",1)
.attr("transform", function(d) {return "translate(" + projection([d.lon,d.lat]) + ")";});
});
function move() {
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]));
zoom.translate(t);
g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ")scale(" + s + ")");
}
})
}
}
};
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment