Browse Source

feat(graph): updating graph panel to new format progress

Torkel Ödegaard 10 years ago
parent
commit
ebba7a0327

+ 1 - 12
public/app/features/panel/metrics_panel_ctrl.ts

@@ -49,9 +49,7 @@ class MetricsPanelCtrl extends PanelCtrl {
   }
 
   initEditMode() {
-    this.addEditorTab('Metrics', () => {
-      return { templateUrl: 'public/app/partials/metrics.html' };
-    });
+    this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
     this.datasources = this.datasourceSrv.getMetricSources();
   }
 
@@ -108,15 +106,6 @@ class MetricsPanelCtrl extends PanelCtrl {
     this.timing.queryEnd = new Date().getTime();
   }
 
-  setTimeRenderStart() {
-    this.timing = this.timing || {};
-    this.timing.renderStart = new Date().getTime();
-  }
-
-  setTimeRenderEnd() {
-    this.timing.renderEnd = new Date().getTime();
-  }
-
   updateTimeRange() {
     this.range = this.timeSrv.timeRange();
     this.rangeRaw = this.timeSrv.timeRange(false);

+ 18 - 10
public/app/features/panel/panel_ctrl.ts

@@ -2,29 +2,28 @@
 
 import config from 'app/core/config';
 
-function generalOptionsTabEditorTab() {
-  return {templateUrl: 'public/app/partials/panelgeneral.html'};
-}
-
 export class PanelCtrl {
   panel: any;
   row: any;
   dashboard: any;
   editorTabIndex: number;
-  name: string;
+  pluginName: string;
+  pluginId: string;
   icon: string;
   editorTabs: any;
   $scope: any;
   $injector: any;
   fullscreen: boolean;
   inspector: any;
+  editModeInitiated: boolean;
 
   constructor($scope, $injector) {
     var plugin = config.panels[this.panel.type];
 
     this.$injector = $injector;
     this.$scope = $scope;
-    this.name = plugin.name;
+    this.pluginName = plugin.name;
+    this.pluginId = plugin.id;
     this.icon = plugin.info.icon;
     this.editorTabIndex = 0;
 
@@ -59,9 +58,9 @@ export class PanelCtrl {
   }
 
   editPanel() {
-    if (!this.editorTabs) {
+    if (!this.editModeInitiated) {
       this.editorTabs = [];
-      this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab});
+      this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
       this.initEditMode();
     }
 
@@ -76,8 +75,13 @@ export class PanelCtrl {
     return;
   }
 
-  addEditorTab(title, directiveFn) {
-    this.editorTabs.push({title: title, directiveFn: directiveFn});
+  addEditorTab(title, templateUrl) {
+    this.editorTabs.push({
+      title: title,
+      directiveFn: function() {
+        return {templateUrl: templateUrl};
+      }
+    });
   }
 
   getMenu() {
@@ -92,4 +96,8 @@ export class PanelCtrl {
   otherPanelInFullscreenMode() {
     return this.dashboard.meta.fullscreen && !this.fullscreen;
   }
+
+  broadcastRender(arg1?, arg2?) {
+    this.$scope.$broadcast('render', arg1, arg2);
+  }
 }

+ 5 - 1
public/app/features/panel/panel_editor_tab.ts

@@ -11,10 +11,14 @@ function panelEditorTab(dynamicDirectiveSrv) {
     scope: {
       ctrl: "=",
       editorTab: "=",
+      index: "=",
     },
     directive: scope => {
+      var pluginId = scope.ctrl.pluginId;
+      var tabIndex = scope.index;
+
       return Promise.resolve({
-        name: 'panel-editor-tab-' + scope.editorTab.title,
+        name: `panel-editor-tab-${pluginId}${tabIndex}`,
         fn: scope.editorTab.directiveFn,
       });
     }

+ 1 - 1
public/app/features/panel/partials/panel.html

@@ -39,7 +39,7 @@
 
 		<div class="gf-box-body">
 			<div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index">
-				<panel-editor-tab editor-tab="tab" ctrl="ctrl"></panel-editor-tab>
+				<panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
 			</div>
 		</div>
 	</div>

+ 53 - 53
public/app/plugins/panel/graph/graph.js

@@ -24,14 +24,16 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
       restrict: 'A',
       template: '<div> </div>',
       link: function(scope, elem) {
-        var dashboard = scope.dashboard;
+        var ctrl = scope.ctrl;
+        var dashboard = ctrl.dashboard;
+        var panel = ctrl.panel;
         var data, annotations;
         var sortedSeries;
         var graphHeight;
         var legendSideLastValue = null;
-        scope.crosshairEmiter = false;
+        var rootScope = scope.$root;
 
-        scope.onAppEvent('setCrosshair', function(event, info) {
+        rootScope.onAppEvent('setCrosshair', function(event, info) {
           // do not need to to this if event is from this panel
           if (info.scope === scope) {
             return;
@@ -43,20 +45,20 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
               plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
             }
           }
-        });
+        }, scope);
 
-        scope.onAppEvent('clearCrosshair', function() {
+        rootScope.onAppEvent('clearCrosshair', function() {
           var plot = elem.data().plot;
           if (plot) {
             plot.clearCrosshair();
           }
-        });
+        }, scope);
 
         // Receive render events
         scope.$on('render',function(event, renderData) {
           data = renderData || data;
           if (!data) {
-            scope.get_data();
+            ctrl.refresh();
             return;
           }
           annotations = data.annotations || annotations;
@@ -64,10 +66,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
         });
 
         function getLegendHeight(panelHeight) {
-          if (!scope.panel.legend.show || scope.panel.legend.rightSide) {
+          if (!panel.legend.show || panel.legend.rightSide) {
             return 0;
           }
-          if (scope.panel.legend.alignAsTable) {
+          if (panel.legend.alignAsTable) {
             var total = 30 + (25 * data.length);
             return Math.min(total, Math.floor(panelHeight/2));
           } else {
@@ -77,14 +79,13 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
 
         function setElementHeight() {
           try {
-            graphHeight = scope.height || scope.panel.height || scope.row.height;
+            graphHeight = ctrl.height || panel.height || ctrl.row.height;
             if (_.isString(graphHeight)) {
               graphHeight = parseInt(graphHeight.replace('px', ''), 10);
             }
 
             graphHeight -= 5; // padding
-            graphHeight -= scope.panel.title ? 24 : 9; // subtract panel title bar
-
+            graphHeight -= panel.title ? 24 : 9; // subtract panel title bar
             graphHeight = graphHeight - getLegendHeight(graphHeight); // subtract one line legend
 
             elem.css('height', graphHeight + 'px');
@@ -100,7 +101,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
             return true;
           }
 
-          if ($rootScope.fullscreen && !scope.fullscreen) {
+          if (ctrl.otherPanelInFullscreenMode()) {
             return true;
           }
 
@@ -122,11 +123,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
           for (var i = 0; i < data.length; i++) {
             var series = data[i];
             var axis = yaxis[series.yaxis - 1];
-            var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
+            var formater = kbn.valueFormats[panel.y_formats[series.yaxis - 1]];
 
             // decimal override
-            if (_.isNumber(scope.panel.decimals)) {
-              series.updateLegendValues(formater, scope.panel.decimals, null);
+            if (_.isNumber(panel.decimals)) {
+              series.updateLegendValues(formater, panel.decimals, null);
             } else {
               // auto decimals
               // legend and tooltip gets one more decimal precision
@@ -135,22 +136,22 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
               series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
             }
 
-            if(!scope.$$phase) { scope.$digest(); }
+            if(!rootScope.$$phase) { scope.$digest(); }
           }
 
           // add left axis labels
-          if (scope.panel.leftYAxisLabel) {
+          if (panel.leftYAxisLabel) {
             var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
-              .text(scope.panel.leftYAxisLabel)
+              .text(panel.leftYAxisLabel)
               .appendTo(elem);
 
             yaxisLabel.css("margin-top", yaxisLabel.width() / 2);
           }
 
           // add right axis labels
-          if (scope.panel.rightYAxisLabel) {
+          if (panel.rightYAxisLabel) {
             var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
-              .text(scope.panel.rightYAxisLabel)
+              .text(panel.rightYAxisLabel)
               .appendTo(elem);
 
             rightLabel.css("margin-top", rightLabel.width() / 2);
@@ -158,8 +159,8 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
         }
 
         function processOffsetHook(plot, gridMargin) {
-          if (scope.panel.leftYAxisLabel) { gridMargin.left = 20; }
-          if (scope.panel.rightYAxisLabel) { gridMargin.right = 20; }
+          if (panel.leftYAxisLabel) { gridMargin.left = 20; }
+          if (panel.rightYAxisLabel) { gridMargin.right = 20; }
         }
 
         // Function for rendering panel
@@ -168,7 +169,6 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
             return;
           }
 
-          var panel = scope.panel;
           var stack = panel.stack ? true : null;
 
           // Populate element
@@ -230,7 +230,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
             series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode, panel.y_formats);
 
             // if hidden remove points and disable stack
-            if (scope.hiddenSeries[series.alias]) {
+            if (ctrl.hiddenSeries[series.alias]) {
               series.data = [];
               series.stack = false;
             }
@@ -255,7 +255,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
             }
 
             if (incrementRenderCounter) {
-              scope.panelRenderingComplete();
+              ctrl.renderingCompleted();
             }
           }
 
@@ -285,18 +285,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
 
         function addTimeAxis(options) {
           var ticks = elem.width() / 100;
-          var min = _.isUndefined(scope.range.from) ? null : scope.range.from.valueOf();
-          var max = _.isUndefined(scope.range.to) ? null : scope.range.to.valueOf();
+          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.timezone,
-            show: scope.panel['x-axis'],
+            show: panel['x-axis'],
             mode: "time",
             min: min,
             max: max,
             label: "Datetime",
             ticks: ticks,
-            timeformat: time_format(scope.interval, ticks, min, max),
+            timeformat: time_format(ctrl.interval, ticks, min, max),
           };
         }
 
@@ -361,11 +361,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
         function configureAxisOptions(data, options) {
           var defaults = {
             position: 'left',
-            show: scope.panel['y-axis'],
-            min: scope.panel.grid.leftMin,
+            show: panel['y-axis'],
+            min: panel.grid.leftMin,
             index: 1,
-            logBase: scope.panel.grid.leftLogBase || 1,
-            max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
+            logBase: panel.grid.leftLogBase || 1,
+            max: panel.percentage && panel.stack ? 100 : panel.grid.leftMax,
           };
 
           options.yaxes.push(defaults);
@@ -373,18 +373,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
           if (_.findWhere(data, {yaxis: 2})) {
             var secondY = _.clone(defaults);
             secondY.index = 2,
-            secondY.logBase = scope.panel.grid.rightLogBase || 1,
+            secondY.logBase = panel.grid.rightLogBase || 1,
             secondY.position = 'right';
-            secondY.min = scope.panel.grid.rightMin;
-            secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
+            secondY.min = panel.grid.rightMin;
+            secondY.max = panel.percentage && panel.stack ? 100 : panel.grid.rightMax;
             options.yaxes.push(secondY);
 
             applyLogScale(options.yaxes[1], data);
-            configureAxisMode(options.yaxes[1], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[1]);
+            configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.y_formats[1]);
           }
 
           applyLogScale(options.yaxes[0], data);
-          configureAxisMode(options.yaxes[0], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[0]);
+          configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.y_formats[0]);
         }
 
         function applyLogScale(axis, data) {
@@ -463,18 +463,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
           url += '&height=' + elem.css('height').replace('px', '');
           url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
           url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
-          url += scope.panel.stack ? '&areaMode=stacked' : '';
-          url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
-          url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
-          url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
-          url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
-          url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
-          url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
-          url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
-          url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
-          url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
-
-          switch(scope.panel.y_formats[0]) {
+          url += panel.stack ? '&areaMode=stacked' : '';
+          url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
+          url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
+          url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
+          url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : '';
+          url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : '';
+          url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : '';
+          url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : '';
+          url += panel['x-axis'] ? '' : '&hideAxes=true';
+          url += panel['y-axis'] ? '' : '&hideYAxis=true';
+
+          switch(panel.y_formats[0]) {
             case 'bytes':
               url += '&yUnitSystem=binary';
               break;
@@ -507,7 +507,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
               break;
           }
 
-          switch(scope.panel.nullPointMode) {
+          switch(panel.nullPointMode) {
             case 'connected':
               url += '&lineMode=connected';
               break;
@@ -518,7 +518,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
               break;
           }
 
-          url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
+          url += panel.steppedLine ? '&lineMode=staircase' : '';
 
           elem.html('<img src="' + url + '"></img>');
         }

+ 292 - 0
public/app/plugins/panel/graph/graph_ctrl.ts

@@ -0,0 +1,292 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import moment from 'moment';
+import kbn from 'app/core/utils/kbn';
+import _ from 'lodash';
+import TimeSeries from '../../../core/time_series2';
+import * as fileExport from '../../../core/utils/file_export';
+import {MetricsPanelCtrl} from '../../../features/panel/panel';
+
+var panelDefaults = {
+  // datasource name, null = default datasource
+  datasource: null,
+  // sets client side (flot) or native graphite png renderer (png)
+  renderer: 'flot',
+  // Show/hide the x-axis
+  'x-axis'      : true,
+  // Show/hide y-axis
+  'y-axis'      : true,
+  // y axis formats, [left axis,right axis]
+  y_formats    : ['short', 'short'],
+  // grid options
+  grid          : {
+    leftLogBase: 1,
+    leftMax: null,
+    rightMax: null,
+    leftMin: null,
+    rightMin: null,
+    rightLogBase: 1,
+    threshold1: null,
+    threshold2: null,
+    threshold1Color: 'rgba(216, 200, 27, 0.27)',
+    threshold2Color: 'rgba(234, 112, 112, 0.22)'
+  },
+  // show/hide lines
+  lines         : true,
+  // fill factor
+  fill          : 1,
+  // line width in pixels
+  linewidth     : 2,
+  // show hide points
+  points        : false,
+  // point radius in pixels
+  pointradius   : 5,
+  // show hide bars
+  bars          : false,
+  // enable/disable stacking
+  stack         : false,
+  // stack percentage mode
+  percentage    : false,
+  // legend options
+  legend: {
+    show: true, // disable/enable legend
+    values: false, // disable/enable legend values
+    min: false,
+    max: false,
+    current: false,
+    total: false,
+    avg: false
+  },
+  // how null points should be handled
+  nullPointMode : 'connected',
+  // staircase line mode
+  steppedLine: false,
+  // tooltip options
+  tooltip       : {
+    value_type: 'cumulative',
+    shared: true,
+  },
+  // time overrides
+  timeFrom: null,
+  timeShift: null,
+  // metric queries
+  targets: [{}],
+  // series color overrides
+  aliasColors: {},
+  // other style overrides
+  seriesOverrides: [],
+};
+
+class GraphCtrl extends MetricsPanelCtrl {
+  hiddenSeries: any = {};
+  seriesList: any = [];
+  logScales: any;
+  unitFormats: any;
+  annotationsPromise: any;
+  datapointsCount: number;
+  datapointsOutside: boolean;
+  datapointsWarning: boolean;
+  colors: any = [];
+
+  /** @ngInject */
+  constructor($scope, $injector, private annotationsSrv) {
+    super($scope, $injector);
+
+    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel.tooltip, panelDefaults.tooltip);
+    _.defaults(this.panel.grid, panelDefaults.grid);
+    _.defaults(this.panel.legend, panelDefaults.legend);
+
+    this.colors = $scope.$root.colors;
+  }
+
+  initEditMode() {
+    super.initEditMode();
+
+    this.icon = "fa fa-bar-chart";
+    this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html');
+    this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html');
+
+    // $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
+    // $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
+    // $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
+    //
+    this.logScales = {
+      'linear': 1,
+      'log (base 2)': 2,
+      'log (base 10)': 10,
+      'log (base 32)': 32,
+      'log (base 1024)': 1024
+    };
+    this.unitFormats = kbn.getUnitFormats();
+  }
+
+  setUnitFormat(axis, subItem) {
+    this.panel.y_formats[axis] = subItem.value;
+    this.render();
+  }
+
+  refreshData(datasource) {
+    this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
+
+    return this.issueQueries()
+    .then(res => this.dataHandler(res))
+    .catch(err => {
+      this.seriesList = [];
+      this.render([]);
+      throw err;
+    });
+  }
+
+  zoomOut(evt) {
+    this.publishAppEvent('zoom-out', evt);
+  }
+
+  loadSnapshot(snapshotData) {
+    this.updateTimeRange();
+    this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
+    this.dataHandler(snapshotData);
+  }
+
+  dataHandler(results) {
+    // png renderer returns just a url
+    if (_.isString(results)) {
+      this.render(results);
+      return;
+    }
+
+    this.datapointsWarning = false;
+    this.datapointsCount = 0;
+    this.datapointsOutside = false;
+    this.seriesList = _.map(results.data, (series, i) => this.seriesHandler(series, i));
+    this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
+
+    this.annotationsPromise.then(annotations => {
+      this.loading = false;
+      this.seriesList.annotations = annotations;
+      this.render(this.seriesList);
+    }, () => {
+      this.loading = false;
+      this.render(this.seriesList);
+    });
+  };
+
+  seriesHandler(seriesData, index) {
+    var datapoints = seriesData.datapoints;
+    var alias = seriesData.target;
+    var colorIndex = index % this.colors.length;
+    var color = this.panel.aliasColors[alias] || this.colors[colorIndex];
+
+    var series = new TimeSeries({
+      datapoints: datapoints,
+      alias: alias,
+      color: color,
+    });
+
+    if (datapoints && datapoints.length > 0) {
+      var last = moment.utc(datapoints[datapoints.length - 1][1]);
+      var from = moment.utc(this.range.from);
+      if (last - from < -10000) {
+        this.datapointsOutside = true;
+      }
+
+      this.datapointsCount += datapoints.length;
+    }
+
+    return series;
+  }
+
+  render(data?: any) {
+    this.broadcastRender(data);
+  }
+
+  changeSeriesColor(series, color) {
+    series.color = color;
+    this.panel.aliasColors[series.alias] = series.color;
+    this.render();
+  }
+
+  toggleSeries(serie, event) {
+    if (event.ctrlKey || event.metaKey || event.shiftKey) {
+      if (this.hiddenSeries[serie.alias]) {
+        delete this.hiddenSeries[serie.alias];
+      } else {
+        this.hiddenSeries[serie.alias] = true;
+      }
+    } else {
+      this.toggleSeriesExclusiveMode(serie);
+    }
+
+    this.render();
+  }
+
+  toggleSeriesExclusiveMode (serie) {
+    var hidden = this.hiddenSeries;
+
+    if (hidden[serie.alias]) {
+      delete hidden[serie.alias];
+    }
+
+    // check if every other series is hidden
+    var alreadyExclusive = _.every(this.seriesList, value => {
+      if (value.alias === serie.alias) {
+        return true;
+      }
+
+      return hidden[value.alias];
+    });
+
+    if (alreadyExclusive) {
+      // remove all hidden series
+      _.each(this.seriesList, value => {
+        delete this.hiddenSeries[value.alias];
+      });
+    } else {
+      // hide all but this serie
+      _.each(this.seriesList, value => {
+        if (value.alias === serie.alias) {
+          return;
+        }
+
+        this.hiddenSeries[value.alias] = true;
+      });
+    }
+  }
+
+  toggleYAxis(info) {
+    var override = _.findWhere(this.panel.seriesOverrides, { alias: info.alias });
+    if (!override) {
+      override = { alias: info.alias };
+      this.panel.seriesOverrides.push(override);
+    }
+    override.yaxis = info.yaxis === 2 ? 1 : 2;
+    this.render();
+  };
+
+  addSeriesOverride(override) {
+    this.panel.seriesOverrides.push(override || {});
+  }
+
+  removeSeriesOverride(override) {
+    this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);
+    this.render();
+  }
+
+  // Called from panel menu
+  toggleLegend() {
+    this.panel.legend.show = !this.panel.legend.show;
+    this.refresh();
+  }
+
+  legendValuesOptionChanged() {
+    var legend = this.panel.legend;
+    legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
+    this.render();
+  }
+
+  exportCsv() {
+    fileExport.exportSeriesListToCsv(this.seriesList);
+  }
+}
+
+export {GraphCtrl}

+ 11 - 9
public/app/plugins/panel/graph/graph_tooltip.js

@@ -6,6 +6,8 @@ function ($) {
 
   function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
     var self = this;
+    var ctrl = scope.ctrl;
+    var panel = ctrl.panel;
 
     var $tooltip = $('<div id="tooltip">');
 
@@ -47,12 +49,12 @@ function ($) {
       for (i = 0; i < seriesList.length; i++) {
         series = seriesList[i];
 
-        if (!series.data.length || (scope.panel.legend.hideEmpty && series.allIsNull)) {
+        if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
           results.push({ hidden: true });
           continue;
         }
 
-        if (!series.data.length || (scope.panel.legend.hideZero && series.allIsZero)) {
+        if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
           results.push({ hidden: true });
           continue;
         }
@@ -61,7 +63,7 @@ function ($) {
         results.time = series.data[hoverIndex][0];
 
         if (series.stack) {
-          if (scope.panel.tooltip.value_type === 'individual') {
+          if (panel.tooltip.value_type === 'individual') {
             value = series.data[hoverIndex][1];
           } else if (!series.stack) {
             value = series.data[hoverIndex][1];
@@ -89,7 +91,7 @@ function ($) {
     };
 
     elem.mouseleave(function () {
-      if (scope.panel.tooltip.shared) {
+      if (panel.tooltip.shared) {
         var plot = elem.data().plot;
         if (plot) {
           $tooltip.detach();
@@ -98,7 +100,7 @@ function ($) {
       }
 
       if (dashboard.sharedCrosshair) {
-        scope.appEvent('clearCrosshair');
+        ctrl.publishAppEvent('clearCrosshair');
       }
     });
 
@@ -108,15 +110,15 @@ function ($) {
       var seriesList = getSeriesFn();
       var group, value, absoluteTime, relativeTime, hoverInfo, i, series, seriesHtml;
 
-      if(dashboard.sharedCrosshair){
-        scope.appEvent('setCrosshair', { pos: pos, scope: scope });
+      if (dashboard.sharedCrosshair) {
+        ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
       }
 
       if (seriesList.length === 0) {
         return;
       }
 
-      if (scope.panel.tooltip.shared) {
+      if (panel.tooltip.shared) {
         plot.unhighlight();
 
         var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos);
@@ -153,7 +155,7 @@ function ($) {
         group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
         group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
 
-        if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
+        if (panel.stack && panel.tooltip.value_type === 'individual') {
           value = item.datapoint[1] - item.datapoint[2];
         }
         else {

+ 0 - 3
public/app/plugins/panel/graph/module.d.ts

@@ -1,3 +0,0 @@
-declare var panel: any;
-declare var GraphCtrl: any;
-export {panel, GraphCtrl};

+ 14 - 17
public/app/plugins/panel/graph/module.html

@@ -1,25 +1,22 @@
-<grafana-panel>
-
-	<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
-		<div class="graph-canvas-wrapper">
-
-			<div ng-if="datapointsWarning" class="datapoints-warning">
-				<span class="small" ng-show="!datapointsCount">
-					No datapoints <tip>No datapoints returned from metric query</tip>
-				</span>
-				<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
-			</div>
-
-			<div grafana-graph class="histogram-chart" ng-dblclick="zoomOut()">
-			</div>
+<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
+	<div class="graph-canvas-wrapper">
+
+		<div ng-if="datapointsWarning" class="datapoints-warning">
+			<span class="small" ng-show="!datapointsCount">
+				No datapoints <tip>No datapoints returned from metric query</tip>
+			</span>
+			<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
+		</div>
 
+		<div grafana-graph class="histogram-chart" ng-dblclick="zoomOut()">
 		</div>
 
-		<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
 	</div>
 
-	<div class="clearfix"></div>
+	<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
+</div>
+
+<div class="clearfix"></div>
 
-</grafana-panel>
 
 

+ 0 - 303
public/app/plugins/panel/graph/module.js

@@ -1,303 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'moment',
-  'app/core/utils/kbn',
-  'app/core/utils/file_export',
-  'app/core/time_series',
-  'app/features/panel/panel_meta',
-  './seriesOverridesCtrl',
-  './graph',
-  './legend',
-],
-function (angular, _, moment, kbn, fileExport, TimeSeries, PanelMeta) {
-  'use strict';
-
-  /** @ngInject */
-  function GraphCtrl($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) {
-
-    $scope.panelMeta = new PanelMeta({
-      panelName: 'Graph',
-      editIcon:  "fa fa-bar-chart",
-      fullscreen: true,
-      metricsEditor: true,
-    });
-
-    $scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panel/graph/axisEditor.html');
-    $scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panel/graph/styleEditor.html');
-    $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
-
-    $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
-    $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
-
-    // Set and populate defaults
-    var _d = {
-      // datasource name, null = default datasource
-      datasource: null,
-      // sets client side (flot) or native graphite png renderer (png)
-      renderer: 'flot',
-      // Show/hide the x-axis
-      'x-axis'      : true,
-      // Show/hide y-axis
-      'y-axis'      : true,
-      // y axis formats, [left axis,right axis]
-      y_formats    : ['short', 'short'],
-      // grid options
-      grid          : {
-        leftLogBase: 1,
-        leftMax: null,
-        rightMax: null,
-        leftMin: null,
-        rightMin: null,
-        rightLogBase: 1,
-        threshold1: null,
-        threshold2: null,
-        threshold1Color: 'rgba(216, 200, 27, 0.27)',
-        threshold2Color: 'rgba(234, 112, 112, 0.22)'
-      },
-      // show/hide lines
-      lines         : true,
-      // fill factor
-      fill          : 1,
-      // line width in pixels
-      linewidth     : 2,
-      // show hide points
-      points        : false,
-      // point radius in pixels
-      pointradius   : 5,
-      // show hide bars
-      bars          : false,
-      // enable/disable stacking
-      stack         : false,
-      // stack percentage mode
-      percentage    : false,
-      // legend options
-      legend: {
-        show: true, // disable/enable legend
-        values: false, // disable/enable legend values
-        min: false,
-        max: false,
-        current: false,
-        total: false,
-        avg: false
-      },
-      // how null points should be handled
-      nullPointMode : 'connected',
-      // staircase line mode
-      steppedLine: false,
-      // tooltip options
-      tooltip       : {
-        value_type: 'cumulative',
-        shared: true,
-      },
-      // time overrides
-      timeFrom: null,
-      timeShift: null,
-      // metric queries
-      targets: [{}],
-      // series color overrides
-      aliasColors: {},
-      // other style overrides
-      seriesOverrides: [],
-    };
-
-    _.defaults($scope.panel,_d);
-    _.defaults($scope.panel.tooltip, _d.tooltip);
-    _.defaults($scope.panel.annotate, _d.annotate);
-    _.defaults($scope.panel.grid, _d.grid);
-    _.defaults($scope.panel.legend, _d.legend);
-
-    $scope.logScales = {'linear': 1, 'log (base 2)': 2, 'log (base 10)': 10, 'log (base 32)': 32, 'log (base 1024)': 1024};
-
-    $scope.hiddenSeries = {};
-    $scope.seriesList = [];
-    $scope.unitFormats = kbn.getUnitFormats();
-
-    $scope.setUnitFormat = function(axis, subItem) {
-      $scope.panel.y_formats[axis] = subItem.value;
-      $scope.render();
-    };
-
-    $scope.refreshData = function(datasource) {
-      panelHelper.updateTimeRange($scope);
-
-      $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
-
-      return panelHelper.issueMetricQuery($scope, datasource)
-        .then($scope.dataHandler, function(err) {
-          $scope.seriesList = [];
-          $scope.render([]);
-          throw err;
-        });
-    };
-
-    $scope.zoomOut = function(evt) {
-      $scope.appEvent('zoom-out', evt);
-    };
-
-    $scope.loadSnapshot = function(snapshotData) {
-      panelHelper.updateTimeRange($scope);
-      $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
-      $scope.dataHandler(snapshotData);
-    };
-
-    $scope.dataHandler = function(results) {
-      // png renderer returns just a url
-      if (_.isString(results)) {
-        $scope.render(results);
-        return;
-      }
-
-      $scope.datapointsWarning = false;
-      $scope.datapointsCount = 0;
-      $scope.datapointsOutside = false;
-
-      $scope.seriesList = _.map(results.data, $scope.seriesHandler);
-
-      $scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
-
-      $scope.annotationsPromise
-        .then(function(annotations) {
-          $scope.panelMeta.loading = false;
-          $scope.seriesList.annotations = annotations;
-          $scope.render($scope.seriesList);
-        }, function() {
-          $scope.panelMeta.loading = false;
-          $scope.render($scope.seriesList);
-        });
-    };
-
-    $scope.seriesHandler = function(seriesData, index) {
-      var datapoints = seriesData.datapoints;
-      var alias = seriesData.target;
-      var colorIndex = index % $rootScope.colors.length;
-      var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex];
-
-      var series = new TimeSeries({
-        datapoints: datapoints,
-        alias: alias,
-        color: color,
-      });
-
-      if (datapoints && datapoints.length > 0) {
-        var last = moment.utc(datapoints[datapoints.length - 1][1]);
-        var from = moment.utc($scope.range.from);
-        if (last - from < -10000) {
-          $scope.datapointsOutside = true;
-        }
-
-        $scope.datapointsCount += datapoints.length;
-      }
-
-      return series;
-    };
-
-    $scope.render = function(data) {
-      panelHelper.broadcastRender($scope, data);
-    };
-
-    $scope.changeSeriesColor = function(series, color) {
-      series.color = color;
-      $scope.panel.aliasColors[series.alias] = series.color;
-      $scope.render();
-    };
-
-    $scope.toggleSeries = function(serie, event) {
-      if (event.ctrlKey || event.metaKey || event.shiftKey) {
-        if ($scope.hiddenSeries[serie.alias]) {
-          delete $scope.hiddenSeries[serie.alias];
-        }
-        else {
-          $scope.hiddenSeries[serie.alias] = true;
-        }
-      } else {
-        $scope.toggleSeriesExclusiveMode(serie);
-      }
-
-      $scope.render();
-    };
-
-    $scope.toggleSeriesExclusiveMode = function(serie) {
-      var hidden = $scope.hiddenSeries;
-
-      if (hidden[serie.alias]) {
-        delete hidden[serie.alias];
-      }
-
-      // check if every other series is hidden
-      var alreadyExclusive = _.every($scope.seriesList, function(value) {
-        if (value.alias === serie.alias) {
-          return true;
-        }
-
-        return hidden[value.alias];
-      });
-
-      if (alreadyExclusive) {
-        // remove all hidden series
-        _.each($scope.seriesList, function(value) {
-          delete $scope.hiddenSeries[value.alias];
-        });
-      }
-      else {
-        // hide all but this serie
-        _.each($scope.seriesList, function(value) {
-          if (value.alias === serie.alias) {
-            return;
-          }
-
-          $scope.hiddenSeries[value.alias] = true;
-        });
-      }
-    };
-
-    $scope.toggleYAxis = function(info) {
-      var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
-      if (!override) {
-        override = { alias: info.alias };
-        $scope.panel.seriesOverrides.push(override);
-      }
-      override.yaxis = info.yaxis === 2 ? 1 : 2;
-      $scope.render();
-    };
-
-    $scope.addSeriesOverride = function(override) {
-      $scope.panel.seriesOverrides.push(override || {});
-    };
-
-    $scope.removeSeriesOverride = function(override) {
-      $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
-      $scope.render();
-    };
-
-    // Called from panel menu
-    $scope.toggleLegend = function() {
-      $scope.panel.legend.show = !$scope.panel.legend.show;
-      $scope.get_data();
-    };
-
-    $scope.legendValuesOptionChanged = function() {
-      var legend = $scope.panel.legend;
-      legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
-      $scope.render();
-    };
-
-    $scope.exportCsv = function() {
-      fileExport.exportSeriesListToCsv($scope.seriesList);
-    };
-
-    panelSrv.init($scope);
-  }
-
-  function graphPanelDirective() {
-    return {
-      controller: GraphCtrl,
-      templateUrl: 'app/plugins/panel/graph/module.html',
-    };
-  }
-
-  return {
-    GraphCtrl: GraphCtrl,
-    panel: graphPanelDirective,
-  };
-});

+ 15 - 0
public/app/plugins/panel/graph/module.ts

@@ -0,0 +1,15 @@
+
+import {PanelDirective} from '../../../features/panel/panel';
+import {GraphCtrl} from './graph_ctrl';
+
+import './graph';
+
+class GraphPanel extends PanelDirective {
+  controller = GraphCtrl;
+  templateUrl = 'public/app/plugins/panel/graph/module.html';
+}
+
+export {
+  GraphPanel,
+  GraphPanel as Panel
+}

+ 22 - 22
public/app/plugins/panel/graph/styleEditor.html

@@ -1,60 +1,60 @@
 <div class="editor-row">
   <div class="section">
     <h5>Chart Options</h5>
-		<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
+		<editor-opt-bool text="Bars" model="ctrl.panel.bars" change="ctrl.render()"></editor-opt-bool>
+		<editor-opt-bool text="Lines" model="ctrl.panel.lines" change="ctrl.render()"></editor-opt-bool>
+		<editor-opt-bool text="Points" model="ctrl.panel.points" change="ctrl.render()"></editor-opt-bool>
   </div>
 
   <div class="section">
     <h5>Line options</h5>
-    <div class="editor-option" ng-show="panel.lines">
+    <div class="editor-option" ng-show="ctrl.panel.lines">
       <label class="small">Line Fill</label>
-      <select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
+      <select class="input-mini" ng-model="ctrl.panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
     </div>
-    <div class="editor-option" ng-show="panel.lines">
+    <div class="editor-option" ng-show="ctrl.panel.lines">
       <label class="small">Line Width</label>
-      <select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
+      <select class="input-mini" ng-model="ctrl.panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
     </div>
-    <div class="editor-option" ng-show="panel.points">
+    <div class="editor-option" ng-show="ctrl.panel.points">
       <label class="small">Point Radius</label>
-      <select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
+      <select class="input-mini" ng-model="ctrl.panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
     </div>
     <div class="editor-option">
       <label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
-      <select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
+      <select class="input-medium" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
     </div>
 
-		<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
+		<editor-opt-bool text="Staircase line" model="ctrl.panel.steppedLine" change="ctrl.render()"></editor-opt-bool>
   </div>
   <div class="section">
     <h5>Multiple Series</h5>
 
-		<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
+		<editor-opt-bool text="Stack" model="ctrl.panel.stack" change="ctrl.render()"></editor-opt-bool>
+		<editor-opt-bool text="Percent" model="ctrl.panel.percentage" change="ctrl.render()" tip="Stack as a percentage of total"></editor-opt-bool>
 	</div>
 
   <div class="section">
     <h5>Rendering</h5>
     <div class="editor-option">
       <label class="small">Flot <tip>client side</tip></label>
-      <input type="radio" class="input-small" ng-model="panel.renderer" value="flot" ng-change="get_data()" />
+      <input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="flot" ng-change="ctrl.refresh()" />
     </div>
     <div class="editor-option">
       <label class="small">Graphite PNG <tip>server side</tip></label>
-      <input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
+      <input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="png" ng-change="ctr.refresh()" />
     </div>
   </div>
 
   <div class="section">
     <h5>Tooltip</h5>
 		<editor-opt-bool
-			text="All series" model="panel.tooltip.shared" change="render()"
+			text="All series" model="ctrl.panel.tooltip.shared" change="ctrl.render()"
 			tip="Show all series on same tooltip and a x croshair to help follow all series">
 		</editor-opt-bool>
-		<div class="editor-option" ng-show="panel.stack">
+		<div class="editor-option" ng-show="ctrl.panel.stack">
       <label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
-      <select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
+      <select class="input-small" ng-model="ctrl.panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="ctrl.render()"></select>
     </div>
   </div>
 </div>
@@ -64,10 +64,10 @@
   <div class="section">
 		<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
 		<div class="tight-form-container">
-			<div class="tight-form" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
+			<div class="tight-form" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
 				<ul class="tight-form-list">
 					<li class="tight-form-item">
-						<i class="fa fa-remove pointer" ng-click="removeSeriesOverride(override)"></i>
+						<i class="fa fa-remove pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
 					</li>
 
 					<li class="tight-form-item">
@@ -75,7 +75,7 @@
 					</li>
 
 					<li>
-						<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
+						<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="ctrl.render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
 					</li>
 
 					<li class="tight-form-item" ng-repeat="option in currentOverrides">
@@ -95,7 +95,7 @@
 			</div>
 		</div>
 
-		<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addSeriesOverride()">
+		<button class="btn btn-inverse" style="margin-top: 20px" ng-click="ctrl.addSeriesOverride()">
 			Add series specific option
 		</button>
 	</div>