removed histogram panel

This commit is contained in:
Torkel Odegaard 2014-01-06 14:50:56 +01:00
parent 26c115f29f
commit 9d2ce9874a
9 changed files with 2 additions and 2663 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@ node_modules
.aws-config.json
dist
web.config
config.dev.js
config.dev.js
*.sublime-workspace

File diff suppressed because one or more lines are too long

View File

@ -1,48 +0,0 @@
<div class="editor-row">
<div class="section">
<h5>Values</h5>
<div class="editor-option">
<label class="small">Chart value</label>
<select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total']"></select>
</div>
<div class="editor-option" ng-show="panel.mode != 'count'">
<label class="small">Value Field <tip>This field must contain a numeric value</tip></label>
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-medium" ng-model="panel.value_field">
</div>
</div>
<div class="section">
<h5>Transform Series</h5>
<div class="editor-option" ng-show="panel.mode != 'count'">
<label class="small">Scale</label>
<input type="text" class="input-mini" ng-model="panel.scale">
</div>
<div class="editor-option">
<label class="small">Seconds <tip>Normalize intervals to per-second</tip></label><input type="checkbox" ng-model="panel.scaleSeconds" ng-checked="panel.scaleSeconds">
</div>
<div class="editor-option">
<label class="small">Derivative <tip>Plot the change per interval in the series</tip></label><input type="checkbox" ng-model="panel.derivative" ng-checked="panel.derivative" ng-change="set_refresh(true)">
</div>
</div>
<div class="section">
<h5>Time Options</h5>
<div class="editor-option">
<label class="small">Time Field</label>
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
</div>
<div class="editor-option">
<label class="small">Time correction</label>
<select ng-model="panel.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
</div>
<div class="editor-option">
<label class="small">Auto-interval</label><input type="checkbox" ng-model="panel.auto_int" ng-checked="panel.auto_int" />
</div>
<div class="editor-option" ng-show='panel.auto_int'>
<label class="small">Resolution <tip>Shoot for this many data points, rounding to sane intervals</tip></label>
<input type="number" class='input-mini' ng-model="panel.resolution" ng-change='set_refresh(true)'/>
</div>
<div class="editor-option" ng-hide='panel.auto_int'>
<label class="small">Interval <tip>Use Elasticsearch date math format (eg 1m, 5m, 1d, 2w, 1y)</tip></label>
<input type="text" class='input-mini' ng-model="panel.interval" ng-change='set_refresh(true)'/>
</div>
</div>
</div>

View File

@ -1,57 +0,0 @@
define([
'kbn'
],
function (kbn) {
'use strict';
/**
* manages the interval logic
* @param {[type]} interval_string An interval string in the format '1m', '1y', etc
*/
function Interval(interval_string) {
this.string = interval_string;
var info = kbn.describe_interval(interval_string);
this.type = info.type;
this.ms = info.sec * 1000 * info.count;
// does the length of the interval change based on the current time?
if (this.type === 'y' || this.type === 'M') {
// we will just modify this time object rather that create a new one constantly
this.get = this.get_complex;
this.date = new Date(0);
} else {
this.get = this.get_simple;
}
}
Interval.prototype = {
toString: function () {
return this.string;
},
after: function(current_ms) {
return this.get(current_ms, 1);
},
before: function (current_ms) {
return this.get(current_ms, -1);
},
get_complex: function (current, delta) {
this.date.setTime(current);
switch(this.type) {
case 'M':
this.date.setUTCMonth(this.date.getUTCMonth() + delta);
break;
case 'y':
this.date.setUTCFullYear(this.date.getUTCFullYear() + delta);
break;
}
return this.date.getTime();
},
get_simple: function (current, delta) {
return current + (delta * this.ms);
}
};
return Interval;
});

View File

@ -1,99 +0,0 @@
<div ng-controller='histogram' ng-init="init()" style="min-height:{{panel.height || row.height}}">
<style>
.histogram-legend {
display:inline-block;
padding-right:5px
}
.histogram-legend-dot {
display:inline-block;
height:10px;
width:10px;
border-radius:5px;
}
.histogram-legend-item {
display:inline-block;
}
.histogram-chart {
position:relative;
}
.histogram-options {
padding: 5px;
margin-right: 15px;
margin-bottom: 0px;
}
.histogram-options label {
margin: 0px 0px 0px 10px !important;
}
.histogram-options span {
white-space: nowrap;
}
/* this is actually should be in bootstrap */
.form-inline .checkbox {
display: inline-block;
}
</style>
<div>
<span ng-show='panel.options'>
<a class="link small" ng-show='panel.options' ng-click="options=!options">
View <i ng-show="!options" class="icon-caret-right"></i><i ng-show="options" class="icon-caret-down"></i>
</a> |&nbsp
</span>
<span ng-show='panel.zoomlinks && data'>
<!--<a class='small' ng-click='zoom(0.5)'><i class='icon-zoom-in'></i> Zoom In</a>-->
<a class='small' ng-click='zoom(2)'><i class='icon-zoom-out'></i> Zoom Out</a> |&nbsp
</span>
<span ng-show="panel.legend" ng-repeat='series in legend' class="histogram-legend">
<i class='icon-circle' ng-style="{color: series.query.color}"></i>
<span class='small histogram-legend-item'>
<span ng-if="panel.show_query">{{series.query.alias || series.query.query}}</span>
<span ng-if="!panel.show_query">{{series.query.alias}}</span>
<span ng-show="panel.legend_counts"> ({{series.hits}})</span>
</span>
</span>
<span ng-show="panel.legend" class="small"><span ng-show="panel.derivative">change in </span><span class="strong" ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong ng-hide="panel.scaleSeconds">{{panel.interval}}</strong><strong ng-show="panel.scaleSeconds">1s</strong> | (<strong>{{hits}}</strong> hits)</span>
</div>
<form class="form-inline bordered histogram-options" ng-show="options">
<span>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
Bars
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
Lines
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
Stack
</label>
</div>
</span>
<span ng-show="panel.stack">
<div class="checkbox">
<label style="white-space:nowrap" class="small">
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
Percent
</label>
</div>
</span>
<span>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend" ng-change="render()">
Legend
</label>
</div>
</span>
<span>
<label class="small">Interval</label> <select ng-change="set_interval(panel.interval);get_data();" class="input-small" ng-model="panel.interval" ng-options="interval_label(time) for time in _.union([panel.interval],panel.intervals)"></select>
</span>
</form>
<center><img ng-show='panel.loading && _.isUndefined(data)' src="img/load_big.gif"></center>
<div histogram-chart class="pointer histogram-chart" params="{{panel}}"></div>
</div>

View File

@ -1,771 +0,0 @@
/** @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.histogram', []);
app.useModule(module);
module.controller('histogram', function($scope, querySrv, dashboard, filterSrv) {
$scope.panelMeta = {
modals : [
{
description: "Inspect",
icon: "icon-info-sign",
partial: "app/partials/inspector.html",
show: $scope.panel.spyable
}
],
editorTabs : [
{
title:'Style',
src:'app/panels/histogram/styleEditor.html'
},
{
title:'Queries',
src:'app/panels/histogram/queriesEditor.html'
},
],
status : "Stable",
description : "A bucketed time series chart of the current query or queries. Uses the "+
"Elasticsearch date_histogram facet. If using time stamped indices this panel will query"+
" them sequentially to attempt to apply the lighest possible load to your Elasticsearch cluster"
};
// Set and populate defaults
var _d = {
/** @scratch /panels/histogram/3
* === Parameters
* ==== Axis options
* mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
* defined. Possible values: count, mean, max, min, total.
*/
mode : 'count',
/** @scratch /panels/histogram/3
* time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
*/
time_field : '@timestamp',
/** @scratch /panels/histogram/3
* value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
*/
value_field : null,
/** @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/5
* ==== Queries
* queries object:: This object describes the queries to use on this panel.
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
* queries.ids::: In +selected+ mode, which query ids are selected.
*/
queries : {
mode : 'all',
ids : []
},
/** @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 : false,
/** @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 : 3,
/** @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 : true,
/** @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 : true,
/** @scratch /panels/histogram/3
* options:: Show quick view options section
*/
options : true,
/** @scratch /panels/histogram/3
* legend:: Display the legond
*/
legend : true,
/** @scratch /panels/histogram/3
* show_query:: If no alias is set, should the query be displayed?
*/
show_query : 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,
/** @scratch /panels/histogram/3
* derivative:: Show each point on the x-axis as the change from the previous point
*/
derivative : false,
/** @scratch /panels/histogram/3
* tooltip object::
* tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
* tooltip.query_as_alias::: If no alias is set, should the query be displayed?
*/
tooltip : {
value_type: 'cumulative',
query_as_alias: true
}
};
_.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;
// 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.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,
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;
};
/**
* 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.
*
* @param {number} segment The segment count, (0 based)
* @param {number} query_id The id of the query, generated on the first run and passed back when
* this call is made recursively for more segments
*/
$scope.get_data = function(data, segment, query_id) {
var
_range,
_interval,
request,
queries,
results;
if (_.isUndefined(segment)) {
segment = 0;
}
delete $scope.panel.error;
// Make sure we have everything for the request to complete
if(dashboard.indices.length === 0) {
return;
}
_range = $scope.get_time_range();
_interval = $scope.get_interval(_range);
if ($scope.panel.auto_int) {
$scope.panel.interval = kbn.secondsToHms(
kbn.calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000);
}
$scope.panelMeta.loading = true;
request = $scope.ejs.Request().indices(dashboard.indices[segment]);
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
// Build the query
_.each(queries, function(q) {
var query = $scope.ejs.FilteredQuery(
querySrv.toEjsObj(q),
filterSrv.getBoolFilter(filterSrv.ids)
);
var facet = $scope.ejs.DateHistogramFacet(q.id);
if($scope.panel.mode === 'count') {
facet = facet.field($scope.panel.time_field).global(true);
} else {
if(_.isNull($scope.panel.value_field)) {
$scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
return;
}
facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field).global(true);
}
facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
request = request.facet(facet)
.size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
});
if($scope.panel.annotate.enable) {
var query = $scope.ejs.FilteredQuery(
$scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
filterSrv.getBoolFilter(filterSrv.idsByType('time'))
);
request = request.query(query);
// This is a hack proposed by @boaz to work around the fact that we can't get
// to field data values directly, and we need timestamps as normalized longs
request = request.sort([
$scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
$scope.ejs.Sort($scope.panel.time_field).desc()
]);
}
// Populate the inspector panel
$scope.populate_modal(request);
// Then run it
results = request.doSearch();
// Populate scope when we have results
return results.then(function(results) {
$scope.panelMeta.loading = false;
if(segment === 0) {
$scope.legend = [];
$scope.hits = 0;
data = [];
$scope.annotations = [];
query_id = $scope.query_id = new Date().getTime();
}
// Check for error and abort if found
if(!(_.isUndefined(results.error))) {
$scope.panel.error = $scope.parse_error(results.error);
return;
}
// Make sure we're still on the same query/queries
if($scope.query_id === query_id) {
var i = 0,
time_series,
hits;
_.each(queries, function(q) {
var query_results = results.facets[q.id];
// we need to initialize the data variable on the first run,
// and when we are working on the first segment of the data.
if(_.isUndefined(data[i]) || segment === 0) {
var tsOpts = {
interval: _interval,
start_date: _range && _range.from,
end_date: _range && _range.to,
fill_style: $scope.panel.derivative ? 'null' : 'minimal'
};
time_series = new timeSeries.ZeroFilled(tsOpts);
hits = 0;
} else {
time_series = data[i].time_series;
hits = data[i].hits;
}
// push each entry into the time series, while incrementing counters
_.each(query_results.entries, function(entry) {
time_series.addValue(entry.time, entry[$scope.panel.mode]);
hits += entry.count; // The series level hits counter
$scope.hits += entry.count; // Entire dataset level hits counter
});
$scope.legend[i] = {query:q,hits:hits};
data[i] = {
info: q,
time_series: time_series,
hits: hits
};
console.log("elastic time_series:", time_series);
i++;
});
if($scope.panel.annotate.enable) {
$scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
var _p = _.omit(hit,'_source','sort','_score');
var _h = _.extend(kbn.flatten_json(hit._source),_p);
return {
min: hit.sort[1],
max: hit.sort[1],
eventType: "annotation",
title: null,
description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
_h[$scope.panel.annotate.field]+"</small><br>"+
moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
score: hit.sort[0]
};
}));
// Sort the data
$scope.annotations = _.sortBy($scope.annotations, function(v){
// Sort in reverse
return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
});
// And slice to the right size
$scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
}
// Tell the histogram directive to render.
$scope.$emit('render', data);
// If we still have segments left, get them
if(segment < dashboard.indices.length-1) {
$scope.get_data(data,segment+1,query_id);
}
}
});
};
// function $scope.zoom
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
$scope.zoom = function(factor) {
var _range = filterSrv.timeRange();
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
var _center = _range.to.valueOf() - _timespan/2;
var _to = (_center + (_timespan*factor)/2);
var _from = (_center - (_timespan*factor)/2);
// If we're not already looking into the future, don't.
if(_to > Date.now() && _range.to < Date.now()) {
var _offset = _to - Date.now();
_from = _from - _offset;
_to = Date.now();
}
if(factor > 1) {
filterSrv.removeByType('time');
}
filterSrv.set({
type:'time',
from:moment.utc(_from).toDate(),
to:moment.utc(_to).toDate(),
field:$scope.panel.time_field
});
};
// I really don't like this function, too much dom manip. Break out into directive?
$scope.populate_modal = function(request) {
$scope.inspector = angular.toJson(JSON.parse(request.toString()),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');
};
});
module.directive('histogramChart', function(dashboard, filterSrv) {
return {
restrict: 'A',
template: '<div></div>',
link: function(scope, elem) {
var data, plot;
scope.$on('refresh',function(){
scope.get_data();
});
// Receive render events
scope.$on('render',function(event,d){
data = d || data;
render_panel(data);
});
// Re-render if the window is resized
angular.element(window).bind('resize', function(){
render_panel(data);
});
var scale = function(series,factor) {
return _.map(series,function(p) {
return [p[0],p[1]*factor];
});
};
var scaleSeconds = function(series,interval) {
return _.map(series,function(p) {
return [p[0],p[1]/kbn.interval_to_seconds(interval)];
});
};
var derivative = function(series) {
return _.map(series, function(p,i) {
var _v;
if(i === 0 || p[1] === null) {
_v = [p[0],null];
} else {
_v = series[i-1][1] === null ? [p[0],null] : [p[0],p[1]-(series[i-1][1])];
}
return _v;
});
};
// Function for rendering panel
function render_panel(data) {
// IE doesn't work without this
elem.css({height:scope.panel.height || scope.row.height});
// Populate from the query service
try {
_.each(data, function(series) {
series.label = series.info.alias;
series.color = series.info.color;
});
} catch(e) {return;}
// Set barwidth based on specified interval
var barwidth = kbn.interval_to_ms(scope.panel.interval);
var stack = scope.panel.stack ? true : null;
// Populate element
try {
var options = {
legend: { show: false },
series: {
stackpercent: scope.panel.stack ? scope.panel.percentage : false,
stack: scope.panel.percentage ? null : stack,
lines: {
show: scope.panel.lines,
// Silly, but fixes bug in stacked percentages
fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
lineWidth: scope.panel.linewidth,
steps: false
},
bars: {
show: scope.panel.bars,
fill: 1,
barWidth: barwidth/1.5,
zero: false,
lineWidth: 0
},
points: {
show: scope.panel.points,
fill: 1,
fillColor: false,
radius: scope.panel.pointradius
},
shadowSize: 1
},
yaxis: {
show: scope.panel['y-axis'],
min: scope.panel.grid.min,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
},
xaxis: {
timezone: scope.panel.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
timeformat: time_format(scope.panel.interval),
label: "Datetime",
ticks: elem.width()/100
},
grid: {
backgroundColor: null,
borderWidth: 0,
hoverable: true,
color: '#c8c8c8'
}
};
if(scope.panel.y_format === 'bytes') {
options.yaxis.mode = "byte";
}
if(scope.panel.y_format === 'short') {
options.yaxis.tickFormatter = function(val) {
return kbn.shortFormat(val,0);
};
}
if(scope.panel.annotate.enable) {
options.events = {
levels: 1,
data: scope.annotations,
types: {
'annotation': {
level: 1,
icon: {
icon: "icon-tag icon-flip-vertical",
size: 20,
color: "#222",
outline: "#bbb"
}
}
}
//xaxis: int // the x axis to attach events to
};
}
if(scope.panel.interactive) {
options.selection = { mode: "x", color: '#666' };
}
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
// so that the stacking happens in the proper order
var required_times = [];
if (data.length > 1) {
required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
return query.time_series.getOrderedTimes();
}));
required_times = _.uniq(required_times.sort(function (a, b) {
// decending numeric sort
return a-b;
}), true);
}
for (var i = 0; i < data.length; i++) {
var _d = data[i].time_series.getFlotPairs(required_times);
if(scope.panel.derivative) {
_d = derivative(_d);
}
if(scope.panel.scale !== 1) {
_d = scale(_d,scope.panel.scale);
}
if(scope.panel.scaleSeconds) {
_d = scaleSeconds(_d,scope.panel.interval);
}
data[i].data = _d;
}
plot = $.plot(elem, data, options);
} catch(e) {
// Nothing to do here
}
}
function time_format(interval) {
var _int = kbn.interval_to_seconds(interval);
if(_int >= 2628000) {
return "%Y-%m";
}
if(_int >= 86400) {
return "%Y-%m-%d";
}
if(_int >= 60) {
return "%H:%M<br>%m-%d";
}
return "%H:%M:%S";
}
var $tooltip = $('<div>');
elem.bind("plothover", function (event, pos, item) {
var group, value, timestamp;
if (item) {
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
group = '<small style="font-size:0.9em;">' +
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
(item.series.info.alias || item.series.info.query)+
'</small><br>';
} else {
group = kbn.query_color_dot(item.series.color, 15) + ' ';
}
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
item.datapoint[1] - item.datapoint[2] :
item.datapoint[1];
if(scope.panel.y_format === 'bytes') {
value = kbn.byteFormat(value,2);
}
if(scope.panel.y_format === 'short') {
value = kbn.shortFormat(value,2);
}
timestamp = scope.panel.timezone === 'browser' ?
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
$tooltip
.html(
group + value + " @ " + timestamp
)
.place_tt(pos.pageX, pos.pageY);
} else {
$tooltip.detach();
}
});
elem.bind("plotselected", function (event, ranges) {
filterSrv.set({
type : 'time',
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
field : scope.panel.time_field
});
});
}
};
});
});

View File

@ -1,43 +0,0 @@
<h4>Charted</h4>
<div ng-include src="'app/partials/querySelect.html'"></div>
<div class="editor-row">
<h4>Markers</h4>
<div class="small">
Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
</div>
<style>
.querySelect .query {
margin-right: 5px;
}
.querySelect .selected {
border: 3px solid;
}
.querySelect .unselected {
border: 0px solid;
}
</style>
<p>
<div class="editor-option">
<label class="small">Enable</label>
<input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Marker Query</label>
<input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Tooltip field</label>
<input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Limit <tip>Max markers on the chart</tip></label>
<input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
<i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
</div>
</div>

View File

@ -1,88 +0,0 @@
<div class="editor-row">
<div class="section">
<h5>Chart Options</h5>
<div class="editor-option">
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars">
</div>
<div class="editor-option">
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines">
</div>
<div class="editor-option">
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points">
</div>
<div class="editor-option">
<label class="small">Selectable</label><input type="checkbox" ng-model="panel.interactive" ng-checked="panel.interactive">
</div>
<div class="editor-option">
<label class="small">xAxis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']"></div>
<div class="editor-option">
<label class="small">yAxis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']"></div>
<div class="editor-option" ng-show="panel.lines">
<label class="small">Line Fill</label>
<select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
</div>
<div class="editor-option" ng-show="panel.lines">
<label class="small">Line Width</label>
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
</div>
<div class="editor-option" ng-show="panel.points">
<label class="small">Point Radius</label>
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]"></select>
</div>
<div class="editor-option">
<label class="small">Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_format" ng-options="f for f in ['none','short','bytes']"></select>
</div>
</div>
<div class="section">
<h5>Multiple Series</h5>
<div class="editor-option">
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack">
</div>
<div class="editor-option" ng-show="panel.stack">
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage">
</div>
<div class="editor-option" ng-show="panel.stack">
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']"></select>
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Header<h5>
<div class="editor-option">
<label class="small">Zoom</label><input type="checkbox" ng-model="panel.zoomlinks" ng-checked="panel.zoomlinks" />
</div>
<div class="editor-option">
<label class="small">View</label><input type="checkbox" ng-model="panel.options" ng-checked="panel.options" />
</div>
</div>
<div class="section">
<h5>Legend<h5>
<div class="editor-option">
<label class="small">Legend</label><input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend">
</div>
<div ng-show="panel.legend" class="editor-option">
<label class="small">Query <tip>If no alias is set, show the query in the legend</tip></label><input type="checkbox" ng-model="panel.show_query" ng-checked="panel.show_query">
</div>
<div ng-show="panel.legend" class="editor-option">
<label class="small">Counts</label><input type="checkbox" ng-model="panel.legend_counts" ng-checked="panel.legend_counts">
</div>
</div>
<div class="section">
<h5>Grid<h5>
<div class="editor-option">
<label class="small">Min / <a href='' ng-click="panel.grid.min = _.toggle(panel.grid.min,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.min"/>
</div>
<div class="editor-option">
<label class="small">Max / <a ref='' ng-click="panel.grid.max = _.toggle(panel.grid.max,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.max"/>
</div>
</div>
</div>

View File

@ -1,232 +0,0 @@
define([
'underscore',
'./interval'
],
function (_, Interval) {
'use strict';
var ts = {};
// map compatable parseInt
function base10Int(val) {
return parseInt(val, 10);
}
// trim the ms off of a time, but return it with empty ms.
function getDatesTime(date) {
return Math.floor(date.getTime() / 1000)*1000;
}
/**
* Certain graphs require 0 entries to be specified for them to render
* properly (like the line graph). So with this we will caluclate all of
* the expected time measurements, and fill the missing ones in with 0
* @param {object} opts An object specifying some/all of the options
*
* OPTIONS:
* @opt {string} interval The interval notion describing the expected spacing between
* each data point.
* @opt {date} start_date (optional) The start point for the time series, setting this and the
* end_date will ensure that the series streches to resemble the entire
* expected result
* @opt {date} end_date (optional) The end point for the time series, see start_date
* @opt {string} fill_style Either "minimal", or "all" describing the strategy used to zero-fill
* the series.
*/
ts.ZeroFilled = function (opts) {
opts = _.defaults(opts, {
interval: '10m',
start_date: null,
end_date: null,
fill_style: 'minimal'
});
// the expected differenece between readings.
this.interval = new Interval(opts.interval);
// will keep all values here, keyed by their time
this._data = {};
this.start_time = opts.start_date && getDatesTime(opts.start_date);
this.end_time = opts.end_date && getDatesTime(opts.end_date);
this.opts = opts;
};
/**
* Add a row
* @param {int} time The time for the value, in
* @param {any} value The value at this time
*/
ts.ZeroFilled.prototype.addValue = function (time, value) {
if (time instanceof Date) {
time = getDatesTime(time);
} else {
time = base10Int(time);
}
if (!isNaN(time)) {
this._data[time] = (_.isUndefined(value) ? 0 : value);
}
this._cached_times = null;
};
/**
* Get an array of the times that have been explicitly set in the series
* @param {array} include (optional) list of timestamps to include in the response
* @return {array} An array of integer times.
*/
ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
var times = _.map(_.keys(this._data), base10Int);
if (_.isArray(include)) {
times = times.concat(include);
}
return _.uniq(times.sort(function (a, b) {
// decending numeric sort
return a - b;
}), true);
};
/**
* return the rows in the format:
* [ [time, value], [time, value], ... ]
*
* Heavy lifting is done by _get(Min|Default|All)FlotPairs()
* @param {array} required_times An array of timestamps that must be in the resulting pairs
* @return {array}
*/
ts.ZeroFilled.prototype.getFlotPairs = function (required_times) {
var times = this.getOrderedTimes(required_times),
strategy,
pairs;
if(this.opts.fill_style === 'all') {
strategy = this._getAllFlotPairs;
} else if(this.opts.fill_style === 'null') {
strategy = this._getNullFlotPairs;
} else if(this.opts.fill_style === 'no') {
strategy = this._getNoZeroFlotPairs;
} else {
strategy = this._getMinFlotPairs;
}
pairs = _.reduce(
times, // what
strategy, // how
[], // where
this // context
);
// if the first or last pair is inside either the start or end time,
// add those times to the series with null values so the graph will stretch to contain them.
// Removing, flot 0.8.1's max/min params satisfy this
/*
if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
pairs.unshift([this.start_time, null]);
}
if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
pairs.push([this.end_time, null]);
}
*/
return pairs;
};
/**
* ** called as a reduce stragegy in getFlotPairs() **
* Fill zero's on either side of the current time, unless there is already a measurement there or
* we are looking at an edge.
* @return {array} An array of points to plot with flot
*/
ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
var next, expected_next, prev, expected_prev;
// check for previous measurement
if (i > 0) {
prev = times[i - 1];
expected_prev = this.interval.before(time);
if (prev < expected_prev) {
result.push([expected_prev, 0]);
}
}
// add the current time
result.push([ time, this._data[time] || 0]);
// check for next measurement
if (times.length > i) {
next = times[i + 1];
expected_next = this.interval.after(time);
if (next > expected_next) {
result.push([expected_next, 0]);
}
}
return result;
};
/**
* ** called as a reduce stragegy in getFlotPairs() **
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
* last measurement
* @return {array} An array of points to plot with flot
*/
ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
var next, expected_next;
result.push([ times[i], this._data[times[i]] || 0 ]);
next = times[i + 1];
expected_next = this.interval.after(time);
for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
result.push([expected_next, 0]);
}
return result;
};
/**
* ** called as a reduce stragegy in getFlotPairs() **
* Same as min, but fills with nulls
* @return {array} An array of points to plot with flot
*/
ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
var next, expected_next, prev, expected_prev;
// check for previous measurement
if (i > 0) {
prev = times[i - 1];
expected_prev = this.interval.before(time);
if (prev < expected_prev) {
result.push([expected_prev, null]);
}
}
// add the current time
result.push([ time, this._data[time] || null]);
// check for next measurement
if (times.length > i) {
next = times[i + 1];
expected_next = this.interval.after(time);
if (next > expected_next) {
result.push([expected_next, null]);
}
}
return result;
};
/**
* ** called as a reduce stragegy in getFlotPairs() **
* Not fill zero's on either side of the current time, only the current time
* @return {array} An array of points to plot with flot
*/
ts.ZeroFilled.prototype._getNoZeroFlotPairs = function (result, time) {
// add the current time
if(this._data[time]){
result.push([ time, this._data[time]]);
}
return result;
};
return ts;
});