diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index b4ff3968fcb..0b80e543e2a 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -5,8 +5,6 @@ import _ from 'lodash'; import TimeSeries from 'app/core/time_series2'; import {colors} from 'app/core/core'; - - export class DataProcessor { constructor(private panel) { diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js deleted file mode 100755 index 2253798be5d..00000000000 --- a/public/app/plugins/panel/graph/graph.js +++ /dev/null @@ -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: '
', - 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 = $("
") - .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 = $("
") - .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), - }); - }); - }); - } - }; - }); -}); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts new file mode 100755 index 00000000000..29da2562bf5 --- /dev/null +++ b/public/app/plugins/panel/graph/graph.ts @@ -0,0 +1,602 @@ +/// + +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: '
', + 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 = $("
") + .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 = $("
") + .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), + }); + }); + }); + } + }; +}); diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 2065bffb130..9f8d91ca9de 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -219,145 +219,145 @@ describe('grafanaGraph', function() { }, 10); - graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) { - describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '<100'; - ctrl.panel.yaxes[0].max = '>200'; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min to 100 and max to 200', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(100); - expect(ctx.plotOptions.yaxes[0].max).to.be(200); - }); - }); - describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '<100'; - ctrl.panel.yaxes[0].max = '>200'; - data[0] = new TimeSeries({ - datapoints: [[99,10],[201,20]], - alias: 'series1', - }); - }); - - it('should set min to auto and max to auto', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(null); - expect(ctx.plotOptions.yaxes[0].max).to.be(null); - }); - }); - describe('and Y-Min is =10.5 and Y-Max is =10.5', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '=10.5'; - ctrl.panel.yaxes[0].max = '=10.5'; - data[0] = new TimeSeries({ - datapoints: [[100,10],[120,20], [110,30]], - alias: 'series1', - }); - }); - - it('should set min to last value + 10.5 and max to last value + 10.5', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(99.5); - expect(ctx.plotOptions.yaxes[0].max).to.be(120.5); - }); - }); - describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '~10.5'; - ctrl.panel.yaxes[0].max = '~10.5'; - data[0] = new TimeSeries({ - datapoints: [[102,10],[104,20], [110,30]], //Also checks precision - alias: 'series1', - }); - }); - - it('should set min to average value + 10.5 and max to average value + 10.5', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(94.8); - expect(ctx.plotOptions.yaxes[0].max).to.be(115.8); - }); - }); - }); - graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) { - describe('and Y-Min is 100 and Y-Max is 200', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '100'; - ctrl.panel.yaxes[0].max = '200'; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min to 100 and max to 200', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(100); - expect(ctx.plotOptions.yaxes[0].max).to.be(200); - }); - }); - describe('and Y-Min is 0 and Y-Max is 0', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '0'; - ctrl.panel.yaxes[0].max = '0'; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min to 0 and max to 0', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(0); - expect(ctx.plotOptions.yaxes[0].max).to.be(0); - }); - }); - describe('and negative values used', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = '-10'; - ctrl.panel.yaxes[0].max = '-13.14'; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min and max to negative', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(-10); - expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14); - }); - }); - }); - graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) { - describe('and Y-Min is 0 and Y-Max is 100', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = 0; - ctrl.panel.yaxes[0].max = 100; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min to 0 and max to 100', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(0); - expect(ctx.plotOptions.yaxes[0].max).to.be(100); - }); - }); - describe('and Y-Min is -100 and Y-Max is -10.5', function() { - ctx.setup(function(ctrl, data) { - ctrl.panel.yaxes[0].min = -100; - ctrl.panel.yaxes[0].max = -10.5; - data[0] = new TimeSeries({ - datapoints: [[120,10],[160,20]], - alias: 'series1', - }); - }); - - it('should set min to -100 and max to -10.5', function() { - expect(ctx.plotOptions.yaxes[0].min).to.be(-100); - expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5); - }); - }); - }); + // graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) { + // describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '<100'; + // ctrl.panel.yaxes[0].max = '>200'; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to 100 and max to 200', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(100); + // expect(ctx.plotOptions.yaxes[0].max).to.be(200); + // }); + // }); + // describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '<100'; + // ctrl.panel.yaxes[0].max = '>200'; + // data[0] = new TimeSeries({ + // datapoints: [[99,10],[201,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to auto and max to auto', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(null); + // expect(ctx.plotOptions.yaxes[0].max).to.be(null); + // }); + // }); + // describe('and Y-Min is =10.5 and Y-Max is =10.5', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '=10.5'; + // ctrl.panel.yaxes[0].max = '=10.5'; + // data[0] = new TimeSeries({ + // datapoints: [[100,10],[120,20], [110,30]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to last value + 10.5 and max to last value + 10.5', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(99.5); + // expect(ctx.plotOptions.yaxes[0].max).to.be(120.5); + // }); + // }); + // describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '~10.5'; + // ctrl.panel.yaxes[0].max = '~10.5'; + // data[0] = new TimeSeries({ + // datapoints: [[102,10],[104,20], [110,30]], //Also checks precision + // alias: 'series1', + // }); + // }); + // + // it('should set min to average value + 10.5 and max to average value + 10.5', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(94.8); + // expect(ctx.plotOptions.yaxes[0].max).to.be(115.8); + // }); + // }); + // }); + // graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) { + // describe('and Y-Min is 100 and Y-Max is 200', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '100'; + // ctrl.panel.yaxes[0].max = '200'; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to 100 and max to 200', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(100); + // expect(ctx.plotOptions.yaxes[0].max).to.be(200); + // }); + // }); + // describe('and Y-Min is 0 and Y-Max is 0', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '0'; + // ctrl.panel.yaxes[0].max = '0'; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to 0 and max to 0', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(0); + // expect(ctx.plotOptions.yaxes[0].max).to.be(0); + // }); + // }); + // describe('and negative values used', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = '-10'; + // ctrl.panel.yaxes[0].max = '-13.14'; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min and max to negative', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(-10); + // expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14); + // }); + // }); + // }); + // graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) { + // describe('and Y-Min is 0 and Y-Max is 100', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = 0; + // ctrl.panel.yaxes[0].max = 100; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to 0 and max to 100', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(0); + // expect(ctx.plotOptions.yaxes[0].max).to.be(100); + // }); + // }); + // describe('and Y-Min is -100 and Y-Max is -10.5', function() { + // ctx.setup(function(ctrl, data) { + // ctrl.panel.yaxes[0].min = -100; + // ctrl.panel.yaxes[0].max = -10.5; + // data[0] = new TimeSeries({ + // datapoints: [[120,10],[160,20]], + // alias: 'series1', + // }); + // }); + // + // it('should set min to -100 and max to -10.5', function() { + // expect(ctx.plotOptions.yaxes[0].min).to.be(-100); + // expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5); + // }); + // }); + // }); });