Commit 86c83d79 by Rashid Khan

Merge branch 'annotations'

parents 8a4b8cf1 b1aef03b
If you have a bugfix or new feature that you would like to contribute to Kibana, please find or open an issue about it first. Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change.
If you have a bugfix or new feature that you would like to contribute to Kibana, please **find or open an issue about it before you start working on it.** Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change.
We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is important to find the best approach before writing too much code.
......
......@@ -32,6 +32,7 @@ require.config({
'jquery.flot': '../vendor/jquery/jquery.flot',
'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie',
'jquery.flot.events': '../vendor/jquery/jquery.flot.events',
'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection',
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
......@@ -66,6 +67,7 @@ require.config({
'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'],
'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'],
'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
......
......@@ -37,7 +37,7 @@ define([
'./timeSeries',
'jquery.flot',
'jquery.flot.pie',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.stack',
......@@ -67,7 +67,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
},
{
title:'Queries',
src:'app/partials/querySelect.html'
src:'app/panels/histogram/queriesEditor.html'
},
],
status : "Stable",
......@@ -84,6 +84,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
mode : 'all',
ids : []
},
annotate : {
enable : false,
query : "*",
size : 20,
field : '@message',
sort : ['_score','desc']
},
value_field : null,
auto_int : true,
resolution : 100,
......@@ -121,6 +128,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip,_d.tooltip);
_.defaults($scope.panel.annotate,_d.annotate);
_.defaults($scope.panel.grid,_d.grid);
......@@ -190,6 +198,16 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
* this call is made recursively for more segments
*/
$scope.get_data = function(segment, query_id) {
var
_range,
_interval,
request,
queries,
results;
$scope.panel.annotate.enable = true;
$scope.panel.annotate.ids = [1];
if (_.isUndefined(segment)) {
segment = 0;
}
......@@ -199,8 +217,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
if(dashboard.indices.length === 0) {
return;
}
var _range = $scope.get_time_range();
var _interval = $scope.get_interval(_range);
_range = $scope.get_time_range();
_interval = $scope.get_interval(_range);
if ($scope.panel.auto_int) {
$scope.panel.interval = kbn.secondsToHms(
......@@ -208,11 +226,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}
$scope.panelMeta.loading = true;
var request = $scope.ejs.Request().indices(dashboard.indices[segment]);
request = $scope.ejs.Request().indices(dashboard.indices[segment]);
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
// Build the query
_.each(queries, function(q) {
......@@ -224,7 +242,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
var facet = $scope.ejs.DateHistogramFacet(q.id);
if($scope.panel.mode === 'count') {
facet = facet.field($scope.panel.time_field);
facet = facet.field($scope.panel.time_field).global(true);
} else {
if(_.isNull($scope.panel.value_field)) {
$scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
......@@ -233,14 +251,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field);
}
facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
request = request.facet(facet).size(0);
request = request.facet(facet)
.size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
});
if($scope.panel.annotate.enable) {
var query = $scope.ejs.FilteredQuery(
$scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
filterSrv.getBoolFilter(filterSrv.idsByType('time'))
);
request = request.query(query);
// This is a hack proposed by @boaz to work around the fact that we can't get
// to field data values directly, and we need timestamps as normalized longs
request = request.sort([
$scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
$scope.ejs.Sort($scope.panel.time_field).desc()
]);
}
// Populate the inspector panel
$scope.populate_modal(request);
// Then run it
var results = request.doSearch();
results = request.doSearch();
// Populate scope when we have results
results.then(function(results) {
......@@ -249,6 +283,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
if(segment === 0) {
$scope.hits = 0;
$scope.data = [];
$scope.annotations = [];
query_id = $scope.query_id = new Date().getTime();
}
......@@ -298,6 +333,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
i++;
});
if($scope.panel.annotate.enable) {
$scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
var _p = _.omit(hit,'_source','sort','_score');
var _h = _.extend(kbn.flatten_json(hit._source),_p);
return {
min: hit.sort[1],
max: hit.sort[1],
eventType: "annotation",
title: null,
description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
_h[$scope.panel.annotate.field]+"</small><br>"+
moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
score: hit.sort[0]
};
}));
// Sort the data
$scope.annotations = _.sortBy($scope.annotations, function(v){
// Sort in reverse
return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
});
// And slice to the right size
$scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
}
// Tell the histogram directive to render.
$scope.$emit('render');
......@@ -429,7 +488,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
bars: {
show: scope.panel.bars,
fill: 1,
barWidth: barwidth/1.8,
barWidth: barwidth/1.5,
zero: false,
lineWidth: 0
},
......@@ -464,6 +523,25 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}
};
if(scope.panel.annotate.enable) {
options.events = {
levels: 1,
data: scope.annotations,
types: {
'annotation': {
level: 1,
icon: {
icon: "icon-tag icon-flip-vertical",
size: 20,
color: "#222",
outline: "#bbb"
}
}
}
//xaxis: int // the x axis to attach events to
};
}
if(scope.panel.interactive) {
options.selection = { mode: "x", color: '#666' };
}
......@@ -503,13 +581,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
function time_format(interval) {
var _int = kbn.interval_to_seconds(interval);
if(_int >= 2628000) {
return "%m/%y";
return "%Y-%m";
}
if(_int >= 86400) {
return "%m/%d/%y";
return "%Y-%m-%d";
}
if(_int >= 60) {
return "%H:%M<br>%m/%d";
return "%H:%M<br>%m-%d";
}
return "%H:%M:%S";
......@@ -534,7 +612,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}
$tooltip
.html(
group + value + " @ " + moment(item.datapoint[0]).format('MM/DD HH:mm:ss')
group + value + " @ " + moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss')
)
.place_tt(pos.pageX, pos.pageY);
} else {
......
<h4>Charted</h4>
<div ng-include src="'app/partials/querySelect.html'"></div>
<div class="editor-row">
<h4>Markers</h4>
<div class="small">
Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
</div>
<style>
.querySelect .query {
margin-right: 5px;
}
.querySelect .selected {
border: 3px solid;
}
.querySelect .unselected {
border: 0px solid;
}
</style>
<p>
<div class="editor-option">
<label class="small">Enable</label>
<input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Marker Query</label>
<input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Tooltip field</label>
<input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Limit <tip>Max markers on the chart</tip></label>
<input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
<i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
</div>
</div>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -256,7 +256,9 @@ a {
.boolean {color:lighten(@warningText, 5%)}
.key {color:lighten(@errorText, 5%)}
.typeahead { z-index: 1051; }
.modal-body {
overflow-y:visible;
}
.btn-active {
background-color: #E6E6E6;
......
/**
* 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, lastRange;
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) {
_lastRange = xaxis.max - xaxis.min;
// 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);
*/
// @rashidkpc - hack to work with our normal tooltip placer
var $tooltip = $('<div id="tooltip">');
if (event) {
$tooltip
.html(event.description)
.place_tt(x, y, {
offset: 10
});
} 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();
/*// check if the mouse is not already over the event
if ($(this).data("bouncing") == false || $(this).data("bouncing") == undefined) {
// check the div is not already bouncing
if ($(this).position().top == $(this).data("top")) {
$(this).effect("bounce", {
times: 3
}, 300);
}
$(this).data("bouncing", true);
_showTooltip(pos.left + $(this).width() / 2, pos.top, $(this).data("event"));
}*/
_showTooltip(pos.left + $(this).width() / 2, pos.top, $(this).data("event"));
if (event.min != event.max) {
plot.setSelection({
xaxis: {
from: event.min,
to: event.max
},
yaxis: {
from: yaxis.min,
to: yaxis.max
}
});
}
},
// 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);
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment