瀏覽代碼

Graph: shared multi series tooltip, refactoring PR #850

Torkel Ödegaard 11 年之前
父節點
當前提交
2473ae3b47

+ 7 - 110
src/app/directives/grafanaGraph.js

@@ -3,9 +3,10 @@ define([
   'jquery',
   'kbn',
   'moment',
-  'lodash'
+  'lodash',
+  './grafanaGraph.tooltip'
 ],
-function (angular, $, kbn, moment, _) {
+function (angular, $, kbn, moment, _, graphTooltip) {
   'use strict';
 
   var module = angular.module('grafana.directives');
@@ -15,8 +16,8 @@ function (angular, $, kbn, moment, _) {
       restrict: 'A',
       template: '<div> </div>',
       link: function(scope, elem) {
-        var data, annotations;
         var dashboard = scope.dashboard;
+        var data, annotations;
         var legendSideLastValue = null;
 
         scope.$on('refresh',function() {
@@ -174,7 +175,7 @@ function (angular, $, kbn, moment, _) {
 
           function callPlot() {
             try {
-              elem.flot=$.plot(elem, sortedSeries, options);
+              $.plot(elem, sortedSeries, options);
             } catch (e) {
               console.log('flotcharts error', e);
             }
@@ -343,112 +344,6 @@ function (angular, $, kbn, moment, _) {
           return "%H:%M";
         }
 
-        var $tooltip = $('<div id="tooltip">');
-
-        //this event will erase tooltip and crosshair once leaved the graph
-        elem.mouseleave(function () {
-          console.log('onmouse out:');
-          if(scope.panel.tooltip.shared) {
-            $tooltip.detach();
-            elem.flot.clearCrosshair();
-            elem.flot.unhighlight();
-          }
-        });
-
-        elem.bind("plothover", function (event, pos, item) {
-          var group, value, timestamp, seriesInfo, format, i, j, s, s_final;
-
-          //if tooltip shared we'll show a crosshair and will look for X and all Y series values
-          //else we will take from item.
-          if(scope.panel.tooltip.shared){
-            //unhighligh previous points.
-            elem.flot.unhighlight();
-            //check if all series has same length if so, only one x index will
-            //be checked and only for exact timestamp values
-            var l = [];
-            var series;
-            for (i = 0; i < data.length; ++i) {
-              series = data[i];
-              l.push(series.data.length);
-            }
-            //if all series has the same length it is because of they share time axis
-            if(_.uniq(l).length === 1) {
-              s='';
-              series = data[0];
-              for(j=0;j<series.data.length;j++) {
-                if(series.data[j][0] > pos.x){
-                  break;
-                }
-              }
-              if(j>0) {
-                j--; //we take previous value in time.
-              }
-              //now we know the current X (j) position for X and Y values
-              timestamp = dashboard.formatDate(series.data[j][0]);
-              var last_value=0; //needed for stacked values
-              for (i = data.length-1; i >= 0; --i) {
-                //stacked values should be added in reverse order
-                series = data[i];
-                seriesInfo = series.info;
-                format = scope.panel.y_formats[seriesInfo.yaxis - 1];
-                if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
-                  value = series.data[j][1];
-                } else {
-                  last_value+=series.data[j][1];
-                  value = last_value;
-                }
-                value = kbn.getFormatFunction(format, 2)(value,series.yaxis);
-                if (seriesInfo.alias) {
-                  group = '<i class="icon-circle" style="color:'+series.color+';"></i>' +
-                          ' ' +  seriesInfo.alias;
-                } else {
-                  group = kbn.query_color_dot(series.color, 15) + ' ';
-                }
-                //pre-pending new values
-                s_final= group+ ": <b>"+value +'</b><br>'+ s;
-                s=s_final;
-                //higligth point
-                elem.flot.highlight(i,j);
-              }
-
-              $tooltip.html('<small style="font-size:0.7em;">Time@ <b>'+
-                            timestamp + '</b><br><hr>' + s + '</small>').place_tt(pos.pageX, pos.pageY);
-              return;
-            }else {
-              console.log('WARNING: tootltip shared can not be shown becouse of from '
-                          +data.length+' series has different length '+_.uniq(l));
-              $tooltip.detach();
-            }
-          }
-          if (item) {
-            seriesInfo = item.series.info;
-            format = scope.panel.y_formats[seriesInfo.yaxis - 1];
-
-            if (seriesInfo.alias) {
-              group = '<small style="font-size:0.9em;">' +
-                '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
-                seriesInfo.alias +
-              '</small><br>';
-            } else {
-              group = kbn.query_color_dot(item.series.color, 15) + ' ';
-            }
-
-            if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
-              value = item.datapoint[1] - item.datapoint[2];
-            }
-            else {
-              value = item.datapoint[1];
-            }
-
-            value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
-            timestamp = dashboard.formatDate(item.datapoint[0]);
-
-            $tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
-          } else {
-            $tooltip.detach();
-          }
-        });
-
         function render_panel_as_graphite_png(url) {
           url += '&width=' + elem.width();
           url += '&height=' + elem.css('height').replace('px', '');
@@ -499,6 +394,8 @@ function (angular, $, kbn, moment, _) {
           elem.html('<img src="' + url + '"></img>');
         }
 
+        graphTooltip.register(elem, dashboard, scope);
+
         elem.bind("plotselected", function (event, ranges) {
           scope.$apply(function() {
             timeSrv.setTime({

+ 122 - 0
src/app/directives/grafanaGraph.tooltip.js

@@ -0,0 +1,122 @@
+define([
+  'jquery',
+  'kbn',
+],
+function ($, kbn) {
+  'use strict';
+
+  function registerTooltipFeatures(elem, dashboard, scope) {
+
+    var $tooltip = $('<div id="tooltip">');
+
+    elem.mouseleave(function () {
+      if(scope.panel.tooltip.shared) {
+        var plot = elem.data().plot;
+        $tooltip.detach();
+        plot.clearCrosshair();
+        plot.unhighlight();
+      }
+    });
+
+    function findHoverIndex(posX, series) {
+      for (var j = 0; j < series.data.length; j++) {
+        if (series.data[j][0] > posX) {
+          return Math.max(j - 1,  0);
+        }
+      }
+      return j - 1;
+    }
+
+    elem.bind("plothover", function (event, pos, item) {
+      var plot = elem.data().plot;
+      var data = plot.getData();
+      var group, value, timestamp, seriesInfo, format, i, series, hoverIndex, seriesHtml;
+
+      if (scope.panel.tooltip.shared) {
+        plot.unhighlight();
+
+        //check if all series has same length if so, only one x index will
+        //be checked and only for exact timestamp values
+        var pointCount = data[0].data.length;
+        for (i = 1; i < data.length; i++) {
+          if (data[i].data.length !== pointCount) {
+            console.log('WARNING: tootltip shared can not be shown becouse of series points do not align, different point counts');
+            $tooltip.detach();
+            return;
+          }
+        }
+
+        seriesHtml = '';
+        series = data[0];
+        hoverIndex = findHoverIndex(pos.x, series);
+
+        //now we know the current X (j) position for X and Y values
+        timestamp = dashboard.formatDate(series.data[hoverIndex][0]);
+        var last_value = 0; //needed for stacked values
+
+        for (i = data.length-1; i >= 0; --i) {
+          //stacked values should be added in reverse order
+          series = data[i];
+          seriesInfo = series.info;
+          format = scope.panel.y_formats[seriesInfo.yaxis - 1];
+
+          if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
+            value = series.data[hoverIndex][1];
+          } else {
+            last_value += series.data[hoverIndex][1];
+            value = last_value;
+          }
+
+          value = kbn.valueFormats[format](value, series.yaxis.tickDecimals);
+
+          if (seriesInfo.alias) {
+            group = '<i class="icon-minus" style="color:' + series.color +';"></i> ' + seriesInfo.alias;
+          } else {
+            group = kbn.query_color_dot(series.color, 15) + ' ';
+          }
+
+          //pre-pending new values
+          seriesHtml = group + ': <span class="graph-tooltip-value">' + value + '</span><br>' + seriesHtml;
+
+          plot.highlight(i, hoverIndex);
+        }
+
+        $tooltip.html('<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ timestamp + '</div> ' + seriesHtml + '</div>')
+          .place_tt(pos.pageX + 20, pos.pageY);
+        return;
+      }
+      if (item) {
+        seriesInfo = item.series.info;
+        format = scope.panel.y_formats[seriesInfo.yaxis - 1];
+
+        if (seriesInfo.alias) {
+          group = '<small style="font-size:0.9em;">' +
+            '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
+            seriesInfo.alias +
+            '</small><br>';
+        } else {
+          group = kbn.query_color_dot(item.series.color, 15) + ' ';
+        }
+
+        if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
+          value = item.datapoint[1] - item.datapoint[2];
+        }
+        else {
+          value = item.datapoint[1];
+        }
+
+        value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
+        timestamp = dashboard.formatDate(item.datapoint[0]);
+
+        $tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
+      } else {
+        $tooltip.detach();
+      }
+    });
+
+  }
+
+  return {
+    register: registerTooltipFeatures
+  };
+});

+ 1 - 1
src/app/panels/graph/module.js

@@ -161,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
 
       tooltip       : {
         value_type: 'cumulative',
-        query_as_alias: true
+        shared: false,
       },
 
       targets: [{}],

+ 16 - 0
src/css/less/graph.less

@@ -166,3 +166,19 @@
     float: left;
   }
 }
+
+.graph-tooltip {
+  .graph-tooltip-time {
+    text-align: center;
+    font-weight: bold;
+    position: relative;
+    top: -3px;
+  }
+
+  .graph-tooltip-value {
+    font-weight: bold;
+    float: right;
+    padding-left: 10px;
+  }
+
+}

+ 56 - 0
src/test/specs/graph-tooltip-specs.js

@@ -0,0 +1,56 @@
+define([
+  'jquery',
+  'directives/grafanaGraph.tooltip'
+], function($, tooltip) {
+  'use strict';
+
+  describe('graph tooltip', function() {
+    var elem = $('<div></div>');
+    var dashboard = {
+      formatDate: sinon.stub().returns('date'),
+    };
+    var scope =  {
+      panel: {
+        tooltip:  {
+          shared: true
+        },
+        y_formats: ['ms', 'none'],
+      }
+    };
+
+    var data = [
+      {
+        data: [[10,10], [12,20]],
+        info: { yaxis: 1 },
+        yaxis: { tickDecimals: 2 },
+      },
+      {
+        data: [[10,10], [12,20]],
+        info: { yaxis: 1 },
+        yaxis: { tickDecimals: 2 },
+      }
+    ];
+
+    var plot = {
+      getData: sinon.stub().returns(data),
+      highlight: sinon.stub(),
+      unhighlight: sinon.stub()
+    };
+
+    elem.data('plot', plot);
+
+    beforeEach(function() {
+      tooltip.register(elem, dashboard, scope);
+      elem.trigger('plothover', [{}, {x: 13}, {}]);
+    });
+
+    it('should add tooltip', function() {
+      var tooltipHtml = $(".graph-tooltip").text();
+      expect(tooltipHtml).to.be('date  : 40.00 ms : 20.00 ms');
+    });
+
+  });
+
+});
+
+

+ 1 - 0
src/test/test-main.js

@@ -129,6 +129,7 @@ require([
     'specs/influxdb-datasource-specs',
     'specs/graph-ctrl-specs',
     'specs/grafanaGraph-specs',
+    'specs/graph-tooltip-specs',
     'specs/seriesOverridesCtrl-specs',
     'specs/timeSrv-specs',
     'specs/templateSrv-specs',