Browse Source

Initial code dump of the new map panel

Zachary Tong 12 năm trước cách đây
mục cha
commit
bdd11db9b6

+ 1 - 1
config.js

@@ -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'], 
   }

+ 43 - 0
panels/map2/editor.html

@@ -0,0 +1,43 @@
+  <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>

+ 1 - 0
panels/map2/lib/d3.hexbin.v0.min.js

@@ -0,0 +1 @@
+(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]}})();

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
panels/map2/lib/d3.v3.min.js


+ 137 - 0
panels/map2/lib/node-geohash.js

@@ -0,0 +1,137 @@
+/**
+ * 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;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
panels/map2/lib/topojson.v0.min.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
panels/map2/lib/world-50m.json


+ 30 - 0
panels/map2/module.html

@@ -0,0 +1,30 @@
+<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>

+ 287 - 0
panels/map2/module.js

@@ -0,0 +1,287 @@
+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 + ")");
+            }
+
+
+
+
+
+
+        })
+      }
+    }
+  };
+});

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác