Sfoglia il codice sorgente

added shared tooltips to graphs

toni-moreno 11 anni fa
parent
commit
f59bb6461a

+ 2 - 0
src/app/components/require.config.js

@@ -40,6 +40,7 @@ require.config({
     'jquery.flot.stack':      '../vendor/jquery/jquery.flot.stack',
     'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
     'jquery.flot.time':       '../vendor/jquery/jquery.flot.time',
+    'jquery.flot.crosshair':  '../vendor/jquery/jquery.flot.crosshair',
 
     modernizr:                '../vendor/modernizr-2.6.1',
 
@@ -83,6 +84,7 @@ require.config({
     'jquery.flot.stack':    ['jquery', 'jquery.flot'],
     'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
     'jquery.flot.time':     ['jquery', 'jquery.flot'],
+    'jquery.flot.crosshair':['jquery', 'jquery.flot'],
     'angular-cookies':      ['angular'],
     'angular-dragdrop':     ['jquery','jquery-ui','angular'],
     'angular-loader':       ['angular'],

+ 69 - 2
src/app/directives/grafanaGraph.js

@@ -130,6 +130,9 @@ function (angular, $, kbn, moment, _) {
             selection: {
               mode: "x",
               color: '#666'
+            },
+            crosshair: {
+              mode: panel.tooltip.shared ? "x" : null
             }
           };
 
@@ -157,7 +160,7 @@ function (angular, $, kbn, moment, _) {
 
           function callPlot() {
             try {
-              $.plot(elem, sortedSeries, options);
+              elem.flot=$.plot(elem, sortedSeries, options);
             } catch (e) {
               console.log('flotcharts error', e);
             }
@@ -326,9 +329,73 @@ function (angular, $, kbn, moment, _) {
 
         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.bind("plothover", function (event, pos, item) {
-          var group, value, timestamp, seriesInfo, format;
+          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){
+            //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];
+              j=0;
+              do {
+                ++j;
+              } while (series.data[j][0] < pos.x);
+              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+ ": "+value +'<br>'+ s;
+                s=s_final;
+              }
 
+              $tooltip.html('<small style="font-size:0.7em;">Time@ <b>'+
+                            timestamp + '</b><br>' + 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];

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

@@ -15,7 +15,8 @@ define([
   'jquery.flot.selection',
   'jquery.flot.time',
   'jquery.flot.stack',
-  'jquery.flot.stackpercent'
+  'jquery.flot.stackpercent',
+  'jquery.flot.crosshair'
 ],
 function (angular, app, $, _, kbn, moment, TimeSeries) {
   'use strict';

+ 8 - 0
src/app/panels/graph/styleEditor.html

@@ -61,8 +61,16 @@
       <input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
     </div>
   </div>
+
+  <div class="section">
+    <h5>Tooltip</h5>
+    <div class="editor-option">
+      <label class="small">shared <tip> Show all series values on the same time in the same tooltip and a x croshair to help follow all series</tip> </label><input type="checkbox" ng-model="panel.tooltip.shared" ng-checked="panel.tooltip.shared" ng-change="render()">
+    </div>
+  </div>
 </div>
 
+
 <div class="editor-row">
   <div class="section">
 		<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>

+ 4 - 1
src/test/specs/grafanaGraph-specs.js

@@ -27,7 +27,10 @@ define([
               legend: {},
               grid: {},
               y_formats: [],
-              seriesOverrides: []
+              seriesOverrides: [],
+	      tooltip: {
+                shared: true
+              }
             };
             scope.hiddenSeries = {};
             scope.dashboard = { timezone: 'browser' };

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

@@ -43,6 +43,7 @@ require.config({
     'jquery.flot.stack':      '../vendor/jquery/jquery.flot.stack',
     'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
     'jquery.flot.time':       '../vendor/jquery/jquery.flot.time',
+    'jquery.flot.crosshair':  '../vendor/jquery/jquery.flot.crosshair',
 
     modernizr:                '../vendor/modernizr-2.6.1',
   },
@@ -77,6 +78,7 @@ require.config({
     'jquery.flot.stack':    ['jquery', 'jquery.flot'],
     'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
     'jquery.flot.time':     ['jquery', 'jquery.flot'],
+    'jquery.flot.crosshair':['jquery', 'jquery.flot'],
 
     'angular-route':        ['angular'],
     'angular-cookies':      ['angular'],

+ 176 - 0
src/vendor/jquery/jquery.flot.crosshair.js

@@ -0,0 +1,176 @@
+/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+	crosshair: {
+		mode: null or "x" or "y" or "xy"
+		color: color
+		lineWidth: number
+	}
+
+Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
+crosshair that lets you trace the values on the x axis, "y" enables a
+horizontal crosshair and "xy" enables them both. "color" is the color of the
+crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
+the drawn lines (default is 1).
+
+The plugin also adds four public methods:
+
+  - setCrosshair( pos )
+
+    Set the position of the crosshair. Note that this is cleared if the user
+    moves the mouse. "pos" is in coordinates of the plot and should be on the
+    form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
+    axes), which is coincidentally the same format as what you get from a
+    "plothover" event. If "pos" is null, the crosshair is cleared.
+
+  - clearCrosshair()
+
+    Clear the crosshair.
+
+  - lockCrosshair(pos)
+
+    Cause the crosshair to lock to the current location, no longer updating if
+    the user moves the mouse. Optionally supply a position (passed on to
+    setCrosshair()) to move it to.
+
+    Example usage:
+
+	var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
+	$("#graph").bind( "plothover", function ( evt, position, item ) {
+		if ( item ) {
+			// Lock the crosshair to the data point being hovered
+			myFlot.lockCrosshair({
+				x: item.datapoint[ 0 ],
+				y: item.datapoint[ 1 ]
+			});
+		} else {
+			// Return normal crosshair operation
+			myFlot.unlockCrosshair();
+		}
+	});
+
+  - unlockCrosshair()
+
+    Free the crosshair to move again after locking it.
+*/
+
+(function ($) {
+    var options = {
+        crosshair: {
+            mode: null, // one of null, "x", "y" or "xy",
+            color: "rgba(170, 0, 0, 0.80)",
+            lineWidth: 1
+        }
+    };
+    
+    function init(plot) {
+        // position of crosshair in pixels
+        var crosshair = { x: -1, y: -1, locked: false };
+
+        plot.setCrosshair = function setCrosshair(pos) {
+            if (!pos)
+                crosshair.x = -1;
+            else {
+                var o = plot.p2c(pos);
+                crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
+                crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
+            }
+            
+            plot.triggerRedrawOverlay();
+        };
+        
+        plot.clearCrosshair = plot.setCrosshair; // passes null for pos
+        
+        plot.lockCrosshair = function lockCrosshair(pos) {
+            if (pos)
+                plot.setCrosshair(pos);
+            crosshair.locked = true;
+        };
+
+        plot.unlockCrosshair = function unlockCrosshair() {
+            crosshair.locked = false;
+        };
+
+        function onMouseOut(e) {
+            if (crosshair.locked)
+                return;
+
+            if (crosshair.x != -1) {
+                crosshair.x = -1;
+                plot.triggerRedrawOverlay();
+            }
+        }
+
+        function onMouseMove(e) {
+            if (crosshair.locked)
+                return;
+                
+            if (plot.getSelection && plot.getSelection()) {
+                crosshair.x = -1; // hide the crosshair while selecting
+                return;
+            }
+                
+            var offset = plot.offset();
+            crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
+            crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
+            plot.triggerRedrawOverlay();
+        }
+        
+        plot.hooks.bindEvents.push(function (plot, eventHolder) {
+            if (!plot.getOptions().crosshair.mode)
+                return;
+
+            eventHolder.mouseout(onMouseOut);
+            eventHolder.mousemove(onMouseMove);
+        });
+
+        plot.hooks.drawOverlay.push(function (plot, ctx) {
+            var c = plot.getOptions().crosshair;
+            if (!c.mode)
+                return;
+
+            var plotOffset = plot.getPlotOffset();
+            
+            ctx.save();
+            ctx.translate(plotOffset.left, plotOffset.top);
+
+            if (crosshair.x != -1) {
+                var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
+
+                ctx.strokeStyle = c.color;
+                ctx.lineWidth = c.lineWidth;
+                ctx.lineJoin = "round";
+
+                ctx.beginPath();
+                if (c.mode.indexOf("x") != -1) {
+                    var drawX = Math.floor(crosshair.x) + adj;
+                    ctx.moveTo(drawX, 0);
+                    ctx.lineTo(drawX, plot.height());
+                }
+                if (c.mode.indexOf("y") != -1) {
+                    var drawY = Math.floor(crosshair.y) + adj;
+                    ctx.moveTo(0, drawY);
+                    ctx.lineTo(plot.width(), drawY);
+                }
+                ctx.stroke();
+            }
+            ctx.restore();
+        });
+
+        plot.hooks.shutdown.push(function (plot, eventHolder) {
+            eventHolder.unbind("mouseout", onMouseOut);
+            eventHolder.unbind("mousemove", onMouseMove);
+        });
+    }
+    
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'crosshair',
+        version: '1.0'
+    });
+})(jQuery);