mirror of https://github.com/grafana/grafana
parent
5682520603
commit
acfde82c0a
@ -1,601 +0,0 @@ |
||||
define([ |
||||
'angular', |
||||
'jquery', |
||||
'moment', |
||||
'lodash', |
||||
'app/core/utils/kbn', |
||||
'./graph_tooltip', |
||||
'./threshold_manager', |
||||
'jquery.flot', |
||||
'jquery.flot.selection', |
||||
'jquery.flot.time', |
||||
'jquery.flot.stack', |
||||
'jquery.flot.stackpercent', |
||||
'jquery.flot.fillbelow', |
||||
'jquery.flot.crosshair', |
||||
'./jquery.flot.events', |
||||
], |
||||
function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) { |
||||
'use strict'; |
||||
|
||||
var module = angular.module('grafana.directives'); |
||||
var labelWidthCache = {}; |
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) { |
||||
return { |
||||
restrict: 'A', |
||||
template: '<div> </div>', |
||||
link: function(scope, elem) { |
||||
var ctrl = scope.ctrl; |
||||
var dashboard = ctrl.dashboard; |
||||
var panel = ctrl.panel; |
||||
var data, annotations; |
||||
var sortedSeries; |
||||
var legendSideLastValue = null; |
||||
var rootScope = scope.$root; |
||||
var panelWidth = 0; |
||||
var thresholdManager = new thresholdManExports.ThresholdManager(ctrl); |
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) { |
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) { |
||||
return; |
||||
} |
||||
|
||||
if(dashboard.sharedCrosshair) { |
||||
var plot = elem.data().plot; |
||||
if (plot) { |
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y }); |
||||
} |
||||
} |
||||
}, scope); |
||||
|
||||
rootScope.onAppEvent('clearCrosshair', function() { |
||||
var plot = elem.data().plot; |
||||
if (plot) { |
||||
plot.clearCrosshair(); |
||||
} |
||||
}, scope); |
||||
|
||||
// Receive render events
|
||||
ctrl.events.on('render', function(renderData) { |
||||
data = renderData || data; |
||||
if (!data) { |
||||
return; |
||||
} |
||||
annotations = data.annotations || annotations; |
||||
render_panel(); |
||||
}); |
||||
|
||||
function getLegendHeight(panelHeight) { |
||||
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; |
||||
} |
||||
} |
||||
|
||||
function setElementHeight() { |
||||
try { |
||||
var height = ctrl.height - getLegendHeight(ctrl.height); |
||||
elem.css('height', height + 'px'); |
||||
|
||||
return true; |
||||
} catch(e) { // IE throws errors sometimes
|
||||
console.log(e); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
function shouldAbortRender() { |
||||
if (!data) { |
||||
return true; |
||||
} |
||||
|
||||
if (!setElementHeight()) { return true; } |
||||
|
||||
if (panelWidth === 0) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
function getLabelWidth(text, elem) { |
||||
var labelWidth = labelWidthCache[text]; |
||||
|
||||
if (!labelWidth) { |
||||
labelWidth = labelWidthCache[text] = elem.width(); |
||||
} |
||||
|
||||
return labelWidth; |
||||
} |
||||
|
||||
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) { |
||||
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>") |
||||
.text(panel.yaxes[0].label) |
||||
.appendTo(elem); |
||||
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px'; |
||||
} |
||||
|
||||
// add right axis labels
|
||||
if (panel.yaxes[1].label) { |
||||
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>") |
||||
.text(panel.yaxes[1].label) |
||||
.appendTo(elem); |
||||
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px'; |
||||
} |
||||
|
||||
thresholdManager.draw(plot); |
||||
} |
||||
|
||||
function processOffsetHook(plot, gridMargin) { |
||||
var left = panel.yaxes[0]; |
||||
var right = panel.yaxes[1]; |
||||
if (left.show && left.label) { gridMargin.left = 20; } |
||||
if (right.show && right.label) { gridMargin.right = 20; } |
||||
} |
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() { |
||||
panelWidth = elem.width(); |
||||
|
||||
if (shouldAbortRender()) { |
||||
return; |
||||
} |
||||
|
||||
// give space to alert editing
|
||||
thresholdManager.prepare(elem, data); |
||||
|
||||
var stack = panel.stack ? true : null; |
||||
|
||||
// Populate element
|
||||
var options = { |
||||
hooks: { |
||||
draw: [drawHook], |
||||
processOffset: [processOffsetHook], |
||||
}, |
||||
legend: { show: false }, |
||||
series: { |
||||
stackpercent: panel.stack ? panel.percentage : false, |
||||
stack: panel.percentage ? null : stack, |
||||
lines: { |
||||
show: panel.lines, |
||||
zero: false, |
||||
fill: translateFillOption(panel.fill), |
||||
lineWidth: panel.linewidth, |
||||
steps: panel.steppedLine |
||||
}, |
||||
bars: { |
||||
show: panel.bars, |
||||
fill: 1, |
||||
barWidth: 1, |
||||
zero: false, |
||||
lineWidth: 0 |
||||
}, |
||||
points: { |
||||
show: panel.points, |
||||
fill: 1, |
||||
fillColor: false, |
||||
radius: panel.points ? panel.pointradius : 2 |
||||
}, |
||||
shadowSize: 0 |
||||
}, |
||||
yaxes: [], |
||||
xaxis: {}, |
||||
grid: { |
||||
minBorderMargin: 0, |
||||
markings: [], |
||||
backgroundColor: null, |
||||
borderWidth: 0, |
||||
hoverable: true, |
||||
color: '#c8c8c8', |
||||
margin: { left: 0, right: 0 }, |
||||
}, |
||||
selection: { |
||||
mode: "x", |
||||
color: '#666' |
||||
}, |
||||
crosshair: { |
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null |
||||
} |
||||
}; |
||||
|
||||
for (var i = 0; i < data.length; i++) { |
||||
var series = data[i]; |
||||
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode); |
||||
|
||||
if (panel.xaxis.mode === 'series') { |
||||
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]]; |
||||
} else if (panel.xaxis.mode === 'table' || |
||||
panel.xaxis.mode === 'elastic') { |
||||
series.data = []; |
||||
for (var j = 0; j < series.datapoints.length; j++) { |
||||
var dataIndex = i * series.datapoints.length + j; |
||||
series.datapoints[j]; |
||||
series.data.push([ |
||||
dataIndex + 1, |
||||
series.datapoints[j][0] |
||||
]); |
||||
} |
||||
} |
||||
|
||||
// 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'; |
||||
addXSeriesAxis(options); |
||||
break; |
||||
} |
||||
case 'table': { |
||||
options.series.bars.barWidth = 0.7; |
||||
options.series.bars.align = 'center'; |
||||
addXTableAxis(options); |
||||
break; |
||||
} |
||||
default: { |
||||
if (data.length && data[0].stats.timeStep) { |
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5; |
||||
} |
||||
addTimeAxis(options); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
thresholdManager.addPlotOptions(options, panel); |
||||
addAnnotations(options); |
||||
configureAxisOptions(data, options); |
||||
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); |
||||
|
||||
function callPlot(incrementRenderCounter) { |
||||
try { |
||||
$.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); |
||||
} |
||||
} |
||||
|
||||
function translateFillOption(fill) { |
||||
return fill === 0 ? 0.001 : fill/10; |
||||
} |
||||
|
||||
function shouldDelayDraw(panel) { |
||||
if (panel.legend.rightSide) { |
||||
return true; |
||||
} |
||||
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
function addTimeAxis(options) { |
||||
var ticks = panelWidth / 100; |
||||
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf(); |
||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf(); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: "time", |
||||
min: min, |
||||
max: max, |
||||
label: "Datetime", |
||||
ticks: ticks, |
||||
timeformat: time_format(ticks, min, max), |
||||
}; |
||||
} |
||||
|
||||
function addXSeriesAxis(options) { |
||||
var ticks = _.map(data, function(series, index) { |
||||
return [index + 1, series.alias]; |
||||
}); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: null, |
||||
min: 0, |
||||
max: ticks.length + 1, |
||||
label: "Datetime", |
||||
ticks: ticks |
||||
}; |
||||
} |
||||
|
||||
function addXTableAxis(options) { |
||||
var ticks = _.map(data, function(series, seriesIndex) { |
||||
return _.map(series.datapoints, function(point, pointIndex) { |
||||
var tickIndex = seriesIndex * series.datapoints.length + pointIndex; |
||||
return [tickIndex + 1, point[1]]; |
||||
}); |
||||
}); |
||||
ticks = _.flatten(ticks, true); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: null, |
||||
min: 0, |
||||
max: ticks.length + 1, |
||||
label: "Datetime", |
||||
ticks: ticks |
||||
}; |
||||
} |
||||
|
||||
function addAnnotations(options) { |
||||
if(!annotations || annotations.length === 0) { |
||||
return; |
||||
} |
||||
|
||||
var types = {}; |
||||
for (var i = 0; i < annotations.length; i++) { |
||||
var item = annotations[i]; |
||||
|
||||
if (!types[item.source.name]) { |
||||
types[item.source.name] = { |
||||
color: item.source.iconColor, |
||||
position: 'BOTTOM', |
||||
markerSize: 5, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
options.events = { |
||||
levels: _.keys(types).length + 1, |
||||
data: annotations, |
||||
types: types, |
||||
}; |
||||
} |
||||
|
||||
//Override min/max to provide more flexible autoscaling
|
||||
function autoscaleSpanOverride(yaxis, data, options) { |
||||
var expr; |
||||
if (yaxis.min != null && data != null) { |
||||
expr = parseThresholdExpr(yaxis.min); |
||||
options.min = autoscaleYAxisMin(expr, data.stats); |
||||
} |
||||
if (yaxis.max != null && data != null) { |
||||
expr = parseThresholdExpr(yaxis.max); |
||||
options.max = autoscaleYAxisMax(expr, data.stats); |
||||
} |
||||
} |
||||
|
||||
function parseThresholdExpr(expr) { |
||||
var match, operator, value, precision; |
||||
expr = String(expr); |
||||
match = expr.match(/\s*([<=>~]*)\s*(\-?\d+(\.\d+)?)/); |
||||
if (match) { |
||||
operator = match[1]; |
||||
value = parseFloat(match[2]); |
||||
//Precision based on input
|
||||
precision = match[3] ? match[3].length - 1 : 0; |
||||
return { |
||||
operator: operator, |
||||
value: value, |
||||
precision: precision |
||||
}; |
||||
} else { |
||||
return undefined; |
||||
} |
||||
} |
||||
|
||||
function autoscaleYAxisMax(expr, dataStats) { |
||||
var operator = expr.operator, |
||||
value = expr.value, |
||||
precision = expr.precision; |
||||
if (operator === ">") { |
||||
return dataStats.max < value ? value : null; |
||||
} else if (operator === "<") { |
||||
return dataStats.max > value ? value : null; |
||||
} else if (operator === "~") { |
||||
return kbn.roundValue(dataStats.avg + value, precision); |
||||
} else if (operator === "=") { |
||||
return kbn.roundValue(dataStats.current + value, precision); |
||||
} else if (!operator && !isNaN(value)) { |
||||
return kbn.roundValue(value, precision); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
function autoscaleYAxisMin(expr, dataStats) { |
||||
var operator = expr.operator, |
||||
value = expr.value, |
||||
precision = expr.precision; |
||||
if (operator === ">") { |
||||
return dataStats.min < value ? value : null; |
||||
} else if (operator === "<") { |
||||
return dataStats.min > value ? value : null; |
||||
} else if (operator === "~") { |
||||
return kbn.roundValue(dataStats.avg - value, precision); |
||||
} else if (operator === "=") { |
||||
return kbn.roundValue(dataStats.current - value, precision); |
||||
} else if (!operator && !isNaN(value)) { |
||||
return kbn.roundValue(value, precision); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
function configureAxisOptions(data, options) { |
||||
var defaults = { |
||||
position: 'left', |
||||
show: panel.yaxes[0].show, |
||||
min: panel.yaxes[0].min, |
||||
index: 1, |
||||
logBase: panel.yaxes[0].logBase || 1, |
||||
max: panel.percentage && panel.stack ? 100 : panel.yaxes[0].max, |
||||
}; |
||||
|
||||
autoscaleSpanOverride(panel.yaxes[0], data[0], defaults); |
||||
options.yaxes.push(defaults); |
||||
|
||||
if (_.find(data, {yaxis: 2})) { |
||||
var secondY = _.clone(defaults); |
||||
secondY.index = 2, |
||||
secondY.show = panel.yaxes[1].show; |
||||
secondY.logBase = panel.yaxes[1].logBase || 1, |
||||
secondY.position = 'right'; |
||||
secondY.min = panel.yaxes[1].min; |
||||
secondY.max = panel.percentage && panel.stack ? 100 : panel.yaxes[1].max; |
||||
autoscaleSpanOverride(panel.yaxes[1], data[1], secondY); |
||||
options.yaxes.push(secondY); |
||||
|
||||
applyLogScale(options.yaxes[1], data); |
||||
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format); |
||||
} |
||||
|
||||
applyLogScale(options.yaxes[0], data); |
||||
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format); |
||||
} |
||||
|
||||
function applyLogScale(axis, data) { |
||||
if (axis.logBase === 1) { |
||||
return; |
||||
} |
||||
|
||||
var series, i; |
||||
var max = axis.max; |
||||
|
||||
if (max === null) { |
||||
for (i = 0; i < data.length; i++) { |
||||
series = data[i]; |
||||
if (series.yaxis === axis.index) { |
||||
if (max < series.stats.max) { |
||||
max = series.stats.max; |
||||
} |
||||
} |
||||
} |
||||
if (max === void 0) { |
||||
max = Number.MAX_VALUE; |
||||
} |
||||
} |
||||
|
||||
axis.min = axis.min !== null ? axis.min : 0; |
||||
axis.ticks = [0, 1]; |
||||
var nextTick = 1; |
||||
|
||||
while (true) { |
||||
nextTick = nextTick * axis.logBase; |
||||
axis.ticks.push(nextTick); |
||||
if (nextTick > max) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (axis.logBase === 10) { |
||||
axis.transform = function(v) { return Math.log(v+0.1); }; |
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); }; |
||||
} else { |
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); }; |
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; |
||||
} |
||||
} |
||||
|
||||
function configureAxisMode(axis, format) { |
||||
axis.tickFormatter = function(val, axis) { |
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals); |
||||
}; |
||||
} |
||||
|
||||
function time_format(ticks, min, max) { |
||||
if (min && max && ticks) { |
||||
var range = max - min; |
||||
var secPerTick = (range/ticks) / 1000; |
||||
var oneDay = 86400000; |
||||
var oneYear = 31536000000; |
||||
|
||||
if (secPerTick <= 45) { |
||||
return "%H:%M:%S"; |
||||
} |
||||
if (secPerTick <= 7200 || range <= oneDay) { |
||||
return "%H:%M"; |
||||
} |
||||
if (secPerTick <= 80000) { |
||||
return "%m/%d %H:%M"; |
||||
} |
||||
if (secPerTick <= 2419200 || range <= oneYear) { |
||||
return "%m/%d"; |
||||
} |
||||
return "%Y-%m"; |
||||
} |
||||
|
||||
return "%H:%M"; |
||||
} |
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() { |
||||
return sortedSeries; |
||||
}); |
||||
|
||||
elem.bind("plotselected", function (event, ranges) { |
||||
scope.$apply(function() { |
||||
timeSrv.setTime({ |
||||
from : moment.utc(ranges.xaxis.from), |
||||
to : moment.utc(ranges.xaxis.to), |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
}; |
||||
}); |
||||
}); |
@ -0,0 +1,602 @@ |
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import 'jquery.flot'; |
||||
import 'jquery.flot.selection'; |
||||
import 'jquery.flot.time'; |
||||
import 'jquery.flot.stack'; |
||||
import 'jquery.flot.stackpercent'; |
||||
import 'jquery.flot.fillbelow'; |
||||
import 'jquery.flot.crosshair'; |
||||
import './jquery.flot.events'; |
||||
|
||||
import angular from 'angular'; |
||||
import $ from 'jquery'; |
||||
import moment from 'moment'; |
||||
import _ from 'lodash'; |
||||
import kbn from 'app/core/utils/kbn'; |
||||
import GraphTooltip from './graph_tooltip'; |
||||
import {ThresholdManager} from './threshold_manager'; |
||||
|
||||
var module = angular.module('grafana.directives'); |
||||
var labelWidthCache = {}; |
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) { |
||||
return { |
||||
restrict: 'A', |
||||
template: '<div> </div>', |
||||
link: function(scope, elem) { |
||||
var ctrl = scope.ctrl; |
||||
var dashboard = ctrl.dashboard; |
||||
var panel = ctrl.panel; |
||||
var data, annotations; |
||||
var sortedSeries; |
||||
var legendSideLastValue = null; |
||||
var rootScope = scope.$root; |
||||
var panelWidth = 0; |
||||
var thresholdManager = new ThresholdManager(ctrl); |
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) { |
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) { |
||||
return; |
||||
} |
||||
|
||||
if (dashboard.sharedCrosshair) { |
||||
var plot = elem.data().plot; |
||||
if (plot) { |
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y }); |
||||
} |
||||
} |
||||
}, scope); |
||||
|
||||
rootScope.onAppEvent('clearCrosshair', function() { |
||||
var plot = elem.data().plot; |
||||
if (plot) { |
||||
plot.clearCrosshair(); |
||||
} |
||||
}, scope); |
||||
|
||||
// Receive render events
|
||||
ctrl.events.on('render', function(renderData) { |
||||
data = renderData || data; |
||||
if (!data) { |
||||
return; |
||||
} |
||||
annotations = data.annotations || annotations; |
||||
render_panel(); |
||||
}); |
||||
|
||||
function getLegendHeight(panelHeight) { |
||||
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; |
||||
} |
||||
} |
||||
|
||||
function setElementHeight() { |
||||
try { |
||||
var height = ctrl.height - getLegendHeight(ctrl.height); |
||||
elem.css('height', height + 'px'); |
||||
|
||||
return true; |
||||
} catch (e) { // IE throws errors sometimes
|
||||
console.log(e); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
function shouldAbortRender() { |
||||
if (!data) { |
||||
return true; |
||||
} |
||||
|
||||
if (!setElementHeight()) { return true; } |
||||
|
||||
if (panelWidth === 0) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
function getLabelWidth(text, elem) { |
||||
var labelWidth = labelWidthCache[text]; |
||||
|
||||
if (!labelWidth) { |
||||
labelWidth = labelWidthCache[text] = elem.width(); |
||||
} |
||||
|
||||
return labelWidth; |
||||
} |
||||
|
||||
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) { |
||||
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>") |
||||
.text(panel.yaxes[0].label) |
||||
.appendTo(elem); |
||||
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px'; |
||||
} |
||||
|
||||
// add right axis labels
|
||||
if (panel.yaxes[1].label) { |
||||
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>") |
||||
.text(panel.yaxes[1].label) |
||||
.appendTo(elem); |
||||
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px'; |
||||
} |
||||
|
||||
thresholdManager.draw(plot); |
||||
} |
||||
|
||||
function processOffsetHook(plot, gridMargin) { |
||||
var left = panel.yaxes[0]; |
||||
var right = panel.yaxes[1]; |
||||
if (left.show && left.label) { gridMargin.left = 20; } |
||||
if (right.show && right.label) { gridMargin.right = 20; } |
||||
} |
||||
|
||||
function processDatapoints(plot) { |
||||
console.log('processDatapoints'); |
||||
} |
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() { |
||||
panelWidth = elem.width(); |
||||
|
||||
if (shouldAbortRender()) { |
||||
return; |
||||
} |
||||
|
||||
// give space to alert editing
|
||||
thresholdManager.prepare(elem, data); |
||||
|
||||
var stack = panel.stack ? true : null; |
||||
|
||||
// Populate element
|
||||
var options: any = { |
||||
hooks: { |
||||
draw: [drawHook], |
||||
processOffset: [processOffsetHook], |
||||
processDatapoints: [processDatapoints], |
||||
}, |
||||
legend: { show: false }, |
||||
series: { |
||||
stackpercent: panel.stack ? panel.percentage : false, |
||||
stack: panel.percentage ? null : stack, |
||||
lines: { |
||||
show: panel.lines, |
||||
zero: false, |
||||
fill: translateFillOption(panel.fill), |
||||
lineWidth: panel.linewidth, |
||||
steps: panel.steppedLine |
||||
}, |
||||
bars: { |
||||
show: panel.bars, |
||||
fill: 1, |
||||
barWidth: 1, |
||||
zero: false, |
||||
lineWidth: 0 |
||||
}, |
||||
points: { |
||||
show: panel.points, |
||||
fill: 1, |
||||
fillColor: false, |
||||
radius: panel.points ? panel.pointradius : 2 |
||||
}, |
||||
shadowSize: 0 |
||||
}, |
||||
yaxes: [], |
||||
xaxis: {}, |
||||
grid: { |
||||
minBorderMargin: 0, |
||||
markings: [], |
||||
backgroundColor: null, |
||||
borderWidth: 0, |
||||
hoverable: true, |
||||
color: '#c8c8c8', |
||||
margin: { left: 0, right: 0 }, |
||||
}, |
||||
selection: { |
||||
mode: "x", |
||||
color: '#666' |
||||
}, |
||||
crosshair: { |
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null |
||||
} |
||||
}; |
||||
|
||||
for (var i = 0; i < data.length; i++) { |
||||
var series = data[i]; |
||||
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode); |
||||
|
||||
if (panel.xaxis.mode === 'series') { |
||||
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]]; |
||||
} else if (panel.xaxis.mode === 'table' || panel.xaxis.mode === 'elastic') { |
||||
series.data = []; |
||||
for (var j = 0; j < series.datapoints.length; j++) { |
||||
var dataIndex = i * series.datapoints.length + j; |
||||
series.datapoints[j]; |
||||
series.data.push([ |
||||
dataIndex + 1, |
||||
series.datapoints[j][0] |
||||
]); |
||||
} |
||||
} |
||||
|
||||
// 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'; |
||||
addXSeriesAxis(options); |
||||
break; |
||||
} |
||||
case 'table': { |
||||
options.series.bars.barWidth = 0.7; |
||||
options.series.bars.align = 'center'; |
||||
addXTableAxis(options); |
||||
break; |
||||
} |
||||
default: { |
||||
if (data.length && data[0].stats.timeStep) { |
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5; |
||||
} |
||||
addTimeAxis(options); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
thresholdManager.addPlotOptions(options, panel); |
||||
addAnnotations(options); |
||||
configureAxisOptions(data, options); |
||||
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); |
||||
|
||||
function callPlot(incrementRenderCounter) { |
||||
try { |
||||
$.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); |
||||
} |
||||
} |
||||
|
||||
function translateFillOption(fill) { |
||||
return fill === 0 ? 0.001 : fill/10; |
||||
} |
||||
|
||||
function shouldDelayDraw(panel) { |
||||
if (panel.legend.rightSide) { |
||||
return true; |
||||
} |
||||
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
function addTimeAxis(options) { |
||||
var ticks = panelWidth / 100; |
||||
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf(); |
||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf(); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: "time", |
||||
min: min, |
||||
max: max, |
||||
label: "Datetime", |
||||
ticks: ticks, |
||||
timeformat: time_format(ticks, min, max), |
||||
}; |
||||
} |
||||
|
||||
function addXSeriesAxis(options) { |
||||
var ticks = _.map(data, function(series, index) { |
||||
return [index + 1, series.alias]; |
||||
}); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: null, |
||||
min: 0, |
||||
max: ticks.length + 1, |
||||
label: "Datetime", |
||||
ticks: ticks |
||||
}; |
||||
} |
||||
|
||||
function addXTableAxis(options) { |
||||
var ticks = _.map(data, function(series, seriesIndex) { |
||||
return _.map(series.datapoints, function(point, pointIndex) { |
||||
var tickIndex = seriesIndex * series.datapoints.length + pointIndex; |
||||
return [tickIndex + 1, point[1]]; |
||||
}); |
||||
}); |
||||
ticks = _.flatten(ticks, true); |
||||
|
||||
options.xaxis = { |
||||
timezone: dashboard.getTimezone(), |
||||
show: panel.xaxis.show, |
||||
mode: null, |
||||
min: 0, |
||||
max: ticks.length + 1, |
||||
label: "Datetime", |
||||
ticks: ticks |
||||
}; |
||||
} |
||||
|
||||
function addAnnotations(options) { |
||||
if (!annotations || annotations.length === 0) { |
||||
return; |
||||
} |
||||
|
||||
var types = {}; |
||||
for (var i = 0; i < annotations.length; i++) { |
||||
var item = annotations[i]; |
||||
|
||||
if (!types[item.source.name]) { |
||||
types[item.source.name] = { |
||||
color: item.source.iconColor, |
||||
position: 'BOTTOM', |
||||
markerSize: 5, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
options.events = { |
||||
levels: _.keys(types).length + 1, |
||||
data: annotations, |
||||
types: types, |
||||
}; |
||||
} |
||||
|
||||
//Override min/max to provide more flexible autoscaling
|
||||
function autoscaleSpanOverride(yaxis, data, options) { |
||||
var expr; |
||||
if (yaxis.min != null && data != null) { |
||||
expr = parseThresholdExpr(yaxis.min); |
||||
options.min = autoscaleYAxisMin(expr, data.stats); |
||||
} |
||||
if (yaxis.max != null && data != null) { |
||||
expr = parseThresholdExpr(yaxis.max); |
||||
options.max = autoscaleYAxisMax(expr, data.stats); |
||||
} |
||||
} |
||||
|
||||
function parseThresholdExpr(expr) { |
||||
var match, operator, value, precision; |
||||
expr = String(expr); |
||||
match = expr.match(/\s*([<=>~]*)\s*(\-?\d+(\.\d+)?)/); |
||||
if (match) { |
||||
operator = match[1]; |
||||
value = parseFloat(match[2]); |
||||
//Precision based on input
|
||||
precision = match[3] ? match[3].length - 1 : 0; |
||||
return { |
||||
operator: operator, |
||||
value: value, |
||||
precision: precision |
||||
}; |
||||
} else { |
||||
return undefined; |
||||
} |
||||
} |
||||
|
||||
function autoscaleYAxisMax(expr, dataStats) { |
||||
var operator = expr.operator, |
||||
value = expr.value, |
||||
precision = expr.precision; |
||||
if (operator === ">") { |
||||
return dataStats.max < value ? value : null; |
||||
} else if (operator === "<") { |
||||
return dataStats.max > value ? value : null; |
||||
} else if (operator === "~") { |
||||
return kbn.roundValue(dataStats.avg + value, precision); |
||||
} else if (operator === "=") { |
||||
return kbn.roundValue(dataStats.current + value, precision); |
||||
} else if (!operator && !isNaN(value)) { |
||||
return kbn.roundValue(value, precision); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
function autoscaleYAxisMin(expr, dataStats) { |
||||
var operator = expr.operator, |
||||
value = expr.value, |
||||
precision = expr.precision; |
||||
if (operator === ">") { |
||||
return dataStats.min < value ? value : null; |
||||
} else if (operator === "<") { |
||||
return dataStats.min > value ? value : null; |
||||
} else if (operator === "~") { |
||||
return kbn.roundValue(dataStats.avg - value, precision); |
||||
} else if (operator === "=") { |
||||
return kbn.roundValue(dataStats.current - value, precision); |
||||
} else if (!operator && !isNaN(value)) { |
||||
return kbn.roundValue(value, precision); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
function configureAxisOptions(data, options) { |
||||
var defaults = { |
||||
position: 'left', |
||||
show: panel.yaxes[0].show, |
||||
// min: panel.yaxes[0].min,
|
||||
index: 1, |
||||
logBase: panel.yaxes[0].logBase || 1, |
||||
max: panel.percentage && panel.stack ? 100 : panel.yaxes[0].max, |
||||
}; |
||||
|
||||
// autoscaleSpanOverride(panel.yaxes[0], data[0], defaults);
|
||||
options.yaxes.push(defaults); |
||||
|
||||
if (_.find(data, {yaxis: 2})) { |
||||
var secondY = _.clone(defaults); |
||||
secondY.index = 2, |
||||
secondY.show = panel.yaxes[1].show; |
||||
secondY.logBase = panel.yaxes[1].logBase || 1, |
||||
secondY.position = 'right'; |
||||
// secondY.min = panel.yaxes[1].min;
|
||||
secondY.max = panel.percentage && panel.stack ? 100 : panel.yaxes[1].max; |
||||
// autoscaleSpanOverride(panel.yaxes[1], data[1], secondY);
|
||||
options.yaxes.push(secondY); |
||||
|
||||
applyLogScale(options.yaxes[1], data); |
||||
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format); |
||||
} |
||||
|
||||
applyLogScale(options.yaxes[0], data); |
||||
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format); |
||||
} |
||||
|
||||
function applyLogScale(axis, data) { |
||||
if (axis.logBase === 1) { |
||||
return; |
||||
} |
||||
|
||||
var series, i; |
||||
var max = axis.max; |
||||
|
||||
if (max === null) { |
||||
for (i = 0; i < data.length; i++) { |
||||
series = data[i]; |
||||
if (series.yaxis === axis.index) { |
||||
if (max < series.stats.max) { |
||||
max = series.stats.max; |
||||
} |
||||
} |
||||
} |
||||
if (max === void 0) { |
||||
max = Number.MAX_VALUE; |
||||
} |
||||
} |
||||
|
||||
axis.min = axis.min !== null ? axis.min : 0; |
||||
axis.ticks = [0, 1]; |
||||
var nextTick = 1; |
||||
|
||||
while (true) { |
||||
nextTick = nextTick * axis.logBase; |
||||
axis.ticks.push(nextTick); |
||||
if (nextTick > max) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (axis.logBase === 10) { |
||||
axis.transform = function(v) { return Math.log(v+0.1); }; |
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); }; |
||||
} else { |
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); }; |
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; |
||||
} |
||||
} |
||||
|
||||
function configureAxisMode(axis, format) { |
||||
axis.tickFormatter = function(val, axis) { |
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals); |
||||
}; |
||||
} |
||||
|
||||
function time_format(ticks, min, max) { |
||||
if (min && max && ticks) { |
||||
var range = max - min; |
||||
var secPerTick = (range/ticks) / 1000; |
||||
var oneDay = 86400000; |
||||
var oneYear = 31536000000; |
||||
|
||||
if (secPerTick <= 45) { |
||||
return "%H:%M:%S"; |
||||
} |
||||
if (secPerTick <= 7200 || range <= oneDay) { |
||||
return "%H:%M"; |
||||
} |
||||
if (secPerTick <= 80000) { |
||||
return "%m/%d %H:%M"; |
||||
} |
||||
if (secPerTick <= 2419200 || range <= oneYear) { |
||||
return "%m/%d"; |
||||
} |
||||
return "%Y-%m"; |
||||
} |
||||
|
||||
return "%H:%M"; |
||||
} |
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() { |
||||
return sortedSeries; |
||||
}); |
||||
|
||||
elem.bind("plotselected", function (event, ranges) { |
||||
scope.$apply(function() { |
||||
timeSrv.setTime({ |
||||
from : moment.utc(ranges.xaxis.from), |
||||
to : moment.utc(ranges.xaxis.to), |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
}; |
||||
}); |
Loading…
Reference in new issue