Просмотр исходного кода

feat(annotations): updated flot events lib and refactored it to use tether-drop lib

Torkel Ödegaard 9 лет назад
Родитель
Сommit
a6a5f393cc

+ 4 - 2
public/app/core/directives/annotation_tooltip.js

@@ -25,7 +25,8 @@ function ($, _, coreModule) {
         var dashboard = dashboardSrv.getCurrent();
         var time = '<i>' + dashboard.formatDate(event.min) + '</i>';
 
-        var tooltip = '<div class="graph-tooltip small"><div class="graph-tooltip-time">' + title + ' ' + time + '</div> ' ;
+        var tooltip = '<div class="graph-annotation">';
+        tooltip += '<div class="graph-annotation-title">' + title + "</div>";
 
         if (event.text) {
           var text = sanitizeString(event.text);
@@ -42,9 +43,10 @@ function ($, _, coreModule) {
 
         if (tags && tags.length) {
           scope.tags = tags;
-          tooltip += '<span class="label label-tag" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>';
+          tooltip += '<span class="label label-tag small" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>';
         }
 
+        tooltip += '<div class="graph-annotation-time">' + time + '</div>' ;
         tooltip += "</div>";
 
         var $tooltip = $(tooltip);

+ 8 - 8
public/app/plugins/datasource/influxdb/partials/annotations.editor.html

@@ -1,26 +1,26 @@
 <div class="gf-form-group">
-	<div class="gf-form-inline">
-		<div class="gf-form">
-			<span class="gf-form-label width-14">InfluxDB Query <tip>Example: select text from events where $timeFilter</tip></span class="gf-form-label">
+	<div class="gf-form">
+		<span class="gf-form-label width-10">InfluxDB Query <tip>Example: select text from events where $timeFilter</tip></span class="gf-form-label">
 			<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="select text from events where $timeFilter"></input>
 		</div>
 	</div>
 </div>
+
 <div class="gf-form-group">
-  <h6>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h6>
-  <div class="gf-form-inline">
+	<h6>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h6>
+	<div class="gf-form-inline">
 		<div class="gf-form">
-			<span class="gf-form-label width-10">Title</span>
+			<span class="gf-form-label width-4">Title</span>
 			<input type="text" class="gf-form-input" ng-model='ctrl.annotation.titleColumn' placeholder=""></input>
 		</div>
 
 		<div class="gf-form">
-			<span class="gf-form-label width-10">Tags</span>
+			<span class="gf-form-label width-4">Tags</span>
 			<input type="text" class="gf-form-input" ng-model='ctrl.annotation.tagsColumn' placeholder=""></input>
 		</div>
 
 		<div class="gf-form">
-			<span class="gf-form-label width-10">Text</span>
+			<span class="gf-form-label width-4">Text</span>
 			<input type="text" class="gf-form-input" ng-model='ctrl.annotation.textColumn' placeholder=""></input>
 		</div>
 	</div>

+ 12 - 16
public/app/plugins/panel/graph/graph.js

@@ -6,13 +6,13 @@ define([
   'app/core/utils/kbn',
   './graph_tooltip',
   'jquery.flot',
-  'jquery.flot.events',
   'jquery.flot.selection',
   'jquery.flot.time',
   'jquery.flot.stack',
   'jquery.flot.stackpercent',
   'jquery.flot.fillbelow',
-  'jquery.flot.crosshair'
+  'jquery.flot.crosshair',
+  './jquery.flot.events',
 ],
 function (angular, $, moment, _, kbn, GraphTooltip) {
   'use strict';
@@ -333,28 +333,24 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
           _.each(annotations, function(event) {
             if (!types[event.annotation.name]) {
               types[event.annotation.name] = {
-                level: _.keys(types).length + 1,
-                icon: {
-                  icon: "fa fa-chevron-down",
-                  size: event.annotation.iconSize,
-                  color: event.annotation.iconColor,
-                }
+                color: event.annotation.iconColor,
+                position: 'BOTTOM',
               };
             }
 
-            if (event.annotation.showLine) {
-              options.grid.markings.push({
-                color: event.annotation.lineColor,
-                lineWidth: 1,
-                xaxis: { from: event.min, to: event.max }
-              });
-            }
+            // if (event.annotation.showLine) {
+            //   options.grid.markings.push({
+            //     color: event.annotation.lineColor,
+            //     lineWidth: 1,
+            //     xaxis: { from: event.min, to: event.max }
+            //   });
+            // }
           });
 
           options.events = {
             levels: _.keys(types).length + 1,
             data: annotations,
-            types: types
+            types: types,
           };
         }
 

+ 399 - 0
public/app/plugins/panel/graph/jquery.flot.events.js

@@ -0,0 +1,399 @@
+define([
+  'jquery',
+  'lodash',
+  'angular',
+  'tether-drop',
+],
+function ($, _, angular, Drop) {
+  'use strict';
+
+  function createAnnotationToolip(element, event) {
+    var injector = angular.element(document).injector();
+    var content = document.createElement('div');
+    content.innerHTML = '<annotation-tooltip></annotation-tooltip>';
+
+    injector.invoke(["$compile", "$rootScope", function($compile, $rootScope) {
+      var tmpScope = $rootScope.$new(true);
+      tmpScope.event = event;
+
+      $compile(content)(tmpScope);
+      tmpScope.$digest();
+      tmpScope.$destroy();
+
+      var drop = new Drop({
+        target: element[0],
+        content: content,
+        position: "bottom center",
+        classes: 'drop-popover',
+        openOn: 'hover',
+        hoverCloseDelay: 200,
+        tetherOptions: {
+          constraints: [{to: 'window', pin: true, attachment: "both"}]
+        }
+      });
+
+      drop.open();
+
+      drop.on('close', function() {
+        setTimeout(function() {
+          drop.destroy();
+        });
+      });
+    }]);
+  }
+
+  /*
+   * jquery.flot.events
+   *
+   * description: Flot plugin for adding events/markers to the plot
+   * version: 0.2.5
+   * authors:
+   *    Alexander Wunschik <alex@wunschik.net>
+   *    Joel Oughton <joeloughton@gmail.com>
+   *    Nicolas Joseph <www.nicolasjoseph.com>
+   *
+   * website: https://github.com/mojoaxel/flot-events
+   *
+   * released under MIT License and GPLv2+
+   */
+
+  /**
+   * A class that allows for the drawing an remove of some object
+   */
+  var DrawableEvent = function(object, drawFunc, clearFunc, moveFunc, left, top, width, height) {
+    var _object = object;
+    var	_drawFunc = drawFunc;
+    var	_clearFunc = clearFunc;
+    var	_moveFunc = moveFunc;
+    var	_position = { left: left, top: top };
+    var	_width = width;
+    var	_height = height;
+
+    this.width = function() { return _width; };
+    this.height = function() { return _height; };
+    this.position = function() { return _position; };
+    this.draw = function() { _drawFunc(_object); };
+    this.clear = function() { _clearFunc(_object); };
+    this.getObject = function() { return _object; };
+    this.moveTo = function(position) {
+      _position = position;
+      _moveFunc(_object, _position);
+    };
+  };
+
+  /**
+   * Event class that stores options (eventType, min, max, title, description) and the object to draw.
+   */
+  var VisualEvent = function(options, drawableEvent) {
+    var _parent;
+    var _options = options;
+    var _drawableEvent = drawableEvent;
+    var _hidden = false;
+
+    this.visual = function() { return _drawableEvent; };
+    this.getOptions = function() { return _options; };
+    this.getParent = function() { return _parent; };
+    this.isHidden = function() { return _hidden; };
+    this.hide = function() { _hidden = true; };
+    this.unhide = function() { _hidden = false; };
+  };
+
+  /**
+   * A Class that handles the event-markers inside the given plot
+   */
+  var EventMarkers = function(plot) {
+    var _events = [];
+
+    this._types = [];
+    this._plot = plot;
+    this.eventsEnabled = false;
+
+    this.getEvents = function() {
+      return _events;
+    };
+
+    this.setTypes = function(types) {
+      return this._types = types;
+    };
+
+    /**
+     * create internal objects for the given events
+     */
+    this.setupEvents = function(events) {
+      var that = this;
+      $.each(events, function(index, event) {
+        var ve = new VisualEvent(event, that._buildDiv(event));
+        _events.push(ve);
+      });
+
+      _events.sort(function(a, b) {
+        var ao = a.getOptions(), bo = b.getOptions();
+        if (ao.min > bo.min) { return 1; }
+        if (ao.min < bo.min) { return -1; }
+        return 0;
+      });
+    };
+
+    /**
+     * draw the events to the plot
+     */
+    this.drawEvents = function() {
+      var that = this;
+      // var o = this._plot.getPlotOffset();
+
+      $.each(_events, function(index, event) {
+        // check event is inside the graph range
+        if (that._insidePlot(event.getOptions().min) && !event.isHidden()) {
+          event.visual().draw();
+        }  else {
+          event.visual().getObject().hide();
+        }
+      });
+    };
+
+    /**
+     * update the position of the event-markers (e.g. after scrolling or zooming)
+     */
+    this.updateEvents = function() {
+      var that = this;
+      var o = this._plot.getPlotOffset(), left, top;
+      var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];
+
+      $.each(_events, function(index, event) {
+        top = o.top + that._plot.height() - event.visual().height();
+        left = xaxis.p2c(event.getOptions().min) + o.left - event.visual().width() / 2;
+        event.visual().moveTo({ top: top, left: left });
+      });
+    };
+
+    /**
+     * remove all events from the plot
+     */
+    this._clearEvents = function() {
+      $.each(_events, function(index, val) {
+        val.visual().clear();
+      });
+      _events = [];
+    };
+
+    /**
+     * create a DOM element for the given event
+     */
+    this._buildDiv = function(event) {
+      var that = this;
+
+      var container = this._plot.getPlaceholder();
+      var o = this._plot.getPlotOffset();
+      var axes = this._plot.getAxes();
+      var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];
+      var yaxis, top, left, color, markerSize, markerShow, lineStyle, lineWidth;
+      var markerTooltip;
+
+      // determine the y axis used
+      if (axes.yaxis && axes.yaxis.used) { yaxis = axes.yaxis; }
+      if (axes.yaxis2 && axes.yaxis2.used) { yaxis = axes.yaxis2; }
+
+      // map the eventType to a types object
+      var eventTypeId = event.eventType;
+
+      if (this._types === null || !this._types[eventTypeId] || !this._types[eventTypeId].color) {
+        color = '#666';
+      } else {
+        color = this._types[eventTypeId].color;
+      }
+
+      if (this._types === null || !this._types[eventTypeId] || !this._types[eventTypeId].markerSize) {
+        markerSize = 8; //default marker size
+      } else {
+        markerSize = this._types[eventTypeId].markerSize;
+      }
+
+      if (this._types === null || !this._types[eventTypeId] || this._types[eventTypeId].markerShow === undefined) {
+        markerShow = true;
+      } else {
+        markerShow = this._types[eventTypeId].markerShow;
+      }
+
+      if (this._types === null || !this._types[eventTypeId] || this._types[eventTypeId].markerTooltip === undefined) {
+        markerTooltip = true;
+      } else {
+        markerTooltip = this._types[eventTypeId].markerTooltip;
+      }
+
+      if (this._types == null || !this._types[eventTypeId] || !this._types[eventTypeId].lineStyle) {
+        lineStyle = 'dashed'; //default line style
+      } else {
+        lineStyle = this._types[eventTypeId].lineStyle.toLowerCase();
+      }
+
+      if (this._types == null || !this._types[eventTypeId] || this._types[eventTypeId].lineWidth === undefined) {
+        lineWidth = 1; //default line width
+      } else {
+        lineWidth = this._types[eventTypeId].lineWidth;
+      }
+
+      top = o.top + this._plot.height();
+      left = xaxis.p2c(event.min) + o.left;
+
+      var line = $('<div class="events_line"></div>').css({
+        "position": "absolute",
+        "opacity": 0.8,
+        "left": left + 'px',
+        "top": 8,
+        "width": lineWidth + "px",
+        "height": this._plot.height(),
+        "border-left-width": lineWidth + "px",
+        "border-left-style": lineStyle,
+        "border-left-color": color
+      })
+      .appendTo(container);
+
+      if (markerShow) {
+        var marker = $('<div class="events_marker"></div>').css({
+          "position": "absolute",
+          "left": (-markerSize-Math.round(lineWidth/2)) + "px",
+          "font-size": 0,
+          "line-height": 0,
+          "width": 0,
+          "height": 0,
+          "border-left": markerSize+"px solid transparent",
+          "border-right": markerSize+"px solid transparent"
+        })
+        .appendTo(line);
+
+        if (this._types[eventTypeId] && this._types[eventTypeId].position && this._types[eventTypeId].position.toUpperCase() === 'BOTTOM') {
+          marker.css({
+            "top": top-markerSize-8 +"px",
+            "border-top": "none",
+            "border-bottom": markerSize+"px solid " + color
+          });
+        } else {
+          marker.css({
+            "top": "0px",
+            "border-top": markerSize+"px solid " + color,
+            "border-bottom": "none"
+          });
+        }
+
+        marker.data({
+          "event": event
+        });
+
+        var mouseenter = function() {
+          createAnnotationToolip(marker, $(this).data("event"));
+        };
+
+        var mouseleave = function() {
+          that._plot.clearSelection();
+        };
+
+        if (markerTooltip) {
+          marker.css({ "cursor": "help" });
+          marker.hover(mouseenter, mouseleave);
+        }
+      }
+
+      var drawableEvent = new DrawableEvent(
+        line,
+        function drawFunc(obj) { obj.show(); },
+        function(obj) { obj.remove(); },
+        function(obj, position) {
+          obj.css({
+            top: position.top,
+            left: position.left
+          });
+        },
+        left,
+        top,
+        line.width(),
+        line.height()
+      );
+
+      return drawableEvent;
+    };
+
+    /**
+     * check if the event is inside visible range
+     */
+    this._insidePlot = function(x) {
+      var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];
+      var xc = xaxis.p2c(x);
+      return xc > 0 && xc < xaxis.p2c(xaxis.max);
+    };
+  };
+
+  /**
+   * initialize the plugin for the given plot
+   */
+  function init(plot) {
+    /*jshint validthis:true */
+    var that = this;
+    var eventMarkers = new EventMarkers(plot);
+
+    plot.getEvents = function() {
+      return eventMarkers._events;
+    };
+
+    plot.hideEvents = function() {
+      $.each(eventMarkers._events, function(index, event) {
+        event.visual().getObject().hide();
+      });
+    };
+
+    plot.showEvents = function() {
+      plot.hideEvents();
+      $.each(eventMarkers._events, function(index, event) {
+        event.hide();
+      });
+
+      that.eventMarkers.drawEvents();
+    };
+
+    // change events on an existing plot
+    plot.setEvents = function(events) {
+      if (eventMarkers.eventsEnabled) {
+        eventMarkers.setupEvents(events);
+      }
+    };
+
+    plot.hooks.processOptions.push(function(plot, options) {
+      // enable the plugin
+      if (options.events.data != null) {
+        eventMarkers.eventsEnabled = true;
+      }
+    });
+
+    plot.hooks.draw.push(function(plot) {
+      var options = plot.getOptions();
+
+      if (eventMarkers.eventsEnabled) {
+        // check for first run
+        if (eventMarkers.getEvents().length < 1) {
+          eventMarkers.setTypes(options.events.types);
+          eventMarkers.setupEvents(options.events.data);
+        } else {
+          eventMarkers.updateEvents();
+        }
+      }
+
+      eventMarkers.drawEvents();
+    });
+  }
+
+  var defaultOptions = {
+    events: {
+      data: null,
+      types: null,
+      xaxis: 1,
+      position: 'BOTTOM'
+    }
+  };
+
+  $.plot.plugins.push({
+    init: init,
+    options: defaultOptions,
+    name: "events",
+    version: "0.2.5"
+  });
+
+});

+ 0 - 1
public/app/system.conf.js

@@ -20,7 +20,6 @@ System.config({
     "bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
     "jquery.flot": "vendor/flot/jquery.flot",
     "jquery.flot.pie": "vendor/flot/jquery.flot.pie",
-    "jquery.flot.events": "vendor/flot/jquery.flot.events",
     "jquery.flot.selection": "vendor/flot/jquery.flot.selection",
     "jquery.flot.stack": "vendor/flot/jquery.flot.stack",
     "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",

+ 25 - 5
public/sass/components/_panel_graph.scss

@@ -232,11 +232,6 @@
     opacity: 0.7;
   }
 
-  .label-tag {
-    margin-right: 4px;
-    margin-top: 8px;
-  }
-
   .graph-tooltip-list-item {
     display: table-row;
   }
@@ -253,6 +248,31 @@
   }
 }
 
+.graph-annotation {
+
+  .label-tag {
+    margin-right: 4px;
+    margin-top: 8px;
+  }
+
+  .graph-annotation-title {
+    font-weight: $font-weight-semi-bold;
+    position: relative;
+    top: -0.4rem;
+  }
+
+  a {
+    color: $blue;
+    text-decoration: underline;
+  }
+
+  .graph-annotation-time {
+    position: relative;
+    text-align: center;
+    top: 0.6rem;
+  }
+}
+
 .left-yaxis-label {
   top: 50%;
   left: -5px;

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

@@ -29,7 +29,6 @@
       "bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
       "jquery.flot": "vendor/flot/jquery.flot",
       "jquery.flot.pie": "vendor/flot/jquery.flot.pie",
-      "jquery.flot.events": "vendor/flot/jquery.flot.events",
       "jquery.flot.selection": "vendor/flot/jquery.flot.selection",
       "jquery.flot.stack": "vendor/flot/jquery.flot.stack",
       "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",

+ 0 - 610
public/vendor/flot/jquery.flot.events.js

@@ -1,610 +0,0 @@
-/**
- * Flot plugin for adding 'events' to the plot.
- *
- * Events are small icons drawn onto the graph that represent something happening at that time.
- *
- * This plugin adds the following options to flot:
- *
- * options = {
- *      events: {
- *          levels: int   // number of hierarchy levels
- *          data: [],     // array of event objects
- *          types: []     // array of icons
- *          xaxis: int    // the x axis to attach events to
- *      }
- *  };
- *
- *
- * An event is a javascript object in the following form:
- *
- * {
- *      min: startTime,
- *      max: endTime,
- *      eventType: "type",
- *      title: "event title",
- *      description: "event description"
- * }
- *
- * Types is an array of javascript objects in the following form:
- *
- * types: [
- *     {
- *         eventType: "eventType",
- *         level: hierarchicalLevel,
- *         icon: {
-               image: "eventImage1.png",
- *             width: 10,
- *             height: 10
- *         }
- *     }
- *  ]
- *
- * @author Joel Oughton
- */
-(function($){
-    function init(plot){
-        var DEFAULT_ICON = {
-            icon: "icon-caret-up",
-            size: 20,
-            width: 19,
-            height: 10
-        };
-
-        var _events = [], _types, _eventsEnabled = false;
-
-        plot.getEvents = function(){
-            return _events;
-        };
-
-        plot.hideEvents = function(levelRange){
-
-            $.each(_events, function(index, event){
-                if (_withinHierarchy(event.level(), levelRange)) {
-                    event.visual().getObject().hide();
-                }
-            });
-
-        };
-
-        plot.showEvents = function(levelRange){
-            plot.hideEvents();
-
-            $.each(_events, function(index, event){
-                if (!_withinHierarchy(event.level(), levelRange)) {
-                    event.hide();
-                }
-            });
-
-            _drawEvents();
-        };
-
-        plot.hooks.processOptions.push(function(plot, options){
-            // enable the plugin
-            if (options.events.data != null) {
-                _eventsEnabled = true;
-            }
-        });
-
-        plot.hooks.draw.push(function(plot, canvascontext){
-            var options = plot.getOptions();
-            var xaxis = plot.getXAxes()[options.events.xaxis - 1];
-
-            if (_eventsEnabled) {
-
-                // check for first run
-                if (_events.length < 1) {
-
-                    // check for clustering
-                    if (options.events.clustering) {
-                        var ed = _clusterEvents(options.events.types, options.events.data, xaxis.max - xaxis.min);
-                        _types = ed.types;
-                        _setupEvents(ed.data);
-                    } else {
-                        _types = options.events.types;
-                        _setupEvents(options.events.data);
-                    }
-
-                } else {
-                    /*if (options.events.clustering) {
-                        _clearEvents();
-                        var ed = _clusterEvents(options.events.types, options.events.data, xaxis.max - xaxis.min);
-                        _types = ed.types;
-                        _setupEvents(ed.data);
-                    }*/
-                    _updateEvents();
-                }
-            }
-
-            _drawEvents();
-        });
-
-        var _drawEvents = function() {
-            var o = plot.getPlotOffset();
-            var pleft = o.left, pright = plot.width() - o.right;
-
-            $.each(_events, function(index, event){
-
-                // check event is inside the graph range and inside the hierarchy level
-                if (_insidePlot(event.getOptions().min) &&
-                    !event.isHidden()) {
-                    event.visual().draw();
-                }  else {
-                    event.visual().getObject().hide();
-                }
-            });
-
-            _identicalStarts();
-            _overlaps();
-        };
-
-        var _withinHierarchy = function(level, levelRange){
-            var range = {};
-
-            if (!levelRange) {
-                range.start = 0;
-                range.end = _events.length - 1;
-            } else {
-                range.start = (levelRange.min == undefined) ? 0 : levelRange.min;
-                range.end = (levelRange.max == undefined) ? _events.length - 1 : levelRange.max;
-            }
-
-            if (level >= range.start && level <= range.end) {
-                return true;
-            }
-            return false;
-        };
-
-        var _clearEvents = function(){
-            $.each(_events, function(index, val) {
-                val.visual().clear();
-            });
-
-            _events = [];
-        };
-
-        var _updateEvents = function() {
-            var o = plot.getPlotOffset(), left, top;
-            var xaxis = plot.getXAxes()[plot.getOptions().events.xaxis - 1];
-
-            $.each(_events, function(index, event) {
-                top = o.top + plot.height() - event.visual().height();
-                left = xaxis.p2c(event.getOptions().min) + o.left - event.visual().width() / 2;
-
-                event.visual().moveTo({ top: top, left: left });
-            });
-        };
-
-        var _showTooltip = function(x, y, event){
-            /*
-            var tooltip = $('<div id="tooltip" class=""></div>').appendTo('body').fadeIn(200);
-
-            $('<div id="title">' + event.title + '</div>').appendTo(tooltip);
-            $('<div id="type">Type: ' + event.eventType + '</div>').appendTo(tooltip);
-            $('<div id="description">' + event.description + '</div>').appendTo(tooltip);
-
-            tooltip.css({
-                top: y - tooltip.height() - 5,
-                left: x
-            });
-            console.log(tooltip);
-            */
-
-          // grafana addition
-            var $tooltip = $('<div id="tooltip" annotation-tooltip>');
-            if (event) {
-                $tooltip
-                    .html(event.description)
-                    .place_tt(x, y, {offset: 10, compile: true, scopeData: {event: event}});
-            } else {
-                $tooltip.remove();
-            }
-        };
-
-        var _setupEvents = function(events){
-
-            $.each(events, function(index, event){
-                var level = (plot.getOptions().events.levels == null || !_types || !_types[event.eventType]) ? 0 : _types[event.eventType].level;
-
-                if (level > plot.getOptions().events.levels) {
-                    throw "A type's level has exceeded the maximum. Level=" +
-                    level +
-                    ", Max levels:" +
-                    (plot.getOptions().events.levels);
-                }
-
-                _events.push(new VisualEvent(event, _buildDiv(event), level));
-            });
-
-            _events.sort(compareEvents);
-        };
-
-        var _identicalStarts = function() {
-            var ranges = [], range = {}, event, prev, offset = 0;
-
-            $.each(_events, function(index, val) {
-
-                if (prev) {
-                    if (val.getOptions().min == prev.getOptions().min) {
-
-                        if (!range.min) {
-                            range.min = index;
-                        }
-                        range.max = index;
-                    } else {
-                        if (range.min) {
-                            ranges.push(range);
-                            range = {};
-                        }
-                    }
-                }
-
-                prev = val;
-            });
-
-            if (range.min) {
-                ranges.push(range);
-            }
-
-            $.each(ranges, function(index, val) {
-                var removed = _events.splice(val.min - offset, val.max - val.min + 1);
-
-                $.each(removed, function(index, val) {
-                    val.visual().clear();
-                });
-
-                offset += val.max - val.min + 1;
-            });
-        };
-
-        var _overlaps = function() {
-            var xaxis = plot.getXAxes()[plot.getOptions().events.xaxis - 1];
-            var range, diff, cmid, pmid, left = 0, right = -1;
-            pright = plot.width() - plot.getPlotOffset().right;
-
-            // coverts a clump of events into a single vertical line
-            var processClump = function() {
-                // find the middle x value
-                pmid = _events[right].getOptions().min -
-                    (_events[right].getOptions().min - _events[left].getOptions().min) / 2;
-
-                cmid = xaxis.p2c(pmid);
-
-                // hide the events between the discovered range
-                while (left <= right) {
-                    _events[left++].visual().getObject().hide();
-                }
-
-                // draw a vertical line in the middle of where they are
-                if (_insidePlot(pmid)) {
-                    _drawLine('#000', 1, { x: cmid, y: 0 }, { x: cmid, y: plot.height() });
-
-                }
-            };
-
-            if (xaxis.min && xaxis.max) {
-                range = xaxis.max - xaxis.min;
-
-                for (var i = 1; i < _events.length; i++) {
-                    diff = _events[i].getOptions().min - _events[i - 1].getOptions().min;
-
-                    if (diff / range > 0.007) {  //enough variance
-                        // has a clump has been found
-                        if (right != -1) {
-                            //processClump();
-                        }
-                        right = -1;
-                        left = i;
-                    } else {    // not enough variance
-                        right = i;
-                        // handle to final case
-                        if (i == _events.length - 1) {
-                            //processClump();
-                        }
-                    }
-                }
-            }
-        };
-
-        var _buildDiv = function(event){
-            //var po = plot.pointOffset({ x: 450, y: 1});
-            var container = plot.getPlaceholder(), o = plot.getPlotOffset(), yaxis,
-            xaxis = plot.getXAxes()[plot.getOptions().events.xaxis - 1], axes = plot.getAxes();
-            var top, left, div, icon, level, drawableEvent;
-
-            // determine the y axis used
-            if (axes.yaxis && axes.yaxis.used) yaxis = axes.yaxis;
-            if (axes.yaxis2 && axes.yaxis2.used) yaxis = axes.yaxis2;
-
-            // use the default icon and level
-            if (_types == null || !_types[event.eventType] || !_types[event.eventType].icon) {
-                icon = DEFAULT_ICON;
-                level = 0;
-            } else {
-                icon = _types[event.eventType].icon;
-                level = _types[event.eventType].level;
-            }
-
-            div = $('<i style="position:absolute" class="'+icon.icon+'"></i>').appendTo(container);
-
-            top = o.top + plot.height() - icon.size + 1;
-            left = xaxis.p2c(event.min) + o.left - icon.size / 2;
-
-            div.css({
-                left: left + 'px',
-                top: top,
-                color: icon.color,
-                "text-shadow" : "1px 1px "+icon.outline+", -1px -1px "+icon.outline+", -1px 1px "+icon.outline+", 1px -1px "+icon.outline,
-                'font-size': icon['size']+'px',
-            });
-            div.hide();
-            div.data({
-                "event": event
-            });
-            div.hover(
-            // mouseenter
-            function(){
-                var pos = $(this).offset();
-
-                _showTooltip(pos.left + $(this).width() / 2, pos.top, $(this).data("event"));
-            },
-            // mouseleave
-            function(){
-                //$(this).data("bouncing", false);
-                $('#tooltip').remove();
-                plot.clearSelection();
-            });
-
-            drawableEvent = new DrawableEvent(
-                div,
-                function(obj){
-                    obj.show();
-                },
-                function(obj){
-                    obj.remove();
-                },
-                function(obj, position){
-                    obj.css({
-                        top: position.top,
-                        left: position.left
-                    });
-                },
-                left, top, div.width(), div.height());
-
-            return drawableEvent;
-        };
-
-        var _getEventsAtPos = function(x, y){
-            var found = [], left, top, width, height;
-
-            $.each(_events, function(index, val){
-
-                left = val.div.offset().left;
-                top = val.div.offset().top;
-                width = val.div.width();
-                height = val.div.height();
-
-                if (x >= left && x <= left + width && y >= top && y <= top + height) {
-                    found.push(val);
-                }
-
-                return found;
-            });
-        };
-
-        var _insidePlot = function(x) {
-            var xaxis = plot.getXAxes()[plot.getOptions().events.xaxis - 1];
-            var xc = xaxis.p2c(x);
-
-            return xc > 0 && xc < xaxis.p2c(xaxis.max);
-        };
-
-        var _drawLine = function(color, lineWidth, from, to) {
-            var ctx = plot.getCanvas().getContext("2d");
-            var plotOffset = plot.getPlotOffset();
-
-            ctx.save();
-            ctx.translate(plotOffset.left, plotOffset.top);
-
-            ctx.beginPath();
-            ctx.strokeStyle = color;
-            ctx.lineWidth = lineWidth;
-            ctx.moveTo(from.x, from.y);
-            ctx.lineTo(to.x, to.y);
-            ctx.stroke();
-
-            ctx.restore();
-        };
-
-
-        /**
-         * Runs over the given 2d array of event objects and returns an object
-         * containing:
-         *
-         * {
-         *      types {},   // An array containing all the different event types
-         *      data [],    // An array of the clustered events
-         * }
-         *
-         * @param {Object} types
-         *          an object containing event types
-         * @param {Object} events
-         *          an array of event to cluster
-         * @param {Object} range
-         *          the current graph range
-         */
-        var _clusterEvents = function(types, events, range) {
-            //TODO: support custom types
-            var groups, clusters = [], newEvents = [];
-
-            // split into same evenType groups
-            groups = _groupEvents(events);
-
-            $.each(groups.eventTypes, function(index, val) {
-                clusters.push(_varianceAlgorithm(groups.groupedEvents[val], 1, range));
-            });
-
-            // summarise clusters
-            $.each(clusters, function(index, eventType) {
-
-                // each cluser of each event type
-                $.each(eventType, function(index, cluster) {
-
-                    var newEvent = {
-                        min: cluster[0].min,
-                        max: cluster[cluster.length - 1].min,    //TODO: needs to be max of end event if it exists
-                        eventType: cluster[0].eventType + ",cluster",
-                        title: "Cluster of: " + cluster[0].title,
-                        description: cluster[0].description + ", Number of events in the cluster: " + cluster.length
-                    };
-
-                    newEvents.push(newEvent);
-                });
-            });
-
-            return { types: types, data: newEvents };
-        };
-
-        /**
-         * Runs over the given 2d array of event objects and returns an object
-         * containing:
-         *
-         * {
-         *      eventTypes [],      // An array containing all the different event types
-         *      groupedEvents {},   // An object containing all the grouped events
-         * }
-         *
-         * @param {Object} events
-         *          an array of event objects
-         */
-        var _groupEvents = function(events) {
-            var eventTypes = [], groupedEvents = {};
-
-            $.each(events, function(index, val) {
-                if (!groupedEvents[val.eventType]) {
-                    groupedEvents[val.eventType] = [];
-                    eventTypes.push(val.eventType);
-                }
-
-                groupedEvents[val.eventType].push(val);
-            });
-
-            return { eventTypes: eventTypes, groupedEvents: groupedEvents };
-        };
-
-        /**
-         * Runs over the given 2d array of event objects and returns a 3d array of
-         * the same events,but clustered into groups with similar x deltas.
-         *
-         * This function assumes that the events are related. So it must be run on
-         * each set of related events.
-         *
-         * @param {Object} events
-         *          an array of event objects
-         * @param {Object} sens
-         *          a measure of the level of grouping tolerance
-         * @param {Object} space
-         *          the size of the space we have to place clusters within
-         */
-        var _varianceAlgorithm = function(events, sens, space) {
-            var cluster, clusters = [], sum = 0, avg, density;
-
-            // find the average x delta
-            for (var i = 1; i < events.length - 1; i++) {
-                sum += events[i].min - events[i - 1].min;
-            }
-            avg = sum / (events.length - 2);
-
-            // first point
-            cluster = [ events[0] ];
-
-            // middle points
-            for (var i = 1; i < events.length; i++) {
-                var leftDiff = events[i].min - events[i - 1].min;
-
-                density = leftDiff / space;
-
-                if (leftDiff > avg * sens && density > 0.05) {
-                    clusters.push(cluster);
-                    cluster = [ events[i] ];
-                } else {
-                    cluster.push(events[i]);
-                }
-            }
-
-            clusters.push(cluster);
-
-            return clusters;
-        };
-    }
-
-    var options = {
-        events: {
-            levels: null,
-            data: null,
-            types: null,
-            xaxis: 1,
-            clustering: false
-        }
-    };
-
-    $.plot.plugins.push({
-        init: init,
-        options: options,
-        name: "events",
-        version: "0.20"
-    });
-
-    /**
-     * A class that allows for the drawing an remove of some object
-     *
-     * @param {Object} object
-     *          the drawable object
-     * @param {Object} drawFunc
-     *          the draw function
-     * @param {Object} clearFunc
-     *          the clear function
-     */
-    function DrawableEvent(object, drawFunc, clearFunc, moveFunc, left, top, width, height){
-        var _object = object, _drawFunc = drawFunc, _clearFunc = clearFunc, _moveFunc = moveFunc,
-        _position = { left: left, top: top }, _width = width, _height = height;
-
-        this.width = function() { return _width; };
-        this.height = function() { return _height };
-        this.position = function() { return _position; };
-        this.draw = function() { _drawFunc(_object); };
-        this.clear = function() { _clearFunc(_object); };
-        this.getObject = function() { return _object; };
-        this.moveTo = function(position) {
-            _position = position;
-            _moveFunc(_object, _position);
-        };
-    }
-
-    /**
-     * Event class that stores options (eventType, min, max, title, description) and the object to draw.
-     *
-     * @param {Object} options
-     * @param {Object} drawableEvent
-     */
-    function VisualEvent(options, drawableEvent, level){
-        var _parent, _options = options, _drawableEvent = drawableEvent,
-            _level = level, _hidden = false;
-
-        this.visual = function() { return _drawableEvent; }
-        this.level = function() { return _level; };
-        this.getOptions = function() { return _options; };
-        this.getParent = function() { return _parent; };
-
-        this.isHidden = function() { return _hidden; };
-        this.hide = function() { _hidden = true; };
-        this.unhide = function() { _hidden = false; };
-    }
-
-    function compareEvents(a, b) {
-        var ao = a.getOptions(), bo = b.getOptions();
-
-        if (ao.min > bo.min) return 1;
-        if (ao.min < bo.min) return -1;
-        return 0;
-    };
-})(jQuery);