diff --git a/config.js b/config.js
index 30d70cd988c..8d694bd8802 100644
--- a/config.js
+++ b/config.js
@@ -22,6 +22,7 @@ var config = new Settings(
kibana_index: "kibana-int",
modules: ['histogram','map','pie','table','filtering',
'timepicker','text','fields','hits','dashcontrol',
- 'column','derivequeries','trends','bettermap','query'],
+ 'column','derivequeries','trends','bettermap','query',
+ 'terms'],
}
);
diff --git a/panels/pie/module.js b/panels/pie/module.js
index 467fe9a4faa..e2d660c3282 100644
--- a/panels/pie/module.js
+++ b/panels/pie/module.js
@@ -25,10 +25,10 @@ angular.module('kibana.pie', [])
.controller('pie', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
$scope.panelMeta = {
- status : "Deprecating Soon",
+ status : "Deprecated",
description : "Uses an Elasticsearch terms facet to create a pie chart. You should really only"+
- " point this at not_analyzed fields for that reason. This panel is going away soon, to be"+
- " replaced with a panel that can represent a terms facet in a variety of ways."
+ " point this at not_analyzed fields for that reason. This panel is going away soon, it has"+
+ " been replaced by the terms panel . Please use that one instead."
};
// Set and populate defaults
@@ -247,11 +247,10 @@ angular.module('kibana.pie', [])
colors: querySrv.colors
};
- // Populate element
+ // Populate legend
if(elem.is(":visible")){
scripts.wait(function(){
- scope.plot = $.plot(elem, scope.data, pie);
- scope.legend = scope.plot.getData();
+ scope.legend = $.plot(elem, scope.data, pie).getData();
if(!scope.$$phase) {
scope.$apply();
}
diff --git a/panels/terms/editor.html b/panels/terms/editor.html
new file mode 100644
index 00000000000..3568e529fc3
--- /dev/null
+++ b/panels/terms/editor.html
@@ -0,0 +1,50 @@
+
+
+
+
+ Style
+
+
+
+ Font Size
+
+
+
+ Legend
+
+
+
+ Legend Format
+
+
+
+ Missing
+
+
+ Other
+
+
+ Donut
+
+
+ Tilt
+
+
+ Labels
+
+
+
+
diff --git a/panels/terms/module.html b/panels/terms/module.html
new file mode 100644
index 00000000000..9cb6848dc44
--- /dev/null
+++ b/panels/terms/module.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+ {{term.label}} {{term.data[0][1]}}
+
+
+
+
+
+ {{term.label}} ({{term.data[0][1]}})
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{term.label}} {{term.data[0][1]}}
+
+
+
+
+
+ {{term.label}} ({{term.data[0][1]}})
+
+
+
+
+
+
+
+ Term Count Action
+
+
+ {{term.label}}
+ {{term.data[0][1]}}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/panels/terms/module.js b/panels/terms/module.js
new file mode 100644
index 00000000000..2d791866e65
--- /dev/null
+++ b/panels/terms/module.js
@@ -0,0 +1,302 @@
+/*jshint globalstrict:true */
+/*global angular:true */
+
+/*
+
+ ## Terms
+
+ ### Parameters
+ * style :: A hash of css styles
+ * size :: top N
+ * arrangement :: How should I arrange the query results? 'horizontal' or 'vertical'
+ * chart :: Show a chart? 'none', 'bar', 'pie'
+ * donut :: Only applies to 'pie' charts. Punches a hole in the chart for some reason
+ * tilt :: Only 'pie' charts. Janky 3D effect. Looks terrible 90% of the time.
+ * lables :: Only 'pie' charts. Labels on the pie?
+
+*/
+
+'use strict';
+
+angular.module('kibana.terms', [])
+.controller('terms', function($scope, querySrv, dashboard, filterSrv) {
+
+ $scope.panelMeta = {
+ status : "Beta",
+ description : "Displays the results of an elasticsearch facet as a pie chart, bar chart, or a "+
+ "table"
+ };
+
+ // Set and populate defaults
+ var _d = {
+ queries : {
+ mode : 'all',
+ ids : []
+ },
+ field : '_type',
+ exclude : [],
+ missing : true,
+ other : true,
+ size : 10,
+ style : { "font-size": '10pt'},
+ donut : false,
+ tilt : false,
+ labels : true,
+ arrangement : 'horizontal',
+ chart : 'bar',
+ counter_pos : 'above'
+ };
+ _.defaults($scope.panel,_d);
+
+ $scope.init = function () {
+ $scope.hits = 0;
+
+ $scope.$on('refresh',function(){
+ $scope.get_data();
+ });
+ $scope.get_data();
+
+ };
+
+ $scope.get_data = function(segment,query_id) {
+ // Make sure we have everything for the request to complete
+ if(dashboard.indices.length === 0) {
+ return;
+ }
+
+ $scope.panel.loading = true;
+ var request,
+ results,
+ boolQuery;
+
+ request = $scope.ejs.Request().indices(dashboard.indices);
+
+ $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+ // This could probably be changed to a BoolFilter
+ boolQuery = $scope.ejs.BoolQuery();
+ _.each($scope.panel.queries.ids,function(id) {
+ boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+ });
+
+ // Terms mode
+ request = request
+ .facet($scope.ejs.TermsFacet('terms')
+ .field($scope.panel.field)
+ .size($scope.panel.size)
+ .exclude($scope.panel.exclude)
+ .facetFilter($scope.ejs.QueryFilter(
+ $scope.ejs.FilteredQuery(
+ boolQuery,
+ filterSrv.getBoolFilter(filterSrv.ids)
+ )))).size(0);
+
+ //$scope.populate_modal(request);
+
+ results = request.doSearch();
+
+ // Populate scope when we have results
+ results.then(function(results) {
+ var k = 0;
+ $scope.panel.loading = false;
+ $scope.hits = results.hits.total;
+ $scope.data = [];
+ _.each(results.facets.terms.terms, function(v) {
+ var slice = { label : v.term, data : [[k,v.count]], actions: true};
+ $scope.data.push(slice);
+ k = k + 1;
+ });
+
+ $scope.data.push({label:'Missing field',
+ data:[[k,results.facets.terms.missing]],meta:"missing",color:'#aaa',opacity:0});
+ $scope.data.push({label:'Other values',
+ data:[[k+1,results.facets.terms.other]],meta:"other",color:'#444'});
+
+ $scope.$emit('render');
+ });
+ };
+
+ $scope.build_search = function(term,negate) {
+ if(_.isUndefined(term.meta)) {
+ filterSrv.set({type:'terms',field:$scope.panel.field,value:term.label,
+ mandate:(negate ? 'mustNot':'must')});
+ } else if(term.meta === 'missing') {
+ filterSrv.set({type:'exists',field:$scope.panel.field,
+ mandate:(negate ? 'must':'mustNot')});
+ } else {
+ return;
+ }
+ dashboard.refresh();
+ };
+
+ $scope.set_refresh = function (state) {
+ $scope.refresh = state;
+ };
+
+ $scope.close_edit = function() {
+ if($scope.refresh) {
+ $scope.get_data();
+ }
+ $scope.refresh = false;
+ $scope.$emit('render');
+ };
+
+ $scope.showMeta = function(term) {
+ if(_.isUndefined(term.meta)) {
+ return true;
+ }
+ if(term.meta === 'other' && !$scope.panel.other) {
+ return false;
+ }
+ if(term.meta === 'missing' && !$scope.panel.missing) {
+ return false;
+ }
+ return true;
+ };
+
+}).directive('termsChart', function(querySrv, filterSrv, dashboard) {
+ return {
+ restrict: 'A',
+ link: function(scope, elem, attrs, ctrl) {
+
+ // Receive render events
+ scope.$on('render',function(){
+ render_panel();
+ });
+
+ // Re-render if the window is resized
+ angular.element(window).bind('resize', function(){
+ render_panel();
+ });
+
+ // Function for rendering panel
+ function render_panel() {
+ var plot, chartData;
+ var scripts = $LAB.script("common/lib/panels/jquery.flot.js").wait()
+ .script("common/lib/panels/jquery.flot.pie.js");
+
+ // IE doesn't work without this
+ elem.css({height:scope.panel.height||scope.row.height});
+
+ // Make a clone we can operate on.
+ chartData = _.clone(scope.data);
+ chartData = scope.panel.missing ? chartData :
+ _.without(chartData,_.findWhere(chartData,{meta:'missing'}));
+ chartData = scope.panel.other ? chartData :
+ _.without(chartData,_.findWhere(chartData,{meta:'other'}));
+
+ // Populate element.
+ scripts.wait(function(){
+ // Populate element
+ try {
+ // Add plot to scope so we can build out own legend
+ if(scope.panel.chart === 'bar') {
+ plot = $.plot(elem, chartData, {
+ legend: { show: false },
+ series: {
+ lines: { show: false, },
+ bars: { show: true, fill: 1, barWidth: 0.8, horizontal: false },
+ shadowSize: 1
+ },
+ yaxis: { show: true, min: 0, color: "#c8c8c8" },
+ xaxis: { show: false },
+ grid: {
+ borderWidth: 0,
+ borderColor: '#eee',
+ color: "#eee",
+ hoverable: true,
+ clickable: true
+ },
+ colors: querySrv.colors
+ });
+ }
+ if(scope.panel.chart === 'pie') {
+ var labelFormat = function(label, series){
+ return ''+
+ label+' '+Math.round(series.percent)+'%
';
+ };
+
+ plot = $.plot(elem, chartData, {
+ legend: { show: false },
+ series: {
+ pie: {
+ innerRadius: scope.panel.donut ? 0.4 : 0,
+ tilt: scope.panel.tilt ? 0.45 : 1,
+ radius: 1,
+ show: true,
+ combine: {
+ color: '#999',
+ label: 'The Rest'
+ },
+ stroke: {
+ width: 0
+ },
+ label: {
+ show: scope.panel.labels,
+ radius: 2/3,
+ formatter: labelFormat,
+ threshold: 0.1
+ }
+ }
+ },
+ //grid: { hoverable: true, clickable: true },
+ grid: { hoverable: true, clickable: true },
+ colors: querySrv.colors
+ });
+ }
+
+ // Populate legend
+ if(elem.is(":visible")){
+ scripts.wait(function(){
+ scope.legend = plot.getData();
+ if(!scope.$$phase) {
+ scope.$apply();
+ }
+ });
+ }
+
+ } catch(e) {
+ elem.text(e);
+ }
+ });
+ }
+
+ function tt(x, y, contents) {
+ var tooltip = $('#pie-tooltip').length ?
+ $('#pie-tooltip') : $('
');
+ //var tooltip = $('#pie-tooltip')
+ tooltip.html(contents).css({
+ position: 'absolute',
+ top : y + 5,
+ left : x + 5,
+ color : "#c8c8c8",
+ padding : '10px',
+ 'font-size': '11pt',
+ 'font-weight' : 200,
+ 'background-color': '#1f1f1f',
+ 'border-radius': '5px',
+ }).appendTo("body");
+ }
+
+ elem.bind("plotclick", function (event, pos, object) {
+ if(object) {
+ scope.build_search(scope.data[object.seriesIndex]);
+ }
+ });
+
+ elem.bind("plothover", function (event, pos, item) {
+ if (item) {
+ var value = scope.panel.chart === 'bar' ?
+ item.datapoint[1] : item.datapoint[1][0][1];
+ tt(pos.pageX, pos.pageY,
+ "
"+item.series.label+
+ " ("+value.toFixed(0)+")");
+ } else {
+ $("#pie-tooltip").remove();
+ }
+ });
+
+ }
+ };
+});