Merge branch 'graph-legend-v5' of https://github.com/alexanderzobnin/grafana into alexanderzobnin-graph-legend-v5

This commit is contained in:
Torkel Ödegaard 2017-12-07 17:09:46 +01:00
commit a7897b945b
8 changed files with 514 additions and 355 deletions

View File

@ -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';
export {
@ -85,5 +87,7 @@ export {
geminiScrollbar,
gfPageDirective,
orgSwitcher,
TimeSeries,
updateLegendValues,
searchResultsDirective
};

View File

@ -1,4 +1,5 @@
import kbn from 'app/core/utils/kbn';
import {getFlotTickDecimals} from 'app/core/utils/ticks';
import _ from 'lodash';
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
@ -16,6 +17,43 @@ 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
* @param height Graph height
*/
export function updateLegendValues(data: TimeSeries[], panel, height) {
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, height);
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[]) {
const datamin = _.minBy(data, (series) => {
return series.stats.min;
}).stats.min;
const datamax = _.maxBy(data, (series: TimeSeries) => {
return series.stats.max;
}).stats.max;
return {datamin, datamax};
}
export default class TimeSeries {
datapoints: any;
id: string;

View File

@ -1,3 +1,5 @@
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,107 @@ 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};
}
/**
* Estimate number of ticks for Y axis.
* Implementation from Flot.
*/
export function getFlotNumberOfTicks(height, ticks?) {
let noTicks;
if (typeof ticks === "number" && ticks > 0) {
noTicks = ticks;
} else {
// heuristic based on the model a*sqrt(x) fitted to
// some data points that seemed reasonable
noTicks = 0.3 * Math.sqrt(height);
}
return noTicks;
}
/**
* Calculate tick decimals.
* Implementation from Flot.
*/
export function getFlotTickDecimals(data, axis, height) {
let {datamin, datamax} = getDataMinMax(data);
let {min, max} = getFlotRange(axis.min, axis.max, datamin, datamax);
let noTicks = getFlotNumberOfTicks(height);
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};
}

View File

@ -22,7 +22,7 @@ import {EventManager} from 'app/features/annotations/all';
import {convertValuesToHistogram, getSeriesValues} from './histogram';
/** @ngInject **/
function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
function graphDirective(timeSrv, popoverSrv, contextSrv) {
return {
restrict: 'A',
template: '',
@ -34,8 +34,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
var data;
var plot;
var sortedSeries;
var legendSideLastValue = null;
var rootScope = scope.$root;
var panelWidth = 0;
var eventManager = new EventManager(ctrl);
var thresholdManager = new ThresholdManager(ctrl);
@ -53,17 +51,28 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
}
});
ctrl.events.on('render', function(renderData) {
/**
* Split graph rendering into two parts.
* First, calculate series stats in buildFlotPairs() function. Then legend rendering started
* (see ctrl.events.on('render') in legend.ts).
* When legend is rendered it emits 'legend-rendering-complete' and graph rendered.
*/
ctrl.events.on('render', (renderData) => {
data = renderData || data;
if (!data) {
return;
}
annotations = ctrl.annotations || [];
buildFlotPairs(data);
ctrl.events.emit('render-legend');
});
ctrl.events.on('legend-rendering-complete', () => {
render_panel();
});
// global events
appEvents.on('graph-hover', function(evt) {
appEvents.on('graph-hover', (evt) => {
// ignore other graph hover events if shared tooltip is disabled
if (!dashboard.sharedTooltipModeEnabled()) {
return;
@ -77,25 +86,32 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
tooltip.show(evt.pos);
}, scope);
appEvents.on('graph-hover-clear', function(event, info) {
appEvents.on('graph-hover-clear', (event, info) => {
if (plot) {
tooltip.clear(plot);
}
}, scope);
function getLegendHeight(panelHeight) {
const LEGEND_PADDING = 23;
if (!panel.legend.show || panel.legend.rightSide) {
return 0;
}
if (panel.legend.alignAsTable) {
var legendSeries = _.filter(data, function(series) {
return series.hideFromLegend(panel.legend) === false;
});
var total = 23 + (21 * legendSeries.length);
return Math.min(total, Math.floor(panelHeight/2));
} else {
return 26;
let legendHeight = getLegendContainerHeight() + LEGEND_PADDING;
return Math.min(legendHeight, Math.floor(panelHeight/2));
}
function getLegendContainerHeight() {
try {
let graphWrapperElem = elem.parent().parent();
let legendElem = graphWrapperElem.find('.graph-legend-wrapper');
let legendHeight = legendElem.height();
return legendHeight;
} catch (e) {
console.log(e);
return 0;
}
}
@ -126,27 +142,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
}
function drawHook(plot) {
// Update legend values
var yaxis = plot.getYAxes();
for (var i = 0; i < data.length; i++) {
var series = data[i];
var axis = yaxis[series.yaxis - 1];
var 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
var tickDecimals = (axis.tickDecimals || -1) + 1;
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
}
if (!rootScope.$$phase) { scope.$digest(); }
}
// add left axis labels
if (panel.yaxes[0].label && panel.yaxes[0].show) {
$("<div class='axisLabel left-yaxis-label flot-temp-elem'></div>").text(panel.yaxes[0].label).appendTo(elem);
@ -207,7 +202,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
// Function for rendering panel
function render_panel() {
panelWidth = elem.width();
if (shouldAbortRender()) {
return;
}
@ -218,10 +212,99 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
// un-check dashes if lines are unchecked
panel.dashes = panel.lines ? panel.dashes : false;
var stack = panel.stack ? true : null;
// Populate element
var options: any = {
let options: any = buildFlotOptions(panel);
prepareXAxis(options, panel);
configureYAxisOptions(data, options);
thresholdManager.addFlotOptions(options, panel);
eventManager.addFlotEvents(annotations, options);
sortedSeries = sortSeries(data, panel);
callPlot(options, true);
}
function buildFlotPairs(data) {
for (let i = 0; i < data.length; i++) {
let series = data[i];
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
// if hidden remove points and disable stack
if (ctrl.hiddenSeries[series.alias]) {
series.data = [];
series.stack = false;
}
}
}
function prepareXAxis(options, panel) {
switch (panel.xaxis.mode) {
case 'series': {
options.series.bars.barWidth = 0.7;
options.series.bars.align = 'center';
for (let i = 0; i < data.length; i++) {
let series = data[i];
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]];
}
addXSeriesAxis(options);
break;
}
case 'histogram': {
let bucketSize: number;
let values = getSeriesValues(data);
if (data.length && values.length) {
let histMin = _.min(_.map(data, s => s.stats.min));
let histMax = _.max(_.map(data, s => s.stats.max));
let ticks = panel.xaxis.buckets || panelWidth / 50;
bucketSize = tickStep(histMin, histMax, ticks);
let histogram = convertValuesToHistogram(values, bucketSize);
data[0].data = histogram;
options.series.bars.barWidth = bucketSize * 0.8;
} else {
bucketSize = 0;
}
addXHistogramAxis(options, bucketSize);
break;
}
case 'table': {
options.series.bars.barWidth = 0.7;
options.series.bars.align = 'center';
addXTableAxis(options);
break;
}
default: {
options.series.bars.barWidth = getMinTimeStepOfSeries(data) / 1.5;
addTimeAxis(options);
break;
}
}
}
function callPlot(options, incrementRenderCounter) {
try {
plot = $.plot(elem, sortedSeries, options);
if (ctrl.renderError) {
delete ctrl.error;
delete ctrl.inspector;
}
} catch (e) {
console.log('flotcharts error', e);
ctrl.error = e.message || "Render Error";
ctrl.renderError = true;
ctrl.inspector = {error: e};
}
if (incrementRenderCounter) {
ctrl.renderingCompleted();
}
}
function buildFlotOptions(panel) {
const stack = panel.stack ? true : null;
let options = {
hooks: {
draw: [drawHook],
processOffset: [processOffsetHook],
@ -278,96 +361,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
mode: 'x'
}
};
for (let i = 0; i < data.length; i++) {
let series = data[i];
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
// if hidden remove points and disable stack
if (ctrl.hiddenSeries[series.alias]) {
series.data = [];
series.stack = false;
}
}
switch (panel.xaxis.mode) {
case 'series': {
options.series.bars.barWidth = 0.7;
options.series.bars.align = 'center';
for (let i = 0; i < data.length; i++) {
let series = data[i];
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]];
}
addXSeriesAxis(options);
break;
}
case 'histogram': {
let bucketSize: number;
let values = getSeriesValues(data);
if (data.length && values.length) {
let histMin = _.min(_.map(data, s => s.stats.min));
let histMax = _.max(_.map(data, s => s.stats.max));
let ticks = panel.xaxis.buckets || panelWidth / 50;
bucketSize = tickStep(histMin, histMax, ticks);
let histogram = convertValuesToHistogram(values, bucketSize);
data[0].data = histogram;
options.series.bars.barWidth = bucketSize * 0.8;
} else {
bucketSize = 0;
}
addXHistogramAxis(options, bucketSize);
break;
}
case 'table': {
options.series.bars.barWidth = 0.7;
options.series.bars.align = 'center';
addXTableAxis(options);
break;
}
default: {
options.series.bars.barWidth = getMinTimeStepOfSeries(data) / 1.5;
addTimeAxis(options);
break;
}
}
thresholdManager.addFlotOptions(options, panel);
eventManager.addFlotEvents(annotations, options);
configureAxisOptions(data, options);
sortedSeries = sortSeries(data, ctrl.panel);
function callPlot(incrementRenderCounter) {
try {
plot = $.plot(elem, sortedSeries, options);
if (ctrl.renderError) {
delete ctrl.error;
delete ctrl.inspector;
}
} catch (e) {
console.log('flotcharts error', e);
ctrl.error = e.message || "Render Error";
ctrl.renderError = true;
ctrl.inspector = {error: e};
}
if (incrementRenderCounter) {
ctrl.renderingCompleted();
}
}
if (shouldDelayDraw(panel)) {
// temp fix for legends on the side, need to render twice to get dimensions right
callPlot(false);
setTimeout(function() { callPlot(true); }, 50);
legendSideLastValue = panel.legend.rightSide;
} else {
callPlot(true);
}
return options;
}
function sortSeries(series, panel) {
@ -410,16 +404,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
}
}
function shouldDelayDraw(panel) {
if (panel.legend.rightSide) {
return true;
}
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
return true;
}
return false;
}
function addTimeAxis(options) {
var ticks = panelWidth / 100;
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
@ -519,7 +503,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
};
}
function configureAxisOptions(data, options) {
function configureYAxisOptions(data, options) {
var defaults = {
position: 'left',
show: panel.yaxes[0].show,

View File

@ -1,215 +0,0 @@
define([
'angular',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
var module = angular.module('grafana.directives');
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;
ctrl.events.on('render', function() {
data = ctrl.seriesList;
if (data) {
render();
}
});
function getSeriesIndexForElement(el) {
return el.parents('[data-series-index]').data('series-index');
}
function openColorSelector(e) {
// if we clicked inside poup container ignore click
if ($(e.target).parents('.popover').length) {
return;
}
var el = $(e.currentTarget).find('.fa-minus');
var index = getSeriesIndexForElement(el);
var series = seriesList[index];
$timeout(function() {
popoverSrv.show({
element: el[0],
position: 'bottom center',
template: '<series-color-picker series="series" onToggleAxis="toggleAxis" onColorChange="colorSelected">' +
'</series-color-picker>',
openOn: 'hover',
model: {
series: series,
toggleAxis: function() {
ctrl.toggleAxis(series);
},
colorSelected: function(color) {
ctrl.changeSeriesColor(series, color);
}
},
});
});
}
function toggleSeries(e) {
var el = $(e.currentTarget);
var index = getSeriesIndexForElement(el);
var seriesInfo = seriesList[index];
var scrollPosition = $($container.children('tbody')).scrollTop();
ctrl.toggleSeries(seriesInfo, e);
$($container.children('tbody')).scrollTop(scrollPosition);
}
function sortLegend(e) {
var el = $(e.currentTarget);
var stat = el.data('stat');
if (stat !== panel.legend.sort) { panel.legend.sortDesc = null; }
// if already sort ascending, disable sorting
if (panel.legend.sortDesc === false) {
panel.legend.sort = null;
panel.legend.sortDesc = null;
ctrl.render();
return;
}
panel.legend.sortDesc = !panel.legend.sortDesc;
panel.legend.sort = stat;
ctrl.render();
}
function getTableHeaderHtml(statName) {
if (!panel.legend[statName]) { return ""; }
var html = '<th class="pointer" data-stat="' + statName + '">' + statName;
if (panel.legend.sort === statName) {
var cssClass = panel.legend.sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up' ;
html += ' <span class="' + cssClass + '"></span>';
}
return html + '</th>';
}
function render() {
if (!ctrl.panel.legend.show) {
elem.empty();
firstRender = true;
return;
}
if (firstRender) {
elem.append($container);
$container.on('click', '.graph-legend-icon', openColorSelector);
$container.on('click', '.graph-legend-alias', toggleSeries);
$container.on('click', 'th', sortLegend);
firstRender = false;
}
seriesList = data;
$container.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);
$container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
var tableHeaderElem;
if (panel.legend.alignAsTable) {
var header = '<tr>';
header += '<th colspan="2" style="text-align:left"></th>';
if (panel.legend.values) {
header += getTableHeaderHtml('min');
header += getTableHeaderHtml('max');
header += getTableHeaderHtml('avg');
header += getTableHeaderHtml('current');
header += getTableHeaderHtml('total');
}
header += '</tr>';
tableHeaderElem = $(header);
}
if (panel.legend.sort) {
seriesList = _.sortBy(seriesList, function(series) {
return series.stats[panel.legend.sort];
});
if (panel.legend.sortDesc) {
seriesList = seriesList.reverse();
}
}
var seriesShown = 0;
var seriesElements = [];
for (i = 0; i < seriesList.length; i++) {
var series = seriesList[i];
if (series.hideFromLegend(panel.legend)) {
continue;
}
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' graph-legend-series--right-y'; }
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
html += '</div>';
html += '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
if (panel.legend.values) {
var avg = series.formatValue(series.stats.avg);
var current = series.formatValue(series.stats.current);
var min = series.formatValue(series.stats.min);
var max = series.formatValue(series.stats.max);
var total = series.formatValue(series.stats.total);
if (panel.legend.min) { html += '<div class="graph-legend-value min">' + min + '</div>'; }
if (panel.legend.max) { html += '<div class="graph-legend-value max">' + max + '</div>'; }
if (panel.legend.avg) { html += '<div class="graph-legend-value avg">' + avg + '</div>'; }
if (panel.legend.current) { html += '<div class="graph-legend-value current">' + current + '</div>'; }
if (panel.legend.total) { html += '<div class="graph-legend-value total">' + total + '</div>'; }
}
html += '</div>';
seriesElements.push($(html));
seriesShown++;
}
if (panel.legend.alignAsTable) {
var maxHeight = ctrl.height;
if (!panel.legend.rightSide) {
maxHeight = maxHeight/2;
}
var topPadding = 6;
var tbodyElem = $('<tbody></tbody>');
tbodyElem.css("max-height", maxHeight - topPadding);
tbodyElem.append(tableHeaderElem);
tbodyElem.append(seriesElements);
$container.append(tbodyElem);
} else {
$container.append(seriesElements);
}
}
}
};
});
});

View File

@ -0,0 +1,238 @@
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');
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-legend', () => {
data = ctrl.seriesList;
if (data) {
render();
}
ctrl.events.emit('legend-rendering-complete');
});
function updateLegendDecimals(graphHeight) {
updateLegendValues(data, panel, graphHeight);
}
function getSeriesIndexForElement(el) {
return el.parents('[data-series-index]').data('series-index');
}
function openColorSelector(e) {
// if we clicked inside poup container ignore click
if ($(e.target).parents('.popover').length) {
return;
}
var el = $(e.currentTarget).find('.fa-minus');
var index = getSeriesIndexForElement(el);
var series = seriesList[index];
$timeout(function() {
popoverSrv.show({
element: el[0],
position: 'bottom center',
template: '<series-color-picker series="series" onToggleAxis="toggleAxis" onColorChange="colorSelected">' +
'</series-color-picker>',
openOn: 'hover',
model: {
series: series,
toggleAxis: function() {
ctrl.toggleAxis(series);
},
colorSelected: function(color) {
ctrl.changeSeriesColor(series, color);
}
},
});
});
}
function toggleSeries(e) {
var el = $(e.currentTarget);
var index = getSeriesIndexForElement(el);
var seriesInfo = seriesList[index];
var scrollPosition = $($container.children('tbody')).scrollTop();
ctrl.toggleSeries(seriesInfo, e);
$($container.children('tbody')).scrollTop(scrollPosition);
}
function sortLegend(e) {
var el = $(e.currentTarget);
var stat = el.data('stat');
if (stat !== panel.legend.sort) { panel.legend.sortDesc = null; }
// if already sort ascending, disable sorting
if (panel.legend.sortDesc === false) {
panel.legend.sort = null;
panel.legend.sortDesc = null;
ctrl.render();
return;
}
panel.legend.sortDesc = !panel.legend.sortDesc;
panel.legend.sort = stat;
ctrl.render();
}
function getTableHeaderHtml(statName) {
if (!panel.legend[statName]) { return ""; }
var html = '<th class="pointer" data-stat="' + statName + '">' + statName;
if (panel.legend.sort === statName) {
var cssClass = panel.legend.sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up' ;
html += ' <span class="' + cssClass + '"></span>';
}
return html + '</th>';
}
function render() {
if (!ctrl.panel.legend.show) {
elem.empty();
firstRender = true;
return;
}
if (firstRender) {
elem.append($container);
$container.on('click', '.graph-legend-icon', openColorSelector);
$container.on('click', '.graph-legend-alias', toggleSeries);
$container.on('click', 'th', sortLegend);
firstRender = false;
}
seriesList = data;
$container.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);
$container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
var tableHeaderElem;
if (panel.legend.alignAsTable) {
var header = '<tr>';
header += '<th colspan="2" style="text-align:left"></th>';
if (panel.legend.values) {
header += getTableHeaderHtml('min');
header += getTableHeaderHtml('max');
header += getTableHeaderHtml('avg');
header += getTableHeaderHtml('current');
header += getTableHeaderHtml('total');
}
header += '</tr>';
tableHeaderElem = $(header);
}
if (panel.legend.sort) {
seriesList = _.sortBy(seriesList, function(series) {
return series.stats[panel.legend.sort];
});
if (panel.legend.sortDesc) {
seriesList = seriesList.reverse();
}
}
// render first time for getting proper legend height
if (!panel.legend.rightSide) {
renderLegendElement(tableHeaderElem);
let graphHeight = ctrl.height - $container.height() - 23;
updateLegendDecimals(graphHeight);
$container.empty();
} else {
updateLegendDecimals(ctrl.height);
}
renderLegendElement(tableHeaderElem);
}
function renderSeriesLegendElements() {
let seriesElements = [];
for (i = 0; i < seriesList.length; i++) {
var series = seriesList[i];
if (series.hideFromLegend(panel.legend)) {
continue;
}
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' graph-legend-series--right-y'; }
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
html += '</div>';
html += '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
if (panel.legend.values) {
var avg = series.formatValue(series.stats.avg);
var current = series.formatValue(series.stats.current);
var min = series.formatValue(series.stats.min);
var max = series.formatValue(series.stats.max);
var total = series.formatValue(series.stats.total);
if (panel.legend.min) { html += '<div class="graph-legend-value min">' + min + '</div>'; }
if (panel.legend.max) { html += '<div class="graph-legend-value max">' + max + '</div>'; }
if (panel.legend.avg) { html += '<div class="graph-legend-value avg">' + avg + '</div>'; }
if (panel.legend.current) { html += '<div class="graph-legend-value current">' + current + '</div>'; }
if (panel.legend.total) { html += '<div class="graph-legend-value total">' + total + '</div>'; }
}
html += '</div>';
seriesElements.push($(html));
}
return seriesElements;
}
function renderLegendElement(tableHeaderElem) {
var seriesElements = renderSeriesLegendElements();
if (panel.legend.alignAsTable) {
var maxHeight = ctrl.height;
if (!panel.legend.rightSide) {
maxHeight = maxHeight/2;
}
var topPadding = 6;
var tbodyElem = $('<tbody></tbody>');
tbodyElem.css("max-height", maxHeight - topPadding);
tbodyElem.append(tableHeaderElem);
tbodyElem.append(seriesElements);
$container.append(tbodyElem);
} else {
var maxLegendHeight = ctrl.height / 2;
$container.css("max-height", maxLegendHeight - 6);
$container.append(seriesElements);
if (!legendScrollbar) {
legendScrollbar = new PerfectScrollbar($container[0]);
}
legendScrollbar.update();
}
}
}
};
});

View File

@ -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];
}));

View File

@ -27,6 +27,7 @@
text-align: center;
width: calc(100% - $spacer);
padding-top: 6px;
position: relative;
.popover-content {
padding: 0;