diff --git a/public/app/core/directives/annotation_tooltip.js b/public/app/core/directives/annotation_tooltip.js index 38c0a34826f..2d51c6b3494 100644 --- a/public/app/core/directives/annotation_tooltip.js +++ b/public/app/core/directives/annotation_tooltip.js @@ -25,7 +25,8 @@ function ($, _, coreModule) { var dashboard = dashboardSrv.getCurrent(); var time = '' + dashboard.formatDate(event.min) + ''; - var tooltip = '
' + title + ' ' + time + '
' ; + var tooltip = '
'; + tooltip += '
' + title + "
"; if (event.text) { var text = sanitizeString(event.text); @@ -42,9 +43,10 @@ function ($, _, coreModule) { if (tags && tags.length) { scope.tags = tags; - tooltip += '{{tag}}
'; + tooltip += '{{tag}}
'; } + tooltip += '
' + time + '
' ; tooltip += "
"; var $tooltip = $(tooltip); diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 10f93b5ef05..a2ff0ff41fb 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -68,7 +68,7 @@
- Name + Name
@@ -77,27 +77,12 @@
-
-
-
- Icon size -
- -
-
-
- - -
diff --git a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html index 3c4634bb657..36746225865 100644 --- a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html @@ -1,27 +1,27 @@ + +
Query
+
+ +
+
+ +
+
Column mappings 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.
- InfluxDB Query Example: select text from events where $timeFilter - -
-
-
-
-
Column mappings 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.
-
-
- Title - + Title +
- Tags - + Tags +
- Text - + Text +
diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 2a9434a76c8..c7490d1133d 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/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,17 @@ 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', + markerSize: 5, }; } - - 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, }; } diff --git a/public/app/plugins/panel/graph/jquery.flot.events.js b/public/app/plugins/panel/graph/jquery.flot.events.js new file mode 100644 index 00000000000..bf3b4b26eb3 --- /dev/null +++ b/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 = ''; + + 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 + * Joel Oughton + * Nicolas Joseph + * + * 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 = $('
').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 = $('
').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" + }); + +}); diff --git a/public/app/system.conf.js b/public/app/system.conf.js index 9e68baa8588..86c00995403 100644 --- a/public/app/system.conf.js +++ b/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", diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index b51ac317043..3f0e104e48c 100644 --- a/public/sass/components/_panel_graph.scss +++ b/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; diff --git a/public/test/test-main.js b/public/test/test-main.js index 608305e46e7..5cc7b25dd6c 100644 --- a/public/test/test-main.js +++ b/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", diff --git a/public/vendor/flot/jquery.flot.events.js b/public/vendor/flot/jquery.flot.events.js deleted file mode 100644 index 6d8a0cf3c42..00000000000 --- a/public/vendor/flot/jquery.flot.events.js +++ /dev/null @@ -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 = $('
').appendTo('body').fadeIn(200); - - $('
' + event.title + '
').appendTo(tooltip); - $('
Type: ' + event.eventType + '
').appendTo(tooltip); - $('
' + event.description + '
').appendTo(tooltip); - - tooltip.css({ - top: y - tooltip.height() - 5, - left: x - }); - console.log(tooltip); - */ - - // grafana addition - var $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 = $('').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);