/** @scratch /panels/5
* include::panels/histogram.asciidoc[]
*/
/** @scratch /panels/histogram/0
* == Histogram
* Status: *Stable*
*
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
* fields.
*
*/
define([
'angular',
'app',
'jquery',
'underscore',
'kbn',
'moment',
'./timeSeries',
'jquery.flot',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.byte',
'jquery.flot.stack',
'jquery.flot.stackpercent'
],
function (angular, app, $, _, kbn, moment, timeSeries) {
'use strict';
var module = angular.module('kibana.panels.graphite', []);
app.useModule(module);
module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout) {
$scope.panelMeta = {
modals : [],
editorTabs: [],
fullEditorTabs : [
{
title:'Targets',
src:'app/panels/graphite/editor.html'
},
{
title:'Axis labels',
src:'app/panels/graphite/axisEditor.html'
},
{
title:'Style',
src:'app/panels/graphite/styleEditor.html'
}
],
menuItems: [
{ text: 'View fullscreen', action: function() { $scope.toggleFullscreen(); }},
{ text: 'Edit', action: function() { $scope.openConfigureModal(); }},
{ text: 'Duplicate', action: function() { $scope.duplicate(); }},
{ text: 'Remove', action: function() { $scope.remove_panel_from_row($scope.row, $scope.panel); }}
],
status : "Unstable",
description : "Graphite graphing panel
"
};
// Set and populate defaults
var _d = {
/** @scratch /panels/histogram/3
* x-axis:: Show the x-axis
*/
'x-axis' : true,
/** @scratch /panels/histogram/3
* y-axis:: Show the y-axis
*/
'y-axis' : true,
/** @scratch /panels/histogram/3
* scale:: Scale the y-axis by this factor
*/
scale : 1,
/** @scratch /panels/histogram/3
* y_format:: 'none','bytes','short '
*/
y_format : 'none',
/** @scratch /panels/histogram/5
* grid object:: Min and max y-axis values
* grid.min::: Minimum y-axis value
* grid.max::: Maximum y-axis value
*/
grid : {
max: null,
min: 0
},
/** @scratch /panels/histogram/3
* ==== Annotations
* annotate object:: A query can be specified, the results of which will be displayed as markers on
* the chart. For example, for noting code deploys.
* annotate.enable::: Should annotations, aka markers, be shown?
* annotate.query::: Lucene query_string syntax query to use for markers.
* annotate.size::: Max number of markers to show
* annotate.field::: Field from documents to show
* annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
*/
annotate : {
enable : false,
query : "*",
size : 20,
field : '_type',
sort : ['_score','desc']
},
/** @scratch /panels/histogram/3
* ==== Interval options
* auto_int:: Automatically scale intervals?
*/
auto_int : true,
/** @scratch /panels/histogram/3
* resolution:: If auto_int is true, shoot for this many bars.
*/
resolution : 100,
/** @scratch /panels/histogram/3
* interval:: If auto_int is set to false, use this as the interval.
*/
interval : '5m',
/** @scratch /panels/histogram/3
* interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
*/
intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
/** @scratch /panels/histogram/3
* ==== Drawing options
* lines:: Show line chart
*/
lines : true,
/** @scratch /panels/histogram/3
* fill:: Area fill factor for line charts, 1-10
*/
fill : 0,
/** @scratch /panels/histogram/3
* linewidth:: Weight of lines in pixels
*/
linewidth : 1,
/** @scratch /panels/histogram/3
* points:: Show points on chart
*/
points : false,
/** @scratch /panels/histogram/3
* pointradius:: Size of points in pixels
*/
pointradius : 5,
/** @scratch /panels/histogram/3
* bars:: Show bars on chart
*/
bars : false,
/** @scratch /panels/histogram/3
* stack:: Stack multiple series
*/
stack : true,
/** @scratch /panels/histogram/3
* spyable:: Show inspect icon
*/
spyable : true,
/** @scratch /panels/histogram/3
* zoomlinks:: Show `Zoom Out' link
*/
zoomlinks : false,
/** @scratch /panels/histogram/3
* options:: Show quick view options section
*/
options : false,
/** @scratch /panels/histogram/3
* legend:: Display the legond
*/
legend : true,
/** @scratch /panels/histogram/3
* interactive:: Enable click-and-drag to zoom functionality
*/
interactive : true,
/** @scratch /panels/histogram/3
* legend_counts:: Show counts in legend
*/
legend_counts : true,
/** @scratch /panels/histogram/3
* ==== Transformations
* timezone:: Correct for browser timezone?. Valid values: browser, utc
*/
timezone : 'browser', // browser or utc
/** @scratch /panels/histogram/3
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
* queries
*/
percentage : false,
/** @scratch /panels/histogram/3
* zerofill:: Improves the accuracy of line charts at a small performance cost.
*/
zerofill : true,
tooltip : {
value_type: 'cumulative',
query_as_alias: true
},
targets: [],
aliasColors: {}
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip,_d.tooltip);
_.defaults($scope.panel.annotate,_d.annotate);
_.defaults($scope.panel.grid,_d.grid);
$scope.init = function() {
// Hide view options by default
$scope.options = false;
$scope.editor = {index: 1};
$scope.editorTabs = _.union(['General'],_.pluck($scope.panelMeta.fullEditorTabs,'title'));
$scope.hiddenSeries = {};
// Always show the query if an alias isn't set. Users can set an alias if the query is too
// long
$scope.panel.tooltip.query_as_alias = true;
$scope.get_data();
};
$scope.set_interval = function(interval) {
if(interval !== 'auto') {
$scope.panel.auto_int = false;
$scope.panel.interval = interval;
} else {
$scope.panel.auto_int = true;
}
};
$scope.typeAheadSource = function () {
return ["test", "asd", "testing2"];
};
$scope.remove_panel_from_row = function(row, panel) {
if ($scope.fullscreen) {
$rootScope.$emit('panel-fullscreen-exit');
}
else {
$scope.$parent.remove_panel_from_row(row, panel);
}
};
$scope.removeTarget = function (target) {
$scope.panel.targets = _.without($scope.panel.targets, target);
$scope.get_data();
};
$scope.interval_label = function(interval) {
return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
};
/**
* The time range effecting the panel
* @return {[type]} [description]
*/
$scope.get_time_range = function () {
var range = $scope.range = filterSrv.timeRange();
return range;
};
$scope.get_interval = function () {
var interval = $scope.panel.interval;
var range;
if ($scope.panel.auto_int) {
range = $scope.get_time_range();
if (range) {
interval = kbn.secondsToHms(
kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
);
}
}
$scope.panel.interval = interval || '10m';
return $scope.panel.interval;
};
$scope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
];
/**
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
* need to be consulted (like timestamped logstash indicies)
*
* The results of this function are stored on the scope's data property. This property will be an
* array of objects with the properties info, time_series, and hits. These objects are used in the
* render_panel function to create the historgram.
*
*/
$scope.get_data = function() {
delete $scope.panel.error;
$scope.panelMeta.loading = true;
var range = $scope.get_time_range();
var interval = $scope.get_interval(range);
var graphiteQuery = {
range: range,
targets: $scope.panel.targets,
maxDataPoints: $scope.panel.span * 50
};
return graphiteSrv.query(graphiteQuery)
.then(function(results) {
$scope.panelMeta.loading = false;
var data = $scope.receiveGraphiteData(results, range, interval);
$scope.$emit('render', data);
})
.then(null, function(err) {
$scope.panel.error = err.message || "Graphite HTTP Request Error";
});
};
$scope.receiveGraphiteData = function(results, range, interval) {
results = results.data;
$scope.legend = [];
var data = [];
if(results.length === 0 ) {
return [];
}
var tsOpts = {
interval: interval,
start_date: range && range.from,
end_date: range && range.to,
fill_style: 'no'
};
_.each(results, function(targetData) {
var time_series = new timeSeries.ZeroFilled(tsOpts);
_.each(targetData.datapoints, function(valueArray) {
if (valueArray[0]) {
time_series.addValue(valueArray[1] * 1000, valueArray[0]);
}
});
var target = graphiteSrv.match($scope.panel.targets, targetData.target);
var alias = targetData.target;
var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
var seriesInfo = {
alias: alias,
color: color,
enable: true,
yaxis: target.yaxis || 1
};
$scope.legend.push(seriesInfo);
data.push({
info: seriesInfo,
time_series: time_series,
yaxis: target.yaxis || 1
});
});
return data;
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
};
$scope.enterFullscreenMode = function(options) {
var oldHeight = $scope.row.height;
var docHeight = $(window).height();
$scope.row.height = options.edit ? 200 : Math.floor(docHeight * 0.7);
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
$scope.inEditMode = false;
$scope.fullscreen = false;
$scope.row.height = oldHeight;
closeEditMode();
$timeout(function() {
$scope.$emit('render');
});
});
$(window).scrollTop(0);
$scope.inEditMode = options.edit;
$scope.fullscreen = true;
$rootScope.$emit('panel-fullscreen-enter');
$timeout(function() {
$scope.$emit('render');
});
};
$scope.openConfigureModal = function() {
if ($scope.fullscreen) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
$scope.enterFullscreenMode({edit: true});
};
$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.render = function() {
$scope.$emit('render');
};
$scope.changeSeriesColor = function(series, color) {
series.color = color;
$scope.panel.aliasColors[series.alias] = series.color;
$scope.render();
};
$scope.duplicate = function(addToRow) {
addToRow = addToRow || $scope.row;
var currentRowSpan = $scope.rowSpan(addToRow);
if (currentRowSpan <= 8) {
addToRow.panels.push(angular.copy($scope.panel));
}
else {
var rowsList = $scope.dashboard.current.rows;
var rowIndex = _.indexOf(rowsList, addToRow);
if (rowIndex === rowsList.length - 1) {
var newRow = angular.copy($scope.row);
newRow.panels = [];
$scope.dashboard.current.rows.push(newRow);
$scope.duplicate(newRow);
}
else {
$scope.duplicate(rowsList[rowIndex+1]);
}
}
};
$scope.toggleFullscreen = function(evt) {
if ($scope.fullscreen) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
if (evt) {
var elem = $(evt.target);
if (!elem.hasClass('panel-extra') ||
elem.attr('ng-click')) {
return;
}
}
$scope.enterFullscreenMode({edit: false});
};
$scope.toggleSeries = function(info) {
if ($scope.hiddenSeries[info.alias]) {
delete $scope.hiddenSeries[info.alias];
}
else {
$scope.hiddenSeries[info.alias] = true;
}
$scope.$emit('toggleLegend', info.alias);
};
});
module.directive('histogramChart', function(filterSrv) {
return {
restrict: 'A',
template: '