From 49b383fd3c64decabfb9c98aea9a9ee66c6c4d0b Mon Sep 17 00:00:00 2001 From: Zachary Tong Date: Fri, 5 Apr 2013 15:52:31 -0400 Subject: [PATCH] Allow binning on secondary field --- panels/map2/editor.html | 33 +++++++++--- panels/map2/module.js | 112 ++++++++++++++++++++++++++++++++-------- 2 files changed, 117 insertions(+), 28 deletions(-) diff --git a/panels/map2/editor.html b/panels/map2/editor.html index aff91cd78ef..1473c58c21b 100644 --- a/panels/map2/editor.html +++ b/panels/map2/editor.html @@ -24,7 +24,9 @@
- +
@@ -32,6 +34,7 @@
@@ -149,12 +152,30 @@ - Encoding -
- - - + + + +
+ + +
+ + + + + + + +
+ +
diff --git a/panels/map2/module.js b/panels/map2/module.js index be7b0f7ff3e..8051abc1703 100644 --- a/panels/map2/module.js +++ b/panels/map2/module.js @@ -25,7 +25,10 @@ angular.module('kibana.map2', []) enabled: true, hexagonSize: 10, hexagonAlpha: 1.0, - encoding: "color" + areaEncoding: true, + areaEncodingField: "primary", + colorEncoding: true, + colorEncodingField: "primary" } }, displayTabs: ["Geopoints", "Binning", "Data"], @@ -63,16 +66,39 @@ angular.module('kibana.map2', []) var request = $scope.ejs.Request().indices($scope.panel.index); - 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)))); + console.log("fields", $scope.panel.field, $scope.panel.secondaryfield); + + //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)))); + } 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)))); + } + + + + // Then the insert into facet and make the request var request = request.facet(facet).size(0); @@ -81,19 +107,31 @@ angular.module('kibana.map2', []) 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) { + var metric = 'count'; + + //If it is a Term facet, use count, otherwise use Total + //May retool this to allow users to pick mean/median/etc + if (typeof $scope.panel.secondaryfield === "undefined") { + metric = 'count'; + } else { + metric = 'total'; + } + //FIX THIS if (!$scope.isNumber(v.term)) { - $scope.data[v.term.toUpperCase()] = v.count; + $scope.data[v.term.toUpperCase()] = v[metric]; } else { - $scope.data[v.term] = v.count; + $scope.data[v.term] = v[metric]; } }); @@ -133,7 +171,6 @@ angular.module('kibana.map2', []) }) .filter('enabledText', function() { return function (value) { - console.log(value); if (value === true) { return "Enabled"; } else { @@ -161,9 +198,9 @@ angular.module('kibana.map2', []) }); function render_panel() { - console.log("render_panel"); - console.log(scope.panel); - console.log(elem); + //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") @@ -225,13 +262,40 @@ angular.module('kibana.map2', []) .attr("d", path); + + + //Geocoded points are decoded into lat/lon, then projected onto x/y - var points = _.map(scope.data, function (k, v) { + points = _.map(scope.data, function (k, v) { var decoded = geohash.decode(v); return projection([decoded.longitude, decoded.latitude]); }); + var binPoints = []; + + //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 = 10/max; + + _.map(scope.data, function (k, v) { + var decoded = geohash.decode(v); + return _.map(_.range(0, k*scale), function(a,b) { + binPoints.push(projection([decoded.longitude, decoded.latitude])); + }) + }); + + } else { + binPoints = points; + } + + + + //hexagonal binning if (scope.panel.display.binning.enabled) { @@ -240,7 +304,11 @@ angular.module('kibana.map2', []) .radius(scope.panel.display.binning.hexagonSize); //bin and sort the points, so we can set the various ranges appropriately - var binnedPoints = hexbin(points).sort(function(a, b) { return b.length - a.length; }); + var binnedPoints = hexbin(binPoints).sort(function(a, b) { return b.length - a.length; });; +console.log(binnedPoints); + //clean up some memory + binPoints = []; + var radius = d3.scale.sqrt() .domain([0, binnedPoints[0].length]) @@ -256,7 +324,7 @@ angular.module('kibana.map2', []) .data(binnedPoints) .enter().append("path") .attr("d", function (d) { - if (scope.panel.display.binning.encoding === 'color') { + if (scope.panel.display.binning.areaEncoding === false) { return hexbin.hexagon(); } else { return hexbin.hexagon(radius(d.length)); @@ -267,8 +335,8 @@ angular.module('kibana.map2', []) return "translate(" + d.x + "," + d.y + ")"; }) .style("fill", function (d) { - if (scope.panel.display.binning.encoding === 'area') { - return color(10); + if (scope.panel.display.binning.colorEncoding === false) { + return color(binnedPoints[0].length / 2); } else { return color(d.length); }