Преглед изворни кода

Merge branch 'issue-5812-x-axis' of https://github.com/alexanderzobnin/grafana into graph-panel-non-timeseries

Torkel Ödegaard пре 9 година
родитељ
комит
9d69eaf07b

+ 79 - 3
public/app/plugins/panel/graph/graph.js

@@ -237,6 +237,23 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
             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.seriesValue]]
+              ];
+            } 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 = [];
@@ -244,11 +261,34 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
             }
           }
 
-          if (data.length && data[0].stats.timeStep) {
-            options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
+          if (panel.xaxis.mode === 'series') {
+            if (data.length) {
+              options.series.bars.barWidth = 0.7;
+              options.series.bars.align = 'center';
+              options.series.bars.show = true;
+              options.series.points.show = false;
+              options.series.lines.show = false;
+            }
+
+            addXSeriesAxis(options);
+
+          } else if (panel.xaxis.mode === 'table' ||
+                     panel.xaxis.mode === 'elastic') {
+            if (data.length) {
+              options.series.bars.barWidth = 0.7;
+              options.series.bars.align = 'center';
+            }
+
+            addXTableAxis(options);
+
+          } else {
+            if (data.length && data[0].stats.timeStep) {
+              options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
+            }
+
+            addTimeAxis(options);
           }
 
-          addTimeAxis(options);
           thresholdManager.addPlotOptions(options, panel);
           addAnnotations(options);
           configureAxisOptions(data, options);
@@ -315,6 +355,42 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
           };
         }
 
+        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;

+ 128 - 7
public/app/plugins/panel/graph/module.ts

@@ -22,6 +22,9 @@ class GraphCtrl extends MetricsPanelCtrl {
   seriesList: any = [];
   logScales: any;
   unitFormats: any;
+  xAxisModes: any;
+  xAxisSeriesValues: any;
+  xAxisColumns: any = [];
   annotationsPromise: any;
   datapointsCount: number;
   datapointsOutside: boolean;
@@ -53,7 +56,9 @@ class GraphCtrl extends MetricsPanelCtrl {
       }
     ],
     xaxis: {
-      show: true
+      show: true,
+      mode: 'time',
+      seriesValue: 'avg'
     },
     alert: {
       warn: {op: '>', value: undefined},
@@ -117,6 +122,7 @@ class GraphCtrl extends MetricsPanelCtrl {
     _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
     _.defaults(this.panel.alert, this.panelDefaults.alert);
     _.defaults(this.panel.legend, this.panelDefaults.legend);
+    _.defaults(this.panel.xaxis, this.panelDefaults.xaxis);
 
     this.colors = $scope.$root.colors;
 
@@ -144,8 +150,16 @@ class GraphCtrl extends MetricsPanelCtrl {
       'log (base 32)': 32,
       'log (base 1024)': 1024
     };
-
     this.unitFormats = kbn.getUnitFormats();
+
+    this.xAxisModes = {
+      'Time': 'time',
+      'Series': 'series',
+      'Table': 'table',
+      'Elastic Raw Doc': 'elastic'
+    };
+
+    this.xAxisSeriesValues = ['min', 'max', 'avg', 'current', 'total'];
     this.subTabIndex = 0;
   }
 
@@ -183,7 +197,40 @@ class GraphCtrl extends MetricsPanelCtrl {
     this.datapointsWarning = false;
     this.datapointsCount = 0;
     this.datapointsOutside = false;
-    this.seriesList = dataList.map(this.seriesHandler.bind(this));
+
+    let dataHandler: (seriesData, index)=>any;
+    if (this.panel.xaxis.mode === 'table') {
+      if (dataList.length) {
+        // Table panel uses only first enabled tagret, so we can use dataList[0]
+        // for table data representation
+        dataList.splice(1, dataList.length - 1);
+        this.xAxisColumns = _.map(dataList[0].columns, (column, index) => {
+          return {
+            text: column.text,
+            index: index
+          };
+        });
+
+        // Set last column as default value
+        if (!this.panel.xaxis.valueColumnIndex) {
+          this.panel.xaxis.valueColumnIndex = this.xAxisColumns.length - 1;
+        }
+      }
+
+      dataHandler = this.tableHandler;
+    } else if (this.panel.xaxis.mode === 'elastic') {
+      if (dataList.length) {
+        dataList.splice(1, dataList.length - 1);
+        var point = _.first(dataList[0].datapoints);
+        this.xAxisColumns = getFieldsFromESDoc(point);
+      }
+
+      dataHandler = this.esRawDocHandler;
+    } else {
+      dataHandler = this.timeSeriesHandler;
+    }
+
+    this.seriesList = dataList.map(dataHandler.bind(this));
     this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
 
     this.annotationsPromise.then(annotations => {
@@ -196,9 +243,7 @@ class GraphCtrl extends MetricsPanelCtrl {
     });
   }
 
-  seriesHandler(seriesData, index) {
-    var datapoints = seriesData.datapoints;
-    var alias = seriesData.target;
+  seriesHandler(seriesData, index, datapoints, alias) {
     var colorIndex = index % this.colors.length;
     var color = this.panel.aliasColors[alias] || this.colors[colorIndex];
 
@@ -220,10 +265,52 @@ class GraphCtrl extends MetricsPanelCtrl {
       this.panel.tooltip.msResolution = this.panel.tooltip.msResolution || series.isMsResolutionNeeded();
     }
 
-
     return series;
   }
 
+  timeSeriesHandler(seriesData, index) {
+    var datapoints = seriesData.datapoints;
+    var alias = seriesData.target;
+
+    return this.seriesHandler(seriesData, index, datapoints, alias);
+  }
+
+  tableHandler(seriesData, index) {
+    var xColumnIndex = Number(this.panel.xaxis.columnIndex);
+    var valueColumnIndex = Number(this.panel.xaxis.valueColumnIndex);
+    var datapoints = _.map(seriesData.rows, (row) => {
+      var value = valueColumnIndex ? row[valueColumnIndex] : _.last(row);
+      return [
+        value,             // Y value
+        row[xColumnIndex]  // X value
+      ];
+    });
+
+    var alias = seriesData.columns[valueColumnIndex].text;
+
+    return this.seriesHandler(seriesData, index, datapoints, alias);
+  }
+
+  esRawDocHandler(seriesData, index) {
+    let xField = this.panel.xaxis.esField;
+    let valueField = this.panel.xaxis.esValueField;
+    let datapoints = _.map(seriesData.datapoints, (doc) => {
+      return [
+        pluckDeep(doc, valueField),  // Y value
+        pluckDeep(doc, xField)       // X value
+      ];
+    });
+
+    // Remove empty points
+    datapoints = _.filter(datapoints, (point) => {
+      return point[0] !== undefined;
+    });
+
+    var alias = valueField;
+
+    return this.seriesHandler(seriesData, index, datapoints, alias);
+  }
+
   onRender() {
     if (!this.seriesList) { return; }
 
@@ -330,4 +417,38 @@ class GraphCtrl extends MetricsPanelCtrl {
 
 }
 
+function getFieldsFromESDoc(doc) {
+  let fields = [];
+  let fieldNameParts = [];
+
+  function getFieldsRecursive(obj) {
+    _.forEach(obj, (value, key) => {
+      if (_.isObject(value)) {
+        fieldNameParts.push(key);
+        getFieldsRecursive(value);
+      } else {
+        let field = fieldNameParts.concat(key).join('.');
+        fields.push(field);
+      }
+    });
+    fieldNameParts.pop();
+  }
+
+  getFieldsRecursive(doc);
+  return fields;
+}
+
+function pluckDeep(obj: any, property: string) {
+  let propertyParts = property.split('.');
+  let value = obj;
+  for (let i = 0; i < propertyParts.length; ++i) {
+    if (value[propertyParts[i]]) {
+      value = value[propertyParts[i]];
+    } else {
+      return undefined;
+    }
+  }
+  return value;
+}
+
 export {GraphCtrl, GraphCtrl as PanelCtrl}

+ 69 - 0
public/app/plugins/panel/graph/tab_axes.html

@@ -40,6 +40,75 @@
 	<div class="section gf-form-group">
 		<h5 class="section-heading">X-Axis</h5>
 		<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
+
+		<div class="gf-form">
+			<label class="gf-form-label width-5">Mode</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.mode"
+					ng-options="v as k for (k, v) in ctrl.xAxisModes"
+					ng-change="ctrl.refresh()">
+				</select>
+			</div>
+		</div>
+
+		<!-- Series mode -->
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
+			<label class="gf-form-label width-5">Value</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.seriesValue"
+					ng-options="v for v in ctrl.xAxisSeriesValues"
+					ng-change="ctrl.render()">
+				</select>
+			</div>
+		</div>
+
+		<!-- Table mode -->
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'table'">
+			<label class="gf-form-label width-5">Column</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.columnIndex"
+					ng-options="col.index as col.text for col in ctrl.xAxisColumns"
+					ng-change="ctrl.refresh()">
+				</select>
+			</div>
+		</div>
+
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'table'">
+			<label class="gf-form-label width-5">Value</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.valueColumnIndex"
+					ng-options="col.index as col.text for col in ctrl.xAxisColumns"
+					ng-change="ctrl.refresh()">
+				</select>
+			</div>
+		</div>
+
+		<!-- Elastic Raw Document mode -->
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'elastic'">
+			<label class="gf-form-label width-5">Column</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.esField"
+					ng-options="field for field in ctrl.xAxisColumns"
+					ng-change="ctrl.refresh()">
+				</select>
+			</div>
+		</div>
+
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'elastic'">
+			<label class="gf-form-label width-5">Value</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.panel.xaxis.esValueField"
+					ng-options="field for field in ctrl.xAxisColumns"
+					ng-change="ctrl.refresh()">
+				</select>
+			</div>
+		</div>
 	</div>
 
 </div>