mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(annotations): updated flot events lib and refactored it to use tether-drop lib
This commit is contained in:
parent
55d4392d90
commit
a6a5f393cc
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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
public/app/plugins/panel/graph/jquery.flot.events.js
Normal file
399
public/app/plugins/panel/graph/jquery.flot.events.js
Normal file
@ -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"
|
||||
});
|
||||
|
||||
});
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
610
public/vendor/flot/jquery.flot.events.js
vendored
610
public/vendor/flot/jquery.flot.events.js
vendored
@ -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
Block a user