Commit 4562f31b by Torkel Ödegaard

merged with master

parents faa5199a 6911e184
...@@ -2,6 +2,9 @@ vNext ...@@ -2,6 +2,9 @@ vNext
**New features or improvements** **New features or improvements**
- Allow [[..]] filter notation in all text panels (markdown/html/text) (Issue #511) - Allow [[..]] filter notation in all text panels (markdown/html/text) (Issue #511)
- New legend display option "Align as table" (Issue #136)
- New legend display option "Right side", will show legend to the right of the graph (Issue #556)
- Enhanced InfluxDB series aliasing (legend names) with pattern replacements (Issue #525)
**Changes** **Changes**
- Use unix epoch for Graphite from/to for absolute time ranges (Closes #536) - Use unix epoch for Graphite from/to for absolute time ranges (Closes #536)
......
...@@ -39,7 +39,6 @@ require.config({ ...@@ -39,7 +39,6 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack', 'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.byte': '../vendor/jquery/jquery.flot.byte',
modernizr: '../vendor/modernizr-2.6.1', modernizr: '../vendor/modernizr-2.6.1',
...@@ -80,7 +79,6 @@ require.config({ ...@@ -80,7 +79,6 @@ require.config({
// //
'jquery-ui': ['jquery'], 'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'], 'jquery.flot': ['jquery'],
'jquery.flot.byte': ['jquery', 'jquery.flot'],
'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'], 'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'], 'jquery.flot.selection':['jquery', 'jquery.flot'],
......
...@@ -18,6 +18,7 @@ function (angular, $, kbn, moment, _) { ...@@ -18,6 +18,7 @@ function (angular, $, kbn, moment, _) {
var data, plot, annotations; var data, plot, annotations;
var hiddenData = {}; var hiddenData = {};
var dashboard = scope.dashboard; var dashboard = scope.dashboard;
var legendSideLastValue = null;
scope.$on('refresh',function() { scope.$on('refresh',function() {
if (scope.otherPanelInFullscreenMode()) { return; } if (scope.otherPanelInFullscreenMode()) { return; }
...@@ -56,7 +57,7 @@ function (angular, $, kbn, moment, _) { ...@@ -56,7 +57,7 @@ function (angular, $, kbn, moment, _) {
height = height - 32; // subtract panel title bar height = height - 32; // subtract panel title bar
if (scope.panel.legend.show) { if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
height = height - 21; // subtract one line legend height = height - 21; // subtract one line legend
} }
...@@ -136,6 +137,7 @@ function (angular, $, kbn, moment, _) { ...@@ -136,6 +137,7 @@ function (angular, $, kbn, moment, _) {
yaxes: [], yaxes: [],
xaxis: {}, xaxis: {},
grid: { grid: {
minBorderMargin: 0,
markings: [], markings: [],
backgroundColor: null, backgroundColor: null,
borderWidth: 0, borderWidth: 0,
...@@ -162,9 +164,30 @@ function (angular, $, kbn, moment, _) { ...@@ -162,9 +164,30 @@ function (angular, $, kbn, moment, _) {
addAnnotations(options); addAnnotations(options);
configureAxisOptions(data, options); configureAxisOptions(data, options);
plot = $.plot(elem, data, options); // if legend is to the right delay plot draw a few milliseconds
// so the legend width calculation can be done
if (shouldDelayDraw(panel)) {
console.log('delay');
legendSideLastValue = panel.legend.rightSide;
setTimeout(function() {
plot = $.plot(elem, data, options);
addAxisLabels();
}, 50);
}
else {
plot = $.plot(elem, data, options);
addAxisLabels();
}
}
addAxisLabels(); function shouldDelayDraw(panel) {
if (panel.legend.rightSide) {
return true;
}
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
return true;
}
return false;
} }
function addTimeAxis(options) { function addTimeAxis(options) {
...@@ -354,8 +377,10 @@ function (angular, $, kbn, moment, _) { ...@@ -354,8 +377,10 @@ function (angular, $, kbn, moment, _) {
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : ''; url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : ''; url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true'; url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : ''; url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : ''; url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true'; url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true'; url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
......
...@@ -33,46 +33,24 @@ ...@@ -33,46 +33,24 @@
<label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label> <label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur /> <input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
</div> </div>
<div class="editor-option">
<label class="small">Label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
</div>
</div> </div>
</div> </div>
<div class="editor-row"> <div class="editor-row">
<div class="section"> <div class="section">
<h5>Grid thresholds</h5> <h5>Legend styles</h5>
<div class="editor-option">
<label class="small">Level1</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option"> <div class="editor-option">
<label class="small">Level2</label> <label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
</div> </div>
<div class="editor-option"> <div class="editor-option">
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();"> <label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
</div> </div>
</div>
<div class="section">
<h5>Legend</h5>
<div class="editor-option"> <div class="editor-option">
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();"> <label class="small">Align as table</label><input type="checkbox" ng-model="panel.legend.alignAsTable" ng-checked="panel.legend.alignAsTable">
</div> </div>
<div class="editor-option"> <div class="editor-option">
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();"> <label class="small">Right side</label><input type="checkbox" ng-model="panel.legend.rightSide" ng-change="render();" ng-checked="panel.legend.rightSide">
</div> </div>
</div> </div>
...@@ -101,6 +79,29 @@ ...@@ -101,6 +79,29 @@
</div> </div>
<div class="section"> <div class="section">
<h5>Grid thresholds</h5>
<div class="editor-option">
<label class="small">Level1</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Level2</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
</div>
</div>
<div class="section">
<h5>Show Axes</h5> <h5>Show Axes</h5>
<div class="editor-option"> <div class="editor-option">
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()"> <label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
......
<span ng-show="panel.legend.show" <section class="graph-legend" ng-class="{'graph-legend-table': panel.legend.alignAsTable}">
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
ng-repeat='series in legend' <div class="graph-legend-series"
class="histogram-legend"> ng-repeat='series in legend'
<i class='icon-minus pointer' ng-class="{'pull-right': series.yaxis === 2, 'graph-legend-series-hidden': hiddenSeries[series.alias]}"
ng-style="{color: series.color}" >
bs-popover="'colorPopup.html'" <div class="graph-legend-icon">
> <i class='icon-minus pointer' ng-style="{color: series.color}" bs-popover="'colorPopup.html'">
</i> </i>
<span class='small histogram-legend-item'> </div>
<div class="graph-legend-alias small">
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}"> <a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
{{series.alias}} {{series.alias}}
</a> </a>
<span ng-if="panel.legend.values"> </div>
<span ng-show="panel.legend.current"> <div class="graph-legend-value small" ng-show="panel.legend.values && panel.legend.current">
&nbsp;&nbsp;Current: {{series.current}}&nbsp; Current: {{series.current}}
</span> </div>
<span ng-show="panel.legend.min"> <div class="graph-legend-value small" ng-show="panel.legend.values && panel.legend.min">
&nbsp;&nbsp;Min: {{series.min}}&nbsp; Min: {{series.min}}
</span> </div>
<span ng-show="panel.legend.max"> <div class="graph-legend-value small" ng-show="panel.legend.values && panel.legend.max">
&nbsp;&nbsp;Max: {{series.max}}&nbsp; Max: {{series.max}}
</span> </div>
<span ng-show="panel.legend.total"> <div class="graph-legend-value small" ng-show="panel.legend.values && panel.legend.total">
&nbsp;&nbsp;Total: {{series.total}}&nbsp; Total: {{series.total}}
</span> </div>
<span ng-show="panel.legend.avg"> <div class="graph-legend-value small" ng-show="panel.legend.values && panel.legend.avg">
&nbsp;&nbsp;Avg: {{series.avg}}&nbsp; Avg: {{series.avg}}
</span> </div>
</span> </div>
</span>
</span> </section>
<script type="text/ng-template" id="colorPopup.html"> <script type="text/ng-template" id="colorPopup.html">
<div class="histogram-legend-popover"> <div class="graph-legend-popover">
<a class="close" ng-click="dismiss();" href="">×</a> <a class="close" ng-click="dismiss();" href="">×</a>
<div class="editor-row small" style="padding-bottom: 0;"> <div class="editor-row small" style="padding-bottom: 0;">
......
...@@ -3,21 +3,47 @@ ...@@ -3,21 +3,47 @@
style="min-height:{{panel.height || row.height}}" style="min-height:{{panel.height || row.height}}"
ng-class="{'panel-fullscreen': fullscreen}"> ng-class="{'panel-fullscreen': fullscreen}">
<div style="position: relative"> <!-- <table style="width: 100%">
<tr>
<td style="width: 100%">
<div style="position: relative">
<div ng-if="datapointsWarning" class="datapoints-warning"> <div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span> <span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span> <span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div> </div>
<div grafana-graph class="pointer histogram-chart"> <div grafana-graph class="pointer histogram-chart">
</div> </div>
</div> </div>
</td>
<td>
<div ng-if="panel.legend.show" ng-include="'app/panels/graph/legend.html'">
</div>
</td>
</tr>
</table> -->
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="panel.legend" class="grafana-legend-container"> <div ng-if="datapointsWarning" class="datapoints-warning">
<div ng-include="'app/panels/graph/legend.html'"></div> <span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="pointer histogram-chart">
</div>
</div>
<div class="graph-legend-wrapper"
ng-if="panel.legend.show"
ng-include="'app/panels/graph/legend.html'">
</div>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="panel-full-edit-tabs" ng-if="editMode"> <div class="panel-full-edit-tabs" ng-if="editMode">
......
...@@ -25,7 +25,6 @@ define([ ...@@ -25,7 +25,6 @@ define([
'jquery.flot.events', 'jquery.flot.events',
'jquery.flot.selection', 'jquery.flot.selection',
'jquery.flot.time', 'jquery.flot.time',
'jquery.flot.byte',
'jquery.flot.stack', 'jquery.flot.stack',
'jquery.flot.stackpercent' 'jquery.flot.stackpercent'
], ],
......
...@@ -153,5 +153,18 @@ ...@@ -153,5 +153,18 @@
</div> </div>
</div> </div>
</div> </div>
<div class="pull-left metrics-editor-help" style="margin-top: 30px;">
<div class="span6">
<span class="pointer">
<i class="icon-question-sign"></i> alias patterns:
</span>
<ul class="hide">
<li>$s = series name</li>
<li>$g = group by</li>
<li>$[0-9] part of series name for series names seperated by dots.</li>
<ul>
</div>
</div>
</div> </div>
<div ng-include src="datasource.editorSrc"></div> <div ng-include src="datasource.editorSrc"></div>
<div class="editor-row" style="margin-top: 20px"> <div class="editor-row" style="margin-top: 30px">
<button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add query</button> <button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add query</button>
<div class="btn-group pull-right" style="margin-right: 10px;"> <div class="btn-group pull-right" style="margin-right: 10px;">
......
define([
'underscore',
],
function (_) {
'use strict';
function InfluxSeries(options) {
this.seriesList = options.seriesList;
this.alias = options.alias;
this.groupByField = options.groupByField;
}
var p = InfluxSeries.prototype;
p.getTimeSeries = function() {
var output = [];
var self = this;
var i;
_.each(self.seriesList, function(series) {
var seriesName;
var timeCol = series.columns.indexOf('time');
var valueCol = 1;
var groupByCol = -1;
if (self.groupByField) {
groupByCol = series.columns.indexOf(self.groupByField);
}
// find value column
_.each(series.columns, function(column, index) {
if (column !== 'time' && column !== 'sequence_number' && column !== self.groupByField) {
valueCol = index;
}
});
var groups = {};
if (self.groupByField) {
groups = _.groupBy(series.points, function (point) {
return point[groupByCol];
});
}
else {
groups[series.columns[valueCol]] = series.points;
}
_.each(groups, function(groupPoints, key) {
var datapoints = [];
for (i = 0; i < groupPoints.length; i++) {
var metricValue = isNaN(groupPoints[i][valueCol]) ? null : groupPoints[i][valueCol];
datapoints[i] = [metricValue, groupPoints[i][timeCol]];
}
seriesName = series.name + '.' + key;
if (self.alias) {
seriesName = self.createNameForSeries(series.name, key);
}
output.push({ target: seriesName, datapoints: datapoints });
});
});
return output;
};
p.createNameForSeries = function(seriesName, groupByColValue) {
var name = this.alias
.replace('$s', seriesName);
var segments = seriesName.split('.');
for (var i = 0; i < segments.length; i++) {
if (segments[i].length > 0) {
name = name.replace('$' + i, segments[i]);
}
}
if (this.groupByField) {
name = name.replace('$g', groupByColValue);
}
return name;
};
return InfluxSeries;
});
\ No newline at end of file
define([ define([
'angular', 'angular',
'underscore', 'underscore',
'kbn' 'kbn',
'./influxSeries'
], ],
function (angular, _, kbn) { function (angular, _, kbn, InfluxSeries) {
'use strict'; 'use strict';
var module = angular.module('kibana.services'); var module = angular.module('kibana.services');
...@@ -194,57 +195,14 @@ function (angular, _, kbn) { ...@@ -194,57 +195,14 @@ function (angular, _, kbn) {
return deferred.promise; return deferred.promise;
}; };
function handleInfluxQueryResponse(alias, groupByField, data) { function handleInfluxQueryResponse(alias, groupByField, seriesList) {
var output = []; var influxSeries = new InfluxSeries({
seriesList: seriesList,
_.each(data, function(series) { alias: alias,
var seriesName; groupByField: groupByField
var timeCol = series.columns.indexOf('time');
var valueCol = 1;
var groupByCol = -1;
if (groupByField) {
groupByCol = series.columns.indexOf(groupByField);
}
// find value column
_.each(series.columns, function(column, index) {
if (column !== 'time' && column !== 'sequence_number' && column !== groupByField) {
valueCol = index;
}
});
var groups = {};
if (groupByField) {
groups = _.groupBy(series.points, function (point) {
return point[groupByCol];
});
}
else {
groups[series.columns[valueCol]] = series.points;
}
_.each(groups, function(groupPoints, key) {
var datapoints = [];
for (var i = 0; i < groupPoints.length; i++) {
var metricValue = isNaN(groupPoints[i][valueCol]) ? null : groupPoints[i][valueCol];
datapoints[i] = [metricValue, groupPoints[i][timeCol]];
}
seriesName = alias ? alias : (series.name + '.' + key);
// if mulitple groups append key to alias
if (alias && groupByField) {
seriesName += key;
}
output.push({ target: seriesName, datapoints: datapoints });
});
}); });
return output; return influxSeries.getTimeSeries();
} }
function getTimeFilter(options) { function getTimeFilter(options) {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
@import "submenu.less"; @import "submenu.less";
@import "graph.less";
@import "bootstrap-tagsinput.less"; @import "bootstrap-tagsinput.less";
.hide-controls { .hide-controls {
...@@ -123,12 +124,6 @@ ...@@ -123,12 +124,6 @@
font-size: 12px; font-size: 12px;
} }
.hidden-series {
a {
color: darken(@linkColor, 45%);
}
}
.panel-fullscreen { .panel-fullscreen {
z-index: 100; z-index: 100;
display: block !important; display: block !important;
...@@ -154,57 +149,10 @@ ...@@ -154,57 +149,10 @@
right: -10000px; right: -10000px;
} }
// Graphite Graph Legends
.grafana-legend-container {
margin: 0 15px;
text-align: left;
position: relative;
top: 2px;
}
.grafana-legend-container .popover-content {
padding: 0;
}
.histogram-legend {
display:inline-block;
padding: 0 4px;
i {
position: relative;
top: 2px;
}
}
.histogram-legend-item {
display:inline-block;
}
.histogram-chart { .histogram-chart {
position:relative; position:relative;
} }
.histogram-legend-popover {
width: 200px;
label {
display: inline-block;
}
.btn {
padding: 1px 3px;
margin-right: 0px;
line-height: initial;
}
.close {
margin-right: 5px;
color: @linkColor;
opacity: 0.7;
text-shadow: none;
}
.editor-row {
padding: 5px;
}
}
.panel-full-edit-tabs { .panel-full-edit-tabs {
margin-top: 10px; margin-top: 10px;
min-height: 250px; min-height: 250px;
...@@ -520,3 +468,10 @@ select.grafana-target-segment-input { ...@@ -520,3 +468,10 @@ select.grafana-target-segment-input {
padding-top: 15px; padding-top: 15px;
text-align: left; text-align: left;
} }
.metrics-editor-help:hover {
.hide {
display: block;
}
}
\ No newline at end of file
.graph-legend {
margin: 0 20px;
text-align: left;
position: relative;
top: 2px;
.popover-content {
padding: 0;
}
}
.graph-legend-icon {
position: relative;
top: 2px;
}
.graph-legend-series,
.graph-legend-icon,
.graph-legend-alias,
.graph-legend-value {
display: inline-block;
white-space: nowrap;
}
.graph-legend-series {
padding-left: 10px;
}
.graph-legend-value {
padding-left: 6px;
}
.graph-legend-table {
display: table;
.graph-legend-series {
display: table-row;
padding-left: 0;
&.pull-right {
float: none;
.graph-legend-alias::after {
content: 'y\00B2';
}
}
}
.graph-legend-alias {
display: table-cell;
white-space: nowrap;
}
.graph-legend-icon {
display: table-cell;
white-space: nowrap;
padding: 0 4px;
}
.graph-legend-value {
display: table-cell;
white-space: nowrap;
padding-left: 15px;
}
}
.graph-legend-rightside {
&.graph-wrapper {
display: table;
width: 100%;
}
.graph-canvas-wrapper {
display: table-cell;
width: 100%;
position: relative;
}
.graph-legend-wrapper {
display: table-cell;
vertical-align: top;
position: relative;
left: -4px;
}
.graph-legend {
margin: 0;
}
.graph-legend-series {
display: block;
padding-left: 0px;
}
.graph-legend-table .graph-legend-series {
display: table-row;
}
}
.graph-legend-series-hidden {
a {
color: darken(@linkColor, 45%);
}
}
.graph-legend-popover {
width: 200px;
label {
display: inline-block;
}
.btn {
padding: 1px 3px;
margin-right: 0px;
line-height: initial;
}
.close {
margin-right: 5px;
color: @linkColor;
opacity: 0.7;
text-shadow: none;
}
.editor-row {
padding: 5px;
}
}
define([
'services/influxdb/influxSeries'
], function(InfluxSeries) {
'use strict';
describe('when generating timeseries from influxdb response', function() {
describe('given two series', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'mean', 'sequence_number'],
name: 'prod.server1.cpu',
points: [[1402596000, 10, 1], [1402596001, 12, 2]]
},
{
columns: ['time', 'mean', 'sequence_number'],
name: 'prod.server2.cpu',
points: [[1402596000, 15, 1], [1402596001, 16, 2]]
}
]
});
var result = series.getTimeSeries();
it('should generate two time series', function() {
expect(result.length).to.be(2);
expect(result[0].target).to.be('prod.server1.cpu.mean');
expect(result[0].datapoints[0][0]).to.be(10);
expect(result[0].datapoints[0][1]).to.be(1402596000);
expect(result[0].datapoints[1][0]).to.be(12);
expect(result[0].datapoints[1][1]).to.be(1402596001);
expect(result[1].target).to.be('prod.server2.cpu.mean');
expect(result[1].datapoints[0][0]).to.be(15);
expect(result[1].datapoints[0][1]).to.be(1402596000);
expect(result[1].datapoints[1][0]).to.be(16);
expect(result[1].datapoints[1][1]).to.be(1402596001);
});
});
describe('given an alias format', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'mean', 'sequence_number'],
name: 'prod.server1.cpu',
points: [[1402596000, 10, 1], [1402596001, 12, 2]]
}
],
alias: '$s.testing'
});
var result = series.getTimeSeries();
it('should generate correct series name', function() {
expect(result[0].target).to.be('prod.server1.cpu.testing');
});
});
describe('given an alias format with segment numbers', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'mean', 'sequence_number'],
name: 'prod.server1.cpu',
points: [[1402596000, 10, 1], [1402596001, 12, 2]]
}
],
alias: '$1.mean'
});
var result = series.getTimeSeries();
it('should generate correct series name', function() {
expect(result[0].target).to.be('server1.mean');
});
});
describe('given an alias format with group by field', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'mean', 'host'],
name: 'prod.cpu',
points: [[1402596000, 10, 'A']]
}
],
groupByField: 'host',
alias: '$g.$1'
});
var result = series.getTimeSeries();
it('should generate correct series name', function() {
expect(result[0].target).to.be('A.cpu');
});
});
describe('given group by column', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'mean', 'host'],
name: 'prod.cpu',
points: [
[1402596000, 10, 'A'],
[1402596001, 11, 'A'],
[1402596000, 5, 'B'],
[1402596001, 6, 'B'],
]
}
],
groupByField: 'host'
});
var result = series.getTimeSeries();
it('should generate two time series', function() {
expect(result.length).to.be(2);
expect(result[0].target).to.be('prod.cpu.A');
expect(result[0].datapoints[0][0]).to.be(10);
expect(result[0].datapoints[0][1]).to.be(1402596000);
expect(result[0].datapoints[1][0]).to.be(11);
expect(result[0].datapoints[1][1]).to.be(1402596001);
expect(result[1].target).to.be('prod.cpu.B');
expect(result[1].datapoints[0][0]).to.be(5);
expect(result[1].datapoints[0][1]).to.be(1402596000);
expect(result[1].datapoints[1][0]).to.be(6);
expect(result[1].datapoints[1][1]).to.be(1402596001);
});
});
});
});
...@@ -42,7 +42,6 @@ require.config({ ...@@ -42,7 +42,6 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack', 'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.byte': '../vendor/jquery/jquery.flot.byte',
modernizr: '../vendor/modernizr-2.6.1', modernizr: '../vendor/modernizr-2.6.1',
}, },
...@@ -75,7 +74,6 @@ require.config({ ...@@ -75,7 +74,6 @@ require.config({
'jquery-ui': ['jquery'], 'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'], 'jquery.flot': ['jquery'],
'jquery.flot.byte': ['jquery', 'jquery.flot'],
'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'], 'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'], 'jquery.flot.selection':['jquery', 'jquery.flot'],
...@@ -96,7 +94,6 @@ require.config({ ...@@ -96,7 +94,6 @@ require.config({
'bootstrap-tagsinput': ['jquery'], 'bootstrap-tagsinput': ['jquery'],
timepicker: ['jquery', 'bootstrap'], timepicker: ['jquery', 'bootstrap'],
datepicker: ['jquery', 'bootstrap'], datepicker: ['jquery', 'bootstrap'],
} }
...@@ -128,8 +125,10 @@ require([ ...@@ -128,8 +125,10 @@ require([
'specs/gfunc-specs', 'specs/gfunc-specs',
'specs/filterSrv-specs', 'specs/filterSrv-specs',
'specs/kbn-format-specs', 'specs/kbn-format-specs',
'specs/influxSeries-specs'
], function () { ], function () {
window.__karma__.start(); window.__karma__.start();
}); });
}); });
(function ($) {
"use strict";
var options = {};
//Round to nearby lower multiple of base
function floorInBase(n, base) {
return base * Math.floor(n / base);
}
function init(plot) {
plot.hooks.processDatapoints.push(function (plot) {
$.each(plot.getAxes(), function(axisName, axis) {
var opts = axis.options;
if (opts.mode === "byte" || opts.mode === "byteRate") {
axis.tickGenerator = function (axis) {
var returnTicks = [],
tickSize = 2,
delta = axis.delta,
steps = 0,
tickMin = 0,
tickVal,
tickCount = 0;
//Set the reference for the formatter
if (opts.mode === "byteRate") {
axis.rate = true;
}
//Enforce maximum tick Decimals
if (typeof opts.tickDecimals === "number") {
axis.tickDecimals = opts.tickDecimals;
} else {
axis.tickDecimals = 2;
}
//Count the steps
while (Math.abs(delta) >= 1024) {
steps++;
delta /= 1024;
}
//Set the tick size relative to the remaining delta
while (tickSize <= 1024) {
if (delta <= tickSize) {
break;
}
tickSize *= 2;
}
//Tell flot the tickSize we've calculated
if (typeof opts.minTickSize !== "undefined" && tickSize < opts.minTickSize) {
axis.tickSize = opts.minTickSize;
} else {
axis.tickSize = tickSize * Math.pow(1024,steps);
}
//Calculate the new ticks
tickMin = floorInBase(axis.min, axis.tickSize);
do {
tickVal = tickMin + (tickCount++) * axis.tickSize;
returnTicks.push(tickVal);
} while (tickVal < axis.max);
return returnTicks;
};
axis.tickFormatter = function(size, axis) {
var ext, steps = 0;
while (Math.abs(size) >= 1024) {
steps++;
size /= 1024;
}
switch (steps) {
case 0: ext = " B"; break;
case 1: ext = " KB"; break;
case 2: ext = " MB"; break;
case 3: ext = " GB"; break;
case 4: ext = " TB"; break;
case 5: ext = " PB"; break;
case 6: ext = " EB"; break;
case 7: ext = " ZB"; break;
case 8: ext = " YB"; break;
}
if (typeof axis.rate !== "undefined") {
ext += "/s";
}
return (size.toFixed(axis.tickDecimals) + ext);
};
}
});
});
}
$.plot.plugins.push({
init: init,
options: options,
name: "byte",
version: "0.1"
});
})(jQuery);
\ No newline at end of file
/* Flot plugin for stacking data sets rather than overlyaing them. /* Flot plugin for stacking data sets rather than overlyaing them.
Copyright (c) 2007-2013 IOLA and Ole Laursen. Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license. Licensed under the MIT license.
The plugin assumes the data is sorted on x (or y if stacking horizontally). The plugin assumes the data is sorted on x (or y if stacking horizontally).
...@@ -14,16 +14,16 @@ Two or more series are stacked when their "stack" attribute is set to the same ...@@ -14,16 +14,16 @@ Two or more series are stacked when their "stack" attribute is set to the same
key (which can be any number or string or just "true"). To specify the default key (which can be any number or string or just "true"). To specify the default
stack, you can set the stack option like this: stack, you can set the stack option like this:
series: { series: {
stack: null/false, true, or a key (number/string) stack: null/false, true, or a key (number/string)
} }
You can also specify it for a single series, like this: You can also specify it for a single series, like this:
$.plot( $("#placeholder"), [{ $.plot( $("#placeholder"), [{
data: [ ... ], data: [ ... ],
stack: true stack: true
}]) }])
The stacking order is determined by the order of the data series in the array The stacking order is determined by the order of the data series in the array
(later series end up on top of the previous). (later series end up on top of the previous).
...@@ -39,21 +39,21 @@ charts or filled areas). ...@@ -39,21 +39,21 @@ charts or filled areas).
var options = { var options = {
series: { stack: null } // or number/string series: { stack: null } // or number/string
}; };
function init(plot) { function init(plot) {
function findMatchingSeries(s, allseries) { function findMatchingSeries(s, allseries) {
var res = null; var res = null;
for (var i = 0; i < allseries.length; ++i) { for (var i = 0; i < allseries.length; ++i) {
if (s == allseries[i]) if (s == allseries[i])
break; break;
if (allseries[i].stack == s.stack) if (allseries[i].stack == s.stack)
res = allseries[i]; res = allseries[i];
} }
return res; return res;
} }
function stackData(plot, s, datapoints) { function stackData(plot, s, datapoints) {
if (s.stack == null || s.stack === false) if (s.stack == null || s.stack === false)
return; return;
...@@ -118,7 +118,7 @@ charts or filled areas). ...@@ -118,7 +118,7 @@ charts or filled areas).
newpoints[l + accumulateOffset] += qy; newpoints[l + accumulateOffset] += qy;
bottom = qy; bottom = qy;
i += ps; i += ps;
j += otherps; j += otherps;
} }
...@@ -131,7 +131,7 @@ charts or filled areas). ...@@ -131,7 +131,7 @@ charts or filled areas).
newpoints.push(intery + qy); newpoints.push(intery + qy);
for (m = 2; m < ps; ++m) for (m = 2; m < ps; ++m)
newpoints.push(points[i + m]); newpoints.push(points[i + m]);
bottom = qy; bottom = qy;
} }
j += otherps; j += otherps;
...@@ -142,22 +142,22 @@ charts or filled areas). ...@@ -142,22 +142,22 @@ charts or filled areas).
i += ps; i += ps;
continue; continue;
} }
for (m = 0; m < ps; ++m) for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]); newpoints.push(points[i + m]);
// we might be able to interpolate a point below, // we might be able to interpolate a point below,
// this can give us a better y // this can give us a better y
if (withlines && j > 0 && otherpoints[j - otherps] != null) if (withlines && j > 0 && otherpoints[j - otherps] != null)
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
newpoints[l + accumulateOffset] += bottom; newpoints[l + accumulateOffset] += bottom;
i += ps; i += ps;
} }
fromgap = false; fromgap = false;
if (l != newpoints.length && withbottom) if (l != newpoints.length && withbottom)
newpoints[l + 2] += bottom; newpoints[l + 2] += bottom;
} }
...@@ -175,10 +175,10 @@ charts or filled areas). ...@@ -175,10 +175,10 @@ charts or filled areas).
datapoints.points = newpoints; datapoints.points = newpoints;
} }
plot.hooks.processDatapoints.push(stackData); plot.hooks.processDatapoints.push(stackData);
} }
$.plot.plugins.push({ $.plot.plugins.push({
init: init, init: init,
options: options, options: options,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment