mirror of https://github.com/grafana/grafana
parent
55d4392d90
commit
a6a5f393cc
@ -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" |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
@ -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); |
|
Loading…
Reference in new issue