mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Allow binning on secondary field
This commit is contained in:
parent
e12f131271
commit
49b383fd3c
@ -24,7 +24,9 @@
|
|||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label" for="panelfield">Primary Field</label>
|
<label class="control-label" for="panelfield">Primary Field</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" id="panelfield" class="input" ng-model="panel.field">
|
<input type="text" id="panelfield" class="input"
|
||||||
|
ng-model="panel.field"
|
||||||
|
ng-change="get_data()" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -32,6 +34,7 @@
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" id="panelsecondaryfield" class="input"
|
<input type="text" id="panelsecondaryfield" class="input"
|
||||||
ng-model="panel.secondaryfield"
|
ng-model="panel.secondaryfield"
|
||||||
|
ng-change="get_data()"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
placeholder="Optional"
|
placeholder="Optional"
|
||||||
bs-tooltip="'Allows aggregating on Primary field, while counting stats on a secondary (e.g. Group By user_id, Sum(purchase_price)).'" />
|
bs-tooltip="'Allows aggregating on Primary field, while counting stats on a secondary (e.g. Group By user_id, Sum(purchase_price)).'" />
|
||||||
@ -149,12 +152,30 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Encoding</td>
|
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group" ng-model="panel.display.binning.encoding" bs-buttons-radio ng-change="$emit('render')">
|
<button type="button" class="btn" bs-button
|
||||||
<button type="button" class="btn" value="areacolor" >Area + Color</button>
|
ng-change="$emit('render')"
|
||||||
<button type="button" class="btn" value="area">Area</button>
|
ng-class="{'btn-success': panel.display.binning.areaEncoding}"
|
||||||
<button type="button" class="btn" value="color">Color</button>
|
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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -25,7 +25,10 @@ angular.module('kibana.map2', [])
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
hexagonSize: 10,
|
hexagonSize: 10,
|
||||||
hexagonAlpha: 1.0,
|
hexagonAlpha: 1.0,
|
||||||
encoding: "color"
|
areaEncoding: true,
|
||||||
|
areaEncodingField: "primary",
|
||||||
|
colorEncoding: true,
|
||||||
|
colorEncodingField: "primary"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
displayTabs: ["Geopoints", "Binning", "Data"],
|
displayTabs: ["Geopoints", "Binning", "Data"],
|
||||||
@ -63,16 +66,39 @@ angular.module('kibana.map2', [])
|
|||||||
var request = $scope.ejs.Request().indices($scope.panel.index);
|
var request = $scope.ejs.Request().indices($scope.panel.index);
|
||||||
|
|
||||||
|
|
||||||
var facet = $scope.ejs.TermsFacet('map')
|
console.log("fields", $scope.panel.field, $scope.panel.secondaryfield);
|
||||||
.field($scope.panel.field)
|
|
||||||
.size($scope.panel.display.data.samples)
|
//Use a regular term facet if there is no secondary field
|
||||||
.exclude($scope.panel.exclude)
|
if (typeof $scope.panel.secondaryfield === "undefined") {
|
||||||
.facetFilter(ejs.QueryFilter(
|
var facet = $scope.ejs.TermsFacet('map')
|
||||||
ejs.FilteredQuery(
|
.field($scope.panel.field)
|
||||||
ejs.QueryStringQuery($scope.panel.query || '*'),
|
.size($scope.panel.display.data.samples)
|
||||||
ejs.RangeFilter($scope.time.field)
|
.exclude($scope.panel.exclude)
|
||||||
.from($scope.time.from)
|
.facetFilter(ejs.QueryFilter(
|
||||||
.to($scope.time.to))));
|
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
|
// Then the insert into facet and make the request
|
||||||
var request = request.facet(facet).size(0);
|
var request = request.facet(facet).size(0);
|
||||||
@ -81,19 +107,31 @@ angular.module('kibana.map2', [])
|
|||||||
|
|
||||||
var results = request.doSearch();
|
var results = request.doSearch();
|
||||||
|
|
||||||
|
|
||||||
// Populate scope when we have results
|
// Populate scope when we have results
|
||||||
results.then(function (results) {
|
results.then(function (results) {
|
||||||
$scope.panel.loading = false;
|
$scope.panel.loading = false;
|
||||||
$scope.hits = results.hits.total;
|
$scope.hits = results.hits.total;
|
||||||
$scope.data = {};
|
$scope.data = {};
|
||||||
|
|
||||||
|
|
||||||
_.each(results.facets.map.terms, function (v) {
|
_.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
|
//FIX THIS
|
||||||
if (!$scope.isNumber(v.term)) {
|
if (!$scope.isNumber(v.term)) {
|
||||||
$scope.data[v.term.toUpperCase()] = v.count;
|
$scope.data[v.term.toUpperCase()] = v[metric];
|
||||||
} else {
|
} else {
|
||||||
$scope.data[v.term] = v.count;
|
$scope.data[v.term] = v[metric];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,7 +171,6 @@ angular.module('kibana.map2', [])
|
|||||||
})
|
})
|
||||||
.filter('enabledText', function() {
|
.filter('enabledText', function() {
|
||||||
return function (value) {
|
return function (value) {
|
||||||
console.log(value);
|
|
||||||
if (value === true) {
|
if (value === true) {
|
||||||
return "Enabled";
|
return "Enabled";
|
||||||
} else {
|
} else {
|
||||||
@ -161,9 +198,9 @@ angular.module('kibana.map2', [])
|
|||||||
});
|
});
|
||||||
|
|
||||||
function render_panel() {
|
function render_panel() {
|
||||||
console.log("render_panel");
|
//console.log("render_panel");
|
||||||
console.log(scope.panel);
|
//console.log(scope.panel);
|
||||||
console.log(elem);
|
//console.log(elem);
|
||||||
|
|
||||||
// Using LABjs, wait until all scripts are loaded before rendering panel
|
// Using LABjs, wait until all scripts are loaded before rendering panel
|
||||||
var scripts = $LAB.script("panels/map2/lib/d3.v3.min.js")
|
var scripts = $LAB.script("panels/map2/lib/d3.v3.min.js")
|
||||||
@ -225,13 +262,40 @@ angular.module('kibana.map2', [])
|
|||||||
.attr("d", path);
|
.attr("d", path);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Geocoded points are decoded into lat/lon, then projected onto x/y
|
//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);
|
var decoded = geohash.decode(v);
|
||||||
return projection([decoded.longitude, decoded.latitude]);
|
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
|
//hexagonal binning
|
||||||
if (scope.panel.display.binning.enabled) {
|
if (scope.panel.display.binning.enabled) {
|
||||||
|
|
||||||
@ -240,7 +304,11 @@ angular.module('kibana.map2', [])
|
|||||||
.radius(scope.panel.display.binning.hexagonSize);
|
.radius(scope.panel.display.binning.hexagonSize);
|
||||||
|
|
||||||
//bin and sort the points, so we can set the various ranges appropriately
|
//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()
|
var radius = d3.scale.sqrt()
|
||||||
.domain([0, binnedPoints[0].length])
|
.domain([0, binnedPoints[0].length])
|
||||||
@ -256,7 +324,7 @@ angular.module('kibana.map2', [])
|
|||||||
.data(binnedPoints)
|
.data(binnedPoints)
|
||||||
.enter().append("path")
|
.enter().append("path")
|
||||||
.attr("d", function (d) {
|
.attr("d", function (d) {
|
||||||
if (scope.panel.display.binning.encoding === 'color') {
|
if (scope.panel.display.binning.areaEncoding === false) {
|
||||||
return hexbin.hexagon();
|
return hexbin.hexagon();
|
||||||
} else {
|
} else {
|
||||||
return hexbin.hexagon(radius(d.length));
|
return hexbin.hexagon(radius(d.length));
|
||||||
@ -267,8 +335,8 @@ angular.module('kibana.map2', [])
|
|||||||
return "translate(" + d.x + "," + d.y + ")";
|
return "translate(" + d.x + "," + d.y + ")";
|
||||||
})
|
})
|
||||||
.style("fill", function (d) {
|
.style("fill", function (d) {
|
||||||
if (scope.panel.display.binning.encoding === 'area') {
|
if (scope.panel.display.binning.colorEncoding === false) {
|
||||||
return color(10);
|
return color(binnedPoints[0].length / 2);
|
||||||
} else {
|
} else {
|
||||||
return color(d.length);
|
return color(d.length);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user