Commit 9f87d8d3 by Torkel Ödegaard

Merge branch 'develop-graph-legend' into develop

parents f78f86d0 35a7109a
......@@ -52,6 +52,8 @@ import {gfPageDirective} from './components/gf_page';
import {orgSwitcher} from './components/org_switcher';
import {profiler} from './profiler';
import {registerAngularDirectives} from './angular_wrappers';
import {updateLegendValues} from './time_series2';
import TimeSeries from './time_series2';
import {searchResultsDirective} from './components/search/search_results';
import {manageDashboardsDirective} from './components/manage_dashboards/manage_dashboards';
......@@ -86,6 +88,8 @@ export {
geminiScrollbar,
gfPageDirective,
orgSwitcher,
searchResultsDirective,
manageDashboardsDirective
manageDashboardsDirective,
TimeSeries,
updateLegendValues,
searchResultsDirective
};
import kbn from 'app/core/utils/kbn';
import {getFlotTickDecimals} from 'app/core/utils/ticks';
import _ from 'lodash';
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
......@@ -16,6 +17,48 @@ function translateFillOption(fill) {
return fill === 0 ? 0.001 : fill/10;
}
/**
* Calculate decimals for legend and update values for each series.
* @param data series data
* @param panel
*/
export function updateLegendValues(data: TimeSeries[], panel) {
for (let i = 0; i < data.length; i++) {
let series = data[i];
let yaxes = panel.yaxes;
let axis = yaxes[series.yaxis - 1];
let {tickDecimals, scaledDecimals} = getFlotTickDecimals(data, axis);
let formater = kbn.valueFormats[panel.yaxes[series.yaxis - 1].format];
// decimal override
if (_.isNumber(panel.decimals)) {
series.updateLegendValues(formater, panel.decimals, null);
} else {
// auto decimals
// legend and tooltip gets one more decimal precision
// than graph legend ticks
tickDecimals = (tickDecimals || -1) + 1;
series.updateLegendValues(formater, tickDecimals, scaledDecimals + 2);
}
}
}
export function getDataMinMax(data: TimeSeries[]) {
let datamin = null;
let datamax = null;
for (let series of data) {
if (datamax === null || datamax < series.stats.max) {
datamax = series.stats.max;
}
if (datamin === null || datamin > series.stats.min) {
datamin = series.stats.min;
}
}
return {datamin, datamax};
}
export default class TimeSeries {
datapoints: any;
id: string;
......
import {getDataMinMax} from 'app/core/time_series2';
/**
* Calculate tick step.
* Implementation from d3-array (ticks.js)
......@@ -32,6 +34,7 @@ export function getScaledDecimals(decimals, tick_size) {
/**
* Calculate tick size based on min and max values, number of ticks and precision.
* Implementation from Flot.
* @param min Axis minimum
* @param max Axis maximum
* @param noTicks Number of ticks
......@@ -65,3 +68,91 @@ export function getFlotTickSize(min: number, max: number, noTicks: number, tickD
return size;
}
/**
* Calculate axis range (min and max).
* Implementation from Flot.
*/
export function getFlotRange(panelMin, panelMax, datamin, datamax) {
const autoscaleMargin = 0.02;
let min = +(panelMin != null ? panelMin : datamin);
let max = +(panelMax != null ? panelMax : datamax);
let delta = max - min;
if (delta === 0.0) {
// Grafana fix: wide Y min and max using increased wideFactor
// when all series values are the same
var wideFactor = 0.25;
var widen = Math.abs(max === 0 ? 1 : max * wideFactor);
if (panelMin === null) {
min -= widen;
}
// always widen max if we couldn't widen min to ensure we
// don't fall into min == max which doesn't work
if (panelMax == null || panelMin != null) {
max += widen;
}
} else {
// consider autoscaling
var margin = autoscaleMargin;
if (margin != null) {
if (panelMin == null) {
min -= delta * margin;
// make sure we don't go below zero if all values
// are positive
if (min < 0 && datamin != null && datamin >= 0) {
min = 0;
}
}
if (panelMax == null) {
max += delta * margin;
if (max > 0 && datamax != null && datamax <= 0) {
max = 0;
}
}
}
}
return {min, max};
}
/**
* Calculate tick decimals.
* Implementation from Flot.
*/
export function getFlotTickDecimals(data, axis) {
let {datamin, datamax} = getDataMinMax(data);
let {min, max} = getFlotRange(axis.min, axis.max, datamin, datamax);
let noTicks = 3;
let tickDecimals, maxDec;
let delta = (max - min) / noTicks;
let dec = -Math.floor(Math.log(delta) / Math.LN10);
let magn = Math.pow(10, -dec);
// norm is between 1.0 and 10.0
let norm = delta / magn;
let size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
// special case for 2.5, requires an extra decimal
if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
size = 2.5;
++dec;
}
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
// grafana addition
const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
return {tickDecimals, scaledDecimals};
}
......@@ -20,7 +20,7 @@ var panelTemplate = `
</div>
<div class="panel-content">
<ng-transclude></ng-transclude>
<ng-transclude class="panel-height-helper"></ng-transclude>
</div>
</div>
......
define([
'angular',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import PerfectScrollbar from 'perfect-scrollbar';
import {updateLegendValues} from 'app/core/core';
var module = angular.module('grafana.directives');
var module = angular.module('grafana.directives');
module.directive('graphLegend', function(popoverSrv, $timeout) {
module.directive('graphLegend', function(popoverSrv, $timeout) {
return {
link: function(scope, elem) {
var $container = $('<section class="graph-legend"></section>');
var firstRender = true;
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var data;
var seriesList;
var i;
var legendScrollbar;
ctrl.events.on('render', function() {
scope.$on("$destroy", function() {
if (!legendScrollbar) {
legendScrollbar.destroy();
}
});
ctrl.events.on('render-legend', () => {
data = ctrl.seriesList;
if (data) {
render();
}
ctrl.events.emit('legend-rendering-complete');
});
function updateLegendDecimals() {
updateLegendValues(data, panel);
}
function getSeriesIndexForElement(el) {
return el.parents('[data-series-index]').data('series-index');
}
......@@ -65,9 +74,9 @@ function (angular, _, $) {
var el = $(e.currentTarget);
var index = getSeriesIndexForElement(el);
var seriesInfo = seriesList[index];
var scrollPosition = $($container.children('tbody')).scrollTop();
var scrollPosition = $(elem.children('tbody')).scrollTop();
ctrl.toggleSeries(seriesInfo, e);
$($container.children('tbody')).scrollTop(scrollPosition);
$(elem.children('tbody')).scrollTop(scrollPosition);
}
function sortLegend(e) {
......@@ -109,22 +118,21 @@ function (angular, _, $) {
}
if (firstRender) {
elem.append($container);
$container.on('click', '.graph-legend-icon', openColorSelector);
$container.on('click', '.graph-legend-alias', toggleSeries);
$container.on('click', 'th', sortLegend);
elem.on('click', '.graph-legend-icon', openColorSelector);
elem.on('click', '.graph-legend-alias', toggleSeries);
elem.on('click', 'th', sortLegend);
firstRender = false;
}
seriesList = data;
$container.empty();
elem.empty();
// Set min-width if side style and there is a value, otherwise remove the CSS propery
var width = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + "px" : "";
$container.css("min-width", width);
elem.css("min-width", width);
$container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
var tableHeaderElem;
if (panel.legend.alignAsTable) {
......@@ -150,9 +158,20 @@ function (angular, _, $) {
}
}
var seriesShown = 0;
var seriesElements = [];
// render first time for getting proper legend height
if (!panel.legend.rightSide) {
renderLegendElement(tableHeaderElem);
updateLegendDecimals();
elem.empty();
} else {
updateLegendDecimals();
}
renderLegendElement(tableHeaderElem);
}
function renderSeriesLegendElements() {
let seriesElements = [];
for (i = 0; i < seriesList.length; i++) {
var series = seriesList[i];
......@@ -187,29 +206,28 @@ function (angular, _, $) {
html += '</div>';
seriesElements.push($(html));
seriesShown++;
}
if (panel.legend.alignAsTable) {
var maxHeight = ctrl.height;
if (!panel.legend.rightSide) {
maxHeight = maxHeight/2;
return seriesElements;
}
var topPadding = 6;
function renderLegendElement(tableHeaderElem) {
var seriesElements = renderSeriesLegendElements();
if (panel.legend.alignAsTable) {
var tbodyElem = $('<tbody></tbody>');
tbodyElem.css("max-height", maxHeight - topPadding);
tbodyElem.append(tableHeaderElem);
tbodyElem.append(seriesElements);
$container.append(tbodyElem);
elem.append(tbodyElem);
} else {
elem.append(seriesElements);
if (!legendScrollbar) {
legendScrollbar = new PerfectScrollbar(elem[0]);
} else {
$container.append(seriesElements);
legendScrollbar.update();
}
}
}
}
};
});
});
......@@ -87,6 +87,8 @@ describe('grafanaGraph', function() {
$.plot = ctx.plotSpy = sinon.spy();
ctrl.events.emit('render', ctx.data);
ctrl.events.emit('render-legend');
ctrl.events.emit('legend-rendering-complete');
ctx.plotData = ctx.plotSpy.getCall(0).args[1];
ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
}));
......
var template = `
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div class="datapoints-warning" ng-if="ctrl.dataWarning">
<span class="small" bs-tooltip="ctrl.dataWarning.tip">{{ctrl.dataWarning.title}}</span>
</div>
<div grafana-graph class="histogram-chart" ng-dblclick="ctrl.zoomOut()">
</div>
<div class="graph-panel" ng-class="{'graph-panel--legend-right': ctrl.panel.legend.rightSide}">
<div class="graph-panel__chart" grafana-graph ng-dblclick="ctrl.zoomOut()">
</div>
<div class="graph-legend-wrapper" graph-legend></div>
</div>
<div class="clearfix"></div>
<div class="graph-legend" graph-legend></div>
</div>
`;
export default template;
.graph-canvas-wrapper {
position: relative;
cursor: crosshair;
.graph-panel {
display: flex;
flex-direction: column;
height: 100%;
&--legend-right {
flex-direction: row;
.graph-legend {
flex: 0 1 10px;
max-height: 100%;
}
.graph-legend-series {
display: block;
padding-left: 0px;
}
.graph-legend-table .graph-legend-series {
display: table-row;
}
}
}
.histogram-chart {
.graph-panel__chart {
position: relative;
cursor: crosshair;
flex-grow: 1;
}
.datapoints-warning {
......@@ -22,11 +43,12 @@
}
.graph-legend {
@include clearfix();
flex: 0 1 auto;
max-height: 30%;
margin: 0 $spacer;
text-align: center;
width: calc(100% - $spacer);
padding-top: 6px;
position: relative;
.popover-content {
padding: 0;
......@@ -89,7 +111,9 @@
display: block;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
padding-bottom: 1px;
padding-right: 5px;
}
.graph-legend-series {
......@@ -160,39 +184,6 @@
}
}
.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 0 0 1rem;
}
.graph-legend-series {
display: block;
padding-left: 0px;
}
.graph-legend-table .graph-legend-series {
display: table-row;
}
}
.graph-legend-series-hidden {
.graph-legend-value,
......
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