mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
got flot graph showing!
This commit is contained in:
parent
50e42c8bdd
commit
bbcc1ef9ee
@ -43,6 +43,9 @@ require.config({
|
|||||||
|
|
||||||
modernizr: '../vendor/modernizr-2.6.1',
|
modernizr: '../vendor/modernizr-2.6.1',
|
||||||
elasticjs: '../vendor/elasticjs/elastic-angular-client',
|
elasticjs: '../vendor/elasticjs/elastic-angular-client',
|
||||||
|
|
||||||
|
'ts-widget': '../vendor/timeserieswidget/jquery.tswidget',
|
||||||
|
'ts-graphite-helpers': '../vendor/timeserieswidget/graphite_helpers'
|
||||||
},
|
},
|
||||||
shim: {
|
shim: {
|
||||||
underscore: {
|
underscore: {
|
||||||
@ -91,7 +94,16 @@ require.config({
|
|||||||
timepicker: ['jquery', 'bootstrap'],
|
timepicker: ['jquery', 'bootstrap'],
|
||||||
datepicker: ['jquery', 'bootstrap'],
|
datepicker: ['jquery', 'bootstrap'],
|
||||||
|
|
||||||
elasticjs: ['angular', '../vendor/elasticjs/elastic']
|
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
|
||||||
|
|
||||||
|
'ts-widget': [
|
||||||
|
'jquery',
|
||||||
|
'jquery.flot',
|
||||||
|
'jquery.flot.selection',
|
||||||
|
'jquery.flot.stack',
|
||||||
|
'jquery.flot.time',
|
||||||
|
'ts-graphite-helpers'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
waitSeconds: 60,
|
waitSeconds: 60,
|
||||||
});
|
});
|
||||||
|
@ -50,7 +50,8 @@
|
|||||||
"group": [
|
"group": [
|
||||||
"default"
|
"default"
|
||||||
],
|
],
|
||||||
"type": "graph"
|
"type": "graph",
|
||||||
|
"someprop": "hej from config"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"notice": true
|
"notice": true
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
<div ng-controller='graph' ng-init="init()">
|
<div ng-controller='graph' ng-init="init()">
|
||||||
<h2>{{saySomething}}</h2>
|
<h2>{{panel.someprop}}</h2>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{panel.graphiteUrl}}</li>
|
<li>{{panel.graphiteUrl}}</li>
|
||||||
<li>{{panel.targets}}</li>
|
<li>{{panel.targets}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<mychart ng-model='data'></mychart>
|
||||||
|
<div class="chart_container flot" id="chart_container_flot">
|
||||||
|
<div class="chart" id="chart_flot" height="300px" width="700px"></div>
|
||||||
|
<div class="legend" id="legend_flot_simple"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,21 +1,11 @@
|
|||||||
/** @scratch /panels/5
|
|
||||||
* include::panels/text.asciidoc[]
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @scratch /panels/text/0
|
|
||||||
* == text
|
|
||||||
* Status: *Stable*
|
|
||||||
*
|
|
||||||
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
|
|
||||||
* text.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
define([
|
define([
|
||||||
|
'jquery',
|
||||||
'angular',
|
'angular',
|
||||||
'app',
|
'app',
|
||||||
'underscore'
|
'underscore',
|
||||||
|
'ts-widget'
|
||||||
],
|
],
|
||||||
function (angular, app, _) {
|
function ($, angular, app, _, timeseriesWidget) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('kibana.panels.graph', []);
|
var module = angular.module('kibana.panels.graph', []);
|
||||||
@ -40,4 +30,50 @@ function (angular, app, _) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('kibana.directives')
|
||||||
|
.directive('mychart', function () {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
link: function (scope, elem, attrs) {
|
||||||
|
var tsData = {
|
||||||
|
graphite_url: 'http://localhost:3030/data',
|
||||||
|
from: '-24hours',
|
||||||
|
until: 'now',
|
||||||
|
height: '300',
|
||||||
|
width: '740',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
name: 'series 1',
|
||||||
|
color: '#CC6699',
|
||||||
|
target: 'random1',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: 'horizontal title',
|
||||||
|
vtitle: 'vertical title',
|
||||||
|
drawNullAsZero: false,
|
||||||
|
legend: { container: '#legend_flot_simple', noColumns: 1 },
|
||||||
|
};
|
||||||
|
$("#chart_flot").graphiteFlot(tsData, function(err) {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('asd');
|
||||||
|
$(elem).html('NJEEEJ!');
|
||||||
|
/*// If the data changes somehow, update it in the chart
|
||||||
|
scope.$watch('data', function(v){
|
||||||
|
if(!chart){
|
||||||
|
chart = $.plot(elem, v , options);
|
||||||
|
elem.show();
|
||||||
|
}else{
|
||||||
|
chart.setData(v);
|
||||||
|
chart.setupGrid();
|
||||||
|
chart.draw();
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
@ -38,21 +38,9 @@ function (Settings) {
|
|||||||
* dashboard, but this list is used in the "add panel" interface.
|
* dashboard, but this list is used in the "add panel" interface.
|
||||||
*/
|
*/
|
||||||
panel_names: [
|
panel_names: [
|
||||||
'histogram',
|
'graph',
|
||||||
'map',
|
|
||||||
'pie',
|
|
||||||
'table',
|
|
||||||
'filtering',
|
|
||||||
'timepicker',
|
|
||||||
'text',
|
'text',
|
||||||
'hits',
|
'column'
|
||||||
'dashcontrol',
|
|
||||||
'column',
|
|
||||||
'trends',
|
|
||||||
'bettermap',
|
|
||||||
'query',
|
|
||||||
'terms',
|
|
||||||
'sparklines'
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
29
src/vendor/timeserieswidget/graphite_helpers.js
vendored
Normal file
29
src/vendor/timeserieswidget/graphite_helpers.js
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
String.prototype.graphiteGlob = function(glob) {
|
||||||
|
var regex = '^';
|
||||||
|
for (var i = 0; i < glob.length; i++ ) {
|
||||||
|
var c = glob.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '*':
|
||||||
|
regex += '[^\.]+';
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
regex += '\\.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
regex += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regex += '$';
|
||||||
|
return this.match(regex);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (!"stats.dfs4.timer".graphiteGlob('stats.*.timer')) {
|
||||||
|
console.log('fail 1');
|
||||||
|
}
|
||||||
|
if ("stats.dfs4.timer".graphiteGlob('statsd.*.timer')) {
|
||||||
|
console.log('fail 2');
|
||||||
|
}
|
||||||
|
if ("stats.dfs4.foo.timer".graphiteGlob('stats.*.timer')) {
|
||||||
|
console.log('fail 3');
|
||||||
|
}
|
||||||
|
*/
|
416
src/vendor/timeserieswidget/jquery.flot.axislabels.js
vendored
Normal file
416
src/vendor/timeserieswidget/jquery.flot.axislabels.js
vendored
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
/*
|
||||||
|
Axis Labels Plugin for flot.
|
||||||
|
http://github.com/markrcote/flot-axislabels
|
||||||
|
|
||||||
|
Original code is Copyright (c) 2010 Xuan Luo.
|
||||||
|
Original code was released under the GPLv3 license by Xuan Luo, September 2010.
|
||||||
|
Original code was rereleased under the MIT license by Xuan Luo, April 2012.
|
||||||
|
|
||||||
|
Improvements by Mark Cote.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
(function ($) {
|
||||||
|
var options = { };
|
||||||
|
|
||||||
|
function canvasSupported() {
|
||||||
|
return !!document.createElement('canvas').getContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasTextSupported() {
|
||||||
|
if (!canvasSupported()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var dummy_canvas = document.createElement('canvas');
|
||||||
|
var context = dummy_canvas.getContext('2d');
|
||||||
|
return typeof context.fillText == 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
function css3TransitionSupported() {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
return typeof div.style.MozTransition != 'undefined' // Gecko
|
||||||
|
|| typeof div.style.OTransition != 'undefined' // Opera
|
||||||
|
|| typeof div.style.webkitTransition != 'undefined' // WebKit
|
||||||
|
|| typeof div.style.transition != 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AxisLabel(axisName, position, padding, plot, opts) {
|
||||||
|
this.axisName = axisName;
|
||||||
|
this.position = position;
|
||||||
|
this.padding = padding;
|
||||||
|
this.plot = plot;
|
||||||
|
this.opts = opts;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CanvasAxisLabel.prototype = new AxisLabel();
|
||||||
|
CanvasAxisLabel.prototype.constructor = CanvasAxisLabel;
|
||||||
|
function CanvasAxisLabel(axisName, position, padding, plot, opts) {
|
||||||
|
AxisLabel.prototype.constructor.call(this, axisName, position, padding,
|
||||||
|
plot, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasAxisLabel.prototype.calculateSize = function() {
|
||||||
|
if (!this.opts.axisLabelFontSizePixels)
|
||||||
|
this.opts.axisLabelFontSizePixels = 14;
|
||||||
|
if (!this.opts.axisLabelFontFamily)
|
||||||
|
this.opts.axisLabelFontFamily = 'sans-serif';
|
||||||
|
|
||||||
|
var textWidth = this.opts.axisLabelFontSizePixels + this.padding;
|
||||||
|
var textHeight = this.opts.axisLabelFontSizePixels + this.padding;
|
||||||
|
if (this.position == 'left' || this.position == 'right') {
|
||||||
|
this.width = this.opts.axisLabelFontSizePixels + this.padding;
|
||||||
|
this.height = 0;
|
||||||
|
} else {
|
||||||
|
this.width = 0;
|
||||||
|
this.height = this.opts.axisLabelFontSizePixels + this.padding;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CanvasAxisLabel.prototype.draw = function(box) {
|
||||||
|
var ctx = this.plot.getCanvas().getContext('2d');
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = this.opts.axisLabelFontSizePixels + 'px ' +
|
||||||
|
this.opts.axisLabelFontFamily;
|
||||||
|
var width = ctx.measureText(this.opts.axisLabel).width;
|
||||||
|
var height = this.opts.axisLabelFontSizePixels;
|
||||||
|
var x, y, angle = 0;
|
||||||
|
if (this.position == 'top') {
|
||||||
|
x = box.left + box.width/2 - width/2;
|
||||||
|
y = box.top + height*0.72;
|
||||||
|
} else if (this.position == 'bottom') {
|
||||||
|
x = box.left + box.width/2 - width/2;
|
||||||
|
y = box.top + box.height - height*0.72;
|
||||||
|
} else if (this.position == 'left') {
|
||||||
|
x = box.left + height*0.72;
|
||||||
|
y = box.height/2 + box.top + width/2;
|
||||||
|
angle = -Math.PI/2;
|
||||||
|
} else if (this.position == 'right') {
|
||||||
|
x = box.left + box.width - height*0.72;
|
||||||
|
y = box.height/2 + box.top - width/2;
|
||||||
|
angle = Math.PI/2;
|
||||||
|
}
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate(angle);
|
||||||
|
ctx.fillText(this.opts.axisLabel, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
HtmlAxisLabel.prototype = new AxisLabel();
|
||||||
|
HtmlAxisLabel.prototype.constructor = HtmlAxisLabel;
|
||||||
|
function HtmlAxisLabel(axisName, position, padding, plot, opts) {
|
||||||
|
AxisLabel.prototype.constructor.call(this, axisName, position,
|
||||||
|
padding, plot, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
HtmlAxisLabel.prototype.calculateSize = function() {
|
||||||
|
var elem = $('<div class="axisLabels" style="position:absolute;">' +
|
||||||
|
this.opts.axisLabel + '</div>');
|
||||||
|
this.plot.getPlaceholder().append(elem);
|
||||||
|
// store height and width of label itself, for use in draw()
|
||||||
|
this.labelWidth = elem.outerWidth(true);
|
||||||
|
this.labelHeight = elem.outerHeight(true);
|
||||||
|
elem.remove();
|
||||||
|
|
||||||
|
this.width = this.height = 0;
|
||||||
|
if (this.position == 'left' || this.position == 'right') {
|
||||||
|
this.width = this.labelWidth + this.padding;
|
||||||
|
} else {
|
||||||
|
this.height = this.labelHeight + this.padding;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HtmlAxisLabel.prototype.draw = function(box) {
|
||||||
|
this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove();
|
||||||
|
var elem = $('<div id="' + this.axisName +
|
||||||
|
'Label" " class="axisLabels" style="position:absolute;">'
|
||||||
|
+ this.opts.axisLabel + '</div>');
|
||||||
|
this.plot.getPlaceholder().append(elem);
|
||||||
|
if (this.position == 'top') {
|
||||||
|
elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
|
||||||
|
elem.css('top', box.top + 'px');
|
||||||
|
} else if (this.position == 'bottom') {
|
||||||
|
elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
|
||||||
|
elem.css('top', box.top + box.height - this.labelHeight + 'px');
|
||||||
|
} else if (this.position == 'left') {
|
||||||
|
elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
|
||||||
|
elem.css('left', box.left + 'px');
|
||||||
|
} else if (this.position == 'right') {
|
||||||
|
elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
|
||||||
|
elem.css('left', box.left + box.width - this.labelWidth + 'px');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CssTransformAxisLabel.prototype = new HtmlAxisLabel();
|
||||||
|
CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel;
|
||||||
|
function CssTransformAxisLabel(axisName, position, padding, plot, opts) {
|
||||||
|
HtmlAxisLabel.prototype.constructor.call(this, axisName, position,
|
||||||
|
padding, plot, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
CssTransformAxisLabel.prototype.calculateSize = function() {
|
||||||
|
HtmlAxisLabel.prototype.calculateSize.call(this);
|
||||||
|
this.width = this.height = 0;
|
||||||
|
if (this.position == 'left' || this.position == 'right') {
|
||||||
|
this.width = this.labelHeight + this.padding;
|
||||||
|
} else {
|
||||||
|
this.height = this.labelHeight + this.padding;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
|
||||||
|
var stransforms = {
|
||||||
|
'-moz-transform': '',
|
||||||
|
'-webkit-transform': '',
|
||||||
|
'-o-transform': '',
|
||||||
|
'-ms-transform': ''
|
||||||
|
};
|
||||||
|
if (x != 0 || y != 0) {
|
||||||
|
var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)';
|
||||||
|
stransforms['-moz-transform'] += stdTranslate;
|
||||||
|
stransforms['-webkit-transform'] += stdTranslate;
|
||||||
|
stransforms['-o-transform'] += stdTranslate;
|
||||||
|
stransforms['-ms-transform'] += stdTranslate;
|
||||||
|
}
|
||||||
|
if (degrees != 0) {
|
||||||
|
var rotation = degrees / 90;
|
||||||
|
var stdRotate = ' rotate(' + degrees + 'deg)';
|
||||||
|
stransforms['-moz-transform'] += stdRotate;
|
||||||
|
stransforms['-webkit-transform'] += stdRotate;
|
||||||
|
stransforms['-o-transform'] += stdRotate;
|
||||||
|
stransforms['-ms-transform'] += stdRotate;
|
||||||
|
}
|
||||||
|
var s = 'top: 0; left: 0; ';
|
||||||
|
for (var prop in stransforms) {
|
||||||
|
if (stransforms[prop]) {
|
||||||
|
s += prop + ':' + stransforms[prop] + ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ';';
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
CssTransformAxisLabel.prototype.calculateOffsets = function(box) {
|
||||||
|
var offsets = { x: 0, y: 0, degrees: 0 };
|
||||||
|
if (this.position == 'bottom') {
|
||||||
|
offsets.x = box.left + box.width/2 - this.labelWidth/2;
|
||||||
|
offsets.y = box.top + box.height - this.labelHeight;
|
||||||
|
} else if (this.position == 'top') {
|
||||||
|
offsets.x = box.left + box.width/2 - this.labelWidth/2;
|
||||||
|
offsets.y = box.top;
|
||||||
|
} else if (this.position == 'left') {
|
||||||
|
offsets.degrees = -90;
|
||||||
|
offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2;
|
||||||
|
offsets.y = box.height/2 + box.top;
|
||||||
|
} else if (this.position == 'right') {
|
||||||
|
offsets.degrees = 90;
|
||||||
|
offsets.x = box.left + box.width - this.labelWidth/2
|
||||||
|
- this.labelHeight/2;
|
||||||
|
offsets.y = box.height/2 + box.top;
|
||||||
|
}
|
||||||
|
return offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
CssTransformAxisLabel.prototype.draw = function(box) {
|
||||||
|
this.plot.getPlaceholder().find("." + this.axisName + "Label").remove();
|
||||||
|
var offsets = this.calculateOffsets(box);
|
||||||
|
var elem = $('<div class="axisLabels ' + this.axisName +
|
||||||
|
'Label" style="position:absolute; ' +
|
||||||
|
'color: ' + this.opts.color + '; ' +
|
||||||
|
this.transforms(offsets.degrees, offsets.x, offsets.y) +
|
||||||
|
'">' + this.opts.axisLabel + '</div>');
|
||||||
|
this.plot.getPlaceholder().append(elem);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
IeTransformAxisLabel.prototype = new CssTransformAxisLabel();
|
||||||
|
IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel;
|
||||||
|
function IeTransformAxisLabel(axisName, position, padding, plot, opts) {
|
||||||
|
CssTransformAxisLabel.prototype.constructor.call(this, axisName,
|
||||||
|
position, padding,
|
||||||
|
plot, opts);
|
||||||
|
this.requiresResize = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
|
||||||
|
// I didn't feel like learning the crazy Matrix stuff, so this uses
|
||||||
|
// a combination of the rotation transform and CSS positioning.
|
||||||
|
var s = '';
|
||||||
|
if (degrees != 0) {
|
||||||
|
var rotation = degrees/90;
|
||||||
|
while (rotation < 0) {
|
||||||
|
rotation += 4;
|
||||||
|
}
|
||||||
|
s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); ';
|
||||||
|
// see below
|
||||||
|
this.requiresResize = (this.position == 'right');
|
||||||
|
}
|
||||||
|
if (x != 0) {
|
||||||
|
s += 'left: ' + x + 'px; ';
|
||||||
|
}
|
||||||
|
if (y != 0) {
|
||||||
|
s += 'top: ' + y + 'px; ';
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
IeTransformAxisLabel.prototype.calculateOffsets = function(box) {
|
||||||
|
var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call(
|
||||||
|
this, box);
|
||||||
|
// adjust some values to take into account differences between
|
||||||
|
// CSS and IE rotations.
|
||||||
|
if (this.position == 'top') {
|
||||||
|
// FIXME: not sure why, but placing this exactly at the top causes
|
||||||
|
// the top axis label to flip to the bottom...
|
||||||
|
offsets.y = box.top + 1;
|
||||||
|
} else if (this.position == 'left') {
|
||||||
|
offsets.x = box.left;
|
||||||
|
offsets.y = box.height/2 + box.top - this.labelWidth/2;
|
||||||
|
} else if (this.position == 'right') {
|
||||||
|
offsets.x = box.left + box.width - this.labelHeight;
|
||||||
|
offsets.y = box.height/2 + box.top - this.labelWidth/2;
|
||||||
|
}
|
||||||
|
return offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
IeTransformAxisLabel.prototype.draw = function(box) {
|
||||||
|
CssTransformAxisLabel.prototype.draw.call(this, box);
|
||||||
|
if (this.requiresResize) {
|
||||||
|
var elem = this.plot.getPlaceholder().find("." + this.axisName + "Label");
|
||||||
|
// Since we used CSS positioning instead of transforms for
|
||||||
|
// translating the element, and since the positioning is done
|
||||||
|
// before any rotations, we have to reset the width and height
|
||||||
|
// in case the browser wrapped the text (specifically for the
|
||||||
|
// y2axis).
|
||||||
|
elem.css('width', this.labelWidth);
|
||||||
|
elem.css('height', this.labelHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function init(plot) {
|
||||||
|
// This is kind of a hack. There are no hooks in Flot between
|
||||||
|
// the creation and measuring of the ticks (setTicks, measureTickLabels
|
||||||
|
// in setupGrid() ) and the drawing of the ticks and plot box
|
||||||
|
// (insertAxisLabels in setupGrid() ).
|
||||||
|
//
|
||||||
|
// Therefore, we use a trick where we run the draw routine twice:
|
||||||
|
// the first time to get the tick measurements, so that we can change
|
||||||
|
// them, and then have it draw it again.
|
||||||
|
var secondPass = false;
|
||||||
|
|
||||||
|
var axisLabels = {};
|
||||||
|
var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
|
||||||
|
var defaultPadding = 2; // padding between axis and tick labels
|
||||||
|
plot.hooks.draw.push(function (plot, ctx) {
|
||||||
|
var hasAxisLabels = false;
|
||||||
|
if (!secondPass) {
|
||||||
|
// MEASURE AND SET OPTIONS
|
||||||
|
$.each(plot.getAxes(), function(axisName, axis) {
|
||||||
|
var opts = axis.options // Flot 0.7
|
||||||
|
|| plot.getOptions()[axisName]; // Flot 0.6
|
||||||
|
if (!opts || !opts.axisLabel || !axis.show)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hasAxisLabels = true;
|
||||||
|
var renderer = null;
|
||||||
|
|
||||||
|
if (!opts.axisLabelUseHtml &&
|
||||||
|
navigator.appName == 'Microsoft Internet Explorer') {
|
||||||
|
var ua = navigator.userAgent;
|
||||||
|
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
|
||||||
|
if (re.exec(ua) != null) {
|
||||||
|
rv = parseFloat(RegExp.$1);
|
||||||
|
}
|
||||||
|
if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
|
||||||
|
renderer = CssTransformAxisLabel;
|
||||||
|
} else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
|
||||||
|
renderer = IeTransformAxisLabel;
|
||||||
|
} else if (opts.axisLabelUseCanvas) {
|
||||||
|
renderer = CanvasAxisLabel;
|
||||||
|
} else {
|
||||||
|
renderer = HtmlAxisLabel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) {
|
||||||
|
renderer = HtmlAxisLabel;
|
||||||
|
} else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) {
|
||||||
|
renderer = CanvasAxisLabel;
|
||||||
|
} else {
|
||||||
|
renderer = CssTransformAxisLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = opts.axisLabelPadding === undefined ?
|
||||||
|
defaultPadding : opts.axisLabelPadding;
|
||||||
|
|
||||||
|
axisLabels[axisName] = new renderer(axisName,
|
||||||
|
axis.position, padding,
|
||||||
|
plot, opts);
|
||||||
|
|
||||||
|
// flot interprets axis.labelHeight and .labelWidth as
|
||||||
|
// the height and width of the tick labels. We increase
|
||||||
|
// these values to make room for the axis label and
|
||||||
|
// padding.
|
||||||
|
|
||||||
|
axisLabels[axisName].calculateSize();
|
||||||
|
|
||||||
|
// AxisLabel.height and .width are the size of the
|
||||||
|
// axis label and padding.
|
||||||
|
axis.labelHeight += axisLabels[axisName].height;
|
||||||
|
axis.labelWidth += axisLabels[axisName].width;
|
||||||
|
opts.labelHeight = axis.labelHeight;
|
||||||
|
opts.labelWidth = axis.labelWidth;
|
||||||
|
});
|
||||||
|
// if there are axis labels re-draw with new label widths and heights
|
||||||
|
if (hasAxisLabels) {
|
||||||
|
secondPass = true;
|
||||||
|
plot.setupGrid();
|
||||||
|
plot.draw();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// DRAW
|
||||||
|
$.each(plot.getAxes(), function(axisName, axis) {
|
||||||
|
var opts = axis.options // Flot 0.7
|
||||||
|
|| plot.getOptions()[axisName]; // Flot 0.6
|
||||||
|
if (!opts || !opts.axisLabel || !axis.show)
|
||||||
|
return;
|
||||||
|
|
||||||
|
axisLabels[axisName].draw(axis.box);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: options,
|
||||||
|
name: 'axisLabels',
|
||||||
|
version: '2.0b0'
|
||||||
|
});
|
||||||
|
})(jQuery);
|
2980
src/vendor/timeserieswidget/jquery.flot.js
vendored
Normal file
2980
src/vendor/timeserieswidget/jquery.flot.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
360
src/vendor/timeserieswidget/jquery.flot.selection.js
vendored
Normal file
360
src/vendor/timeserieswidget/jquery.flot.selection.js
vendored
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
/* Flot plugin for selecting regions of a plot.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
The plugin supports these options:
|
||||||
|
|
||||||
|
selection: {
|
||||||
|
mode: null or "x" or "y" or "xy",
|
||||||
|
color: color,
|
||||||
|
shape: "round" or "miter" or "bevel",
|
||||||
|
minSize: number of pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
Selection support is enabled by setting the mode to one of "x", "y" or "xy".
|
||||||
|
In "x" mode, the user will only be able to specify the x range, similarly for
|
||||||
|
"y" mode. For "xy", the selection becomes a rectangle where both ranges can be
|
||||||
|
specified. "color" is color of the selection (if you need to change the color
|
||||||
|
later on, you can get to it with plot.getOptions().selection.color). "shape"
|
||||||
|
is the shape of the corners of the selection.
|
||||||
|
|
||||||
|
"minSize" is the minimum size a selection can be in pixels. This value can
|
||||||
|
be customized to determine the smallest size a selection can be and still
|
||||||
|
have the selection rectangle be displayed. When customizing this value, the
|
||||||
|
fact that it refers to pixels, not axis units must be taken into account.
|
||||||
|
Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
|
||||||
|
minute, setting "minSize" to 1 will not make the minimum selection size 1
|
||||||
|
minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
|
||||||
|
"plotunselected" events from being fired when the user clicks the mouse without
|
||||||
|
dragging.
|
||||||
|
|
||||||
|
When selection support is enabled, a "plotselected" event will be emitted on
|
||||||
|
the DOM element you passed into the plot function. The event handler gets a
|
||||||
|
parameter with the ranges selected on the axes, like this:
|
||||||
|
|
||||||
|
placeholder.bind( "plotselected", function( event, ranges ) {
|
||||||
|
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
|
||||||
|
// similar for yaxis - with multiple axes, the extra ones are in
|
||||||
|
// x2axis, x3axis, ...
|
||||||
|
});
|
||||||
|
|
||||||
|
The "plotselected" event is only fired when the user has finished making the
|
||||||
|
selection. A "plotselecting" event is fired during the process with the same
|
||||||
|
parameters as the "plotselected" event, in case you want to know what's
|
||||||
|
happening while it's happening,
|
||||||
|
|
||||||
|
A "plotunselected" event with no arguments is emitted when the user clicks the
|
||||||
|
mouse to remove the selection. As stated above, setting "minSize" to 0 will
|
||||||
|
destroy this behavior.
|
||||||
|
|
||||||
|
The plugin allso adds the following methods to the plot object:
|
||||||
|
|
||||||
|
- setSelection( ranges, preventEvent )
|
||||||
|
|
||||||
|
Set the selection rectangle. The passed in ranges is on the same form as
|
||||||
|
returned in the "plotselected" event. If the selection mode is "x", you
|
||||||
|
should put in either an xaxis range, if the mode is "y" you need to put in
|
||||||
|
an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
|
||||||
|
this:
|
||||||
|
|
||||||
|
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
|
||||||
|
|
||||||
|
setSelection will trigger the "plotselected" event when called. If you don't
|
||||||
|
want that to happen, e.g. if you're inside a "plotselected" handler, pass
|
||||||
|
true as the second parameter. If you are using multiple axes, you can
|
||||||
|
specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
|
||||||
|
xaxis, the plugin picks the first one it sees.
|
||||||
|
|
||||||
|
- clearSelection( preventEvent )
|
||||||
|
|
||||||
|
Clear the selection rectangle. Pass in true to avoid getting a
|
||||||
|
"plotunselected" event.
|
||||||
|
|
||||||
|
- getSelection()
|
||||||
|
|
||||||
|
Returns the current selection in the same format as the "plotselected"
|
||||||
|
event. If there's currently no selection, the function returns null.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function ($) {
|
||||||
|
function init(plot) {
|
||||||
|
var selection = {
|
||||||
|
first: { x: -1, y: -1}, second: { x: -1, y: -1},
|
||||||
|
show: false,
|
||||||
|
active: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: The drag handling implemented here should be
|
||||||
|
// abstracted out, there's some similar code from a library in
|
||||||
|
// the navigation plugin, this should be massaged a bit to fit
|
||||||
|
// the Flot cases here better and reused. Doing this would
|
||||||
|
// make this plugin much slimmer.
|
||||||
|
var savedhandlers = {};
|
||||||
|
|
||||||
|
var mouseUpHandler = null;
|
||||||
|
|
||||||
|
function onMouseMove(e) {
|
||||||
|
if (selection.active) {
|
||||||
|
updateSelection(e);
|
||||||
|
|
||||||
|
plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseDown(e) {
|
||||||
|
if (e.which != 1) // only accept left-click
|
||||||
|
return;
|
||||||
|
|
||||||
|
// cancel out any text selections
|
||||||
|
document.body.focus();
|
||||||
|
|
||||||
|
// prevent text selection and drag in old-school browsers
|
||||||
|
if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
|
||||||
|
savedhandlers.onselectstart = document.onselectstart;
|
||||||
|
document.onselectstart = function () { return false; };
|
||||||
|
}
|
||||||
|
if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
|
||||||
|
savedhandlers.ondrag = document.ondrag;
|
||||||
|
document.ondrag = function () { return false; };
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionPos(selection.first, e);
|
||||||
|
|
||||||
|
selection.active = true;
|
||||||
|
|
||||||
|
// this is a bit silly, but we have to use a closure to be
|
||||||
|
// able to whack the same handler again
|
||||||
|
mouseUpHandler = function (e) { onMouseUp(e); };
|
||||||
|
|
||||||
|
$(document).one("mouseup", mouseUpHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(e) {
|
||||||
|
mouseUpHandler = null;
|
||||||
|
|
||||||
|
// revert drag stuff for old-school browsers
|
||||||
|
if (document.onselectstart !== undefined)
|
||||||
|
document.onselectstart = savedhandlers.onselectstart;
|
||||||
|
if (document.ondrag !== undefined)
|
||||||
|
document.ondrag = savedhandlers.ondrag;
|
||||||
|
|
||||||
|
// no more dragging
|
||||||
|
selection.active = false;
|
||||||
|
updateSelection(e);
|
||||||
|
|
||||||
|
if (selectionIsSane())
|
||||||
|
triggerSelectedEvent();
|
||||||
|
else {
|
||||||
|
// this counts as a clear
|
||||||
|
plot.getPlaceholder().trigger("plotunselected", [ ]);
|
||||||
|
plot.getPlaceholder().trigger("plotselecting", [ null ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelection() {
|
||||||
|
if (!selectionIsSane())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!selection.show) return null;
|
||||||
|
|
||||||
|
var r = {}, c1 = selection.first, c2 = selection.second;
|
||||||
|
$.each(plot.getAxes(), function (name, axis) {
|
||||||
|
if (axis.used) {
|
||||||
|
var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
|
||||||
|
r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerSelectedEvent() {
|
||||||
|
var r = getSelection();
|
||||||
|
|
||||||
|
plot.getPlaceholder().trigger("plotselected", [ r ]);
|
||||||
|
|
||||||
|
// backwards-compat stuff, to be removed in future
|
||||||
|
if (r.xaxis && r.yaxis)
|
||||||
|
plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(min, value, max) {
|
||||||
|
return value < min ? min: (value > max ? max: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectionPos(pos, e) {
|
||||||
|
var o = plot.getOptions();
|
||||||
|
var offset = plot.getPlaceholder().offset();
|
||||||
|
var plotOffset = plot.getPlotOffset();
|
||||||
|
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
|
||||||
|
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
|
||||||
|
|
||||||
|
if (o.selection.mode == "y")
|
||||||
|
pos.x = pos == selection.first ? 0 : plot.width();
|
||||||
|
|
||||||
|
if (o.selection.mode == "x")
|
||||||
|
pos.y = pos == selection.first ? 0 : plot.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelection(pos) {
|
||||||
|
if (pos.pageX == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setSelectionPos(selection.second, pos);
|
||||||
|
if (selectionIsSane()) {
|
||||||
|
selection.show = true;
|
||||||
|
plot.triggerRedrawOverlay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
clearSelection(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelection(preventEvent) {
|
||||||
|
if (selection.show) {
|
||||||
|
selection.show = false;
|
||||||
|
plot.triggerRedrawOverlay();
|
||||||
|
if (!preventEvent)
|
||||||
|
plot.getPlaceholder().trigger("plotunselected", [ ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function taken from markings support in Flot
|
||||||
|
function extractRange(ranges, coord) {
|
||||||
|
var axis, from, to, key, axes = plot.getAxes();
|
||||||
|
|
||||||
|
for (var k in axes) {
|
||||||
|
axis = axes[k];
|
||||||
|
if (axis.direction == coord) {
|
||||||
|
key = coord + axis.n + "axis";
|
||||||
|
if (!ranges[key] && axis.n == 1)
|
||||||
|
key = coord + "axis"; // support x1axis as xaxis
|
||||||
|
if (ranges[key]) {
|
||||||
|
from = ranges[key].from;
|
||||||
|
to = ranges[key].to;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// backwards-compat stuff - to be removed in future
|
||||||
|
if (!ranges[key]) {
|
||||||
|
axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
|
||||||
|
from = ranges[coord + "1"];
|
||||||
|
to = ranges[coord + "2"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto-reverse as an added bonus
|
||||||
|
if (from != null && to != null && from > to) {
|
||||||
|
var tmp = from;
|
||||||
|
from = to;
|
||||||
|
to = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { from: from, to: to, axis: axis };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelection(ranges, preventEvent) {
|
||||||
|
var axis, range, o = plot.getOptions();
|
||||||
|
|
||||||
|
if (o.selection.mode == "y") {
|
||||||
|
selection.first.x = 0;
|
||||||
|
selection.second.x = plot.width();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
range = extractRange(ranges, "x");
|
||||||
|
|
||||||
|
selection.first.x = range.axis.p2c(range.from);
|
||||||
|
selection.second.x = range.axis.p2c(range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o.selection.mode == "x") {
|
||||||
|
selection.first.y = 0;
|
||||||
|
selection.second.y = plot.height();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
range = extractRange(ranges, "y");
|
||||||
|
|
||||||
|
selection.first.y = range.axis.p2c(range.from);
|
||||||
|
selection.second.y = range.axis.p2c(range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.show = true;
|
||||||
|
plot.triggerRedrawOverlay();
|
||||||
|
if (!preventEvent && selectionIsSane())
|
||||||
|
triggerSelectedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectionIsSane() {
|
||||||
|
var minSize = plot.getOptions().selection.minSize;
|
||||||
|
return Math.abs(selection.second.x - selection.first.x) >= minSize &&
|
||||||
|
Math.abs(selection.second.y - selection.first.y) >= minSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
plot.clearSelection = clearSelection;
|
||||||
|
plot.setSelection = setSelection;
|
||||||
|
plot.getSelection = getSelection;
|
||||||
|
|
||||||
|
plot.hooks.bindEvents.push(function(plot, eventHolder) {
|
||||||
|
var o = plot.getOptions();
|
||||||
|
if (o.selection.mode != null) {
|
||||||
|
eventHolder.mousemove(onMouseMove);
|
||||||
|
eventHolder.mousedown(onMouseDown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
plot.hooks.drawOverlay.push(function (plot, ctx) {
|
||||||
|
// draw selection
|
||||||
|
if (selection.show && selectionIsSane()) {
|
||||||
|
var plotOffset = plot.getPlotOffset();
|
||||||
|
var o = plot.getOptions();
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(plotOffset.left, plotOffset.top);
|
||||||
|
|
||||||
|
var c = $.color.parse(o.selection.color);
|
||||||
|
|
||||||
|
ctx.strokeStyle = c.scale('a', 0.8).toString();
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.lineJoin = o.selection.shape;
|
||||||
|
ctx.fillStyle = c.scale('a', 0.4).toString();
|
||||||
|
|
||||||
|
var x = Math.min(selection.first.x, selection.second.x) + 0.5,
|
||||||
|
y = Math.min(selection.first.y, selection.second.y) + 0.5,
|
||||||
|
w = Math.abs(selection.second.x - selection.first.x) - 1,
|
||||||
|
h = Math.abs(selection.second.y - selection.first.y) - 1;
|
||||||
|
|
||||||
|
ctx.fillRect(x, y, w, h);
|
||||||
|
ctx.strokeRect(x, y, w, h);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
plot.hooks.shutdown.push(function (plot, eventHolder) {
|
||||||
|
eventHolder.unbind("mousemove", onMouseMove);
|
||||||
|
eventHolder.unbind("mousedown", onMouseDown);
|
||||||
|
|
||||||
|
if (mouseUpHandler)
|
||||||
|
$(document).unbind("mouseup", mouseUpHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: {
|
||||||
|
selection: {
|
||||||
|
mode: null, // one of null, "x", "y" or "xy"
|
||||||
|
color: "#e8cfac",
|
||||||
|
shape: "round", // one of "round", "miter", or "bevel"
|
||||||
|
minSize: 5 // minimum number of pixels
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: 'selection',
|
||||||
|
version: '1.1'
|
||||||
|
});
|
||||||
|
})(jQuery);
|
188
src/vendor/timeserieswidget/jquery.flot.stack.js
vendored
Normal file
188
src/vendor/timeserieswidget/jquery.flot.stack.js
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/* Flot plugin for stacking data sets rather than overlyaing them.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
The plugin assumes the data is sorted on x (or y if stacking horizontally).
|
||||||
|
For line charts, it is assumed that if a line has an undefined gap (from a
|
||||||
|
null point), then the line above it should have the same gap - insert zeros
|
||||||
|
instead of "null" if you want another behaviour. This also holds for the start
|
||||||
|
and end of the chart. Note that stacking a mix of positive and negative values
|
||||||
|
in most instances doesn't make sense (so it looks weird).
|
||||||
|
|
||||||
|
Two or more series are stacked when their "stack" attribute is set to the same
|
||||||
|
key (which can be any number or string or just "true"). To specify the default
|
||||||
|
stack, you can set the stack option like this:
|
||||||
|
|
||||||
|
series: {
|
||||||
|
stack: null/false, true, or a key (number/string)
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also specify it for a single series, like this:
|
||||||
|
|
||||||
|
$.plot( $("#placeholder"), [{
|
||||||
|
data: [ ... ],
|
||||||
|
stack: true
|
||||||
|
}])
|
||||||
|
|
||||||
|
The stacking order is determined by the order of the data series in the array
|
||||||
|
(later series end up on top of the previous).
|
||||||
|
|
||||||
|
Internally, the plugin modifies the datapoints in each series, adding an
|
||||||
|
offset to the y value. For line series, extra data points are inserted through
|
||||||
|
interpolation. If there's a second y value, it's also adjusted (e.g for bar
|
||||||
|
charts or filled areas).
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function ($) {
|
||||||
|
var options = {
|
||||||
|
series: { stack: null } // or number/string
|
||||||
|
};
|
||||||
|
|
||||||
|
function init(plot) {
|
||||||
|
function findMatchingSeries(s, allseries) {
|
||||||
|
var res = null;
|
||||||
|
for (var i = 0; i < allseries.length; ++i) {
|
||||||
|
if (s == allseries[i])
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (allseries[i].stack == s.stack)
|
||||||
|
res = allseries[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stackData(plot, s, datapoints) {
|
||||||
|
if (s.stack == null || s.stack === false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var other = findMatchingSeries(s, plot.getData());
|
||||||
|
if (!other)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ps = datapoints.pointsize,
|
||||||
|
points = datapoints.points,
|
||||||
|
otherps = other.datapoints.pointsize,
|
||||||
|
otherpoints = other.datapoints.points,
|
||||||
|
newpoints = [],
|
||||||
|
px, py, intery, qx, qy, bottom,
|
||||||
|
withlines = s.lines.show,
|
||||||
|
horizontal = s.bars.horizontal,
|
||||||
|
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
|
||||||
|
withsteps = withlines && s.lines.steps,
|
||||||
|
fromgap = true,
|
||||||
|
keyOffset = horizontal ? 1 : 0,
|
||||||
|
accumulateOffset = horizontal ? 0 : 1,
|
||||||
|
i = 0, j = 0, l, m;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i >= points.length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
l = newpoints.length;
|
||||||
|
|
||||||
|
if (points[i] == null) {
|
||||||
|
// copy gaps
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(points[i + m]);
|
||||||
|
i += ps;
|
||||||
|
}
|
||||||
|
else if (j >= otherpoints.length) {
|
||||||
|
// for lines, we can't use the rest of the points
|
||||||
|
if (!withlines) {
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(points[i + m]);
|
||||||
|
}
|
||||||
|
i += ps;
|
||||||
|
}
|
||||||
|
else if (otherpoints[j] == null) {
|
||||||
|
// oops, got a gap
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(null);
|
||||||
|
fromgap = true;
|
||||||
|
j += otherps;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// cases where we actually got two points
|
||||||
|
px = points[i + keyOffset];
|
||||||
|
py = points[i + accumulateOffset];
|
||||||
|
qx = otherpoints[j + keyOffset];
|
||||||
|
qy = otherpoints[j + accumulateOffset];
|
||||||
|
bottom = 0;
|
||||||
|
|
||||||
|
if (px == qx) {
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(points[i + m]);
|
||||||
|
|
||||||
|
newpoints[l + accumulateOffset] += qy;
|
||||||
|
bottom = qy;
|
||||||
|
|
||||||
|
i += ps;
|
||||||
|
j += otherps;
|
||||||
|
}
|
||||||
|
else if (px > qx) {
|
||||||
|
// we got past point below, might need to
|
||||||
|
// insert interpolated extra point
|
||||||
|
if (withlines && i > 0 && points[i - ps] != null) {
|
||||||
|
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||||
|
newpoints.push(qx);
|
||||||
|
newpoints.push(intery + qy);
|
||||||
|
for (m = 2; m < ps; ++m)
|
||||||
|
newpoints.push(points[i + m]);
|
||||||
|
bottom = qy;
|
||||||
|
}
|
||||||
|
|
||||||
|
j += otherps;
|
||||||
|
}
|
||||||
|
else { // px < qx
|
||||||
|
if (fromgap && withlines) {
|
||||||
|
// if we come from a gap, we just skip this point
|
||||||
|
i += ps;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints.push(points[i + m]);
|
||||||
|
|
||||||
|
// we might be able to interpolate a point below,
|
||||||
|
// this can give us a better y
|
||||||
|
if (withlines && j > 0 && otherpoints[j - otherps] != null)
|
||||||
|
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
|
||||||
|
|
||||||
|
newpoints[l + accumulateOffset] += bottom;
|
||||||
|
|
||||||
|
i += ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
fromgap = false;
|
||||||
|
|
||||||
|
if (l != newpoints.length && withbottom)
|
||||||
|
newpoints[l + 2] += bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maintain the line steps invariant
|
||||||
|
if (withsteps && l != newpoints.length && l > 0
|
||||||
|
&& newpoints[l] != null
|
||||||
|
&& newpoints[l] != newpoints[l - ps]
|
||||||
|
&& newpoints[l + 1] != newpoints[l - ps + 1]) {
|
||||||
|
for (m = 0; m < ps; ++m)
|
||||||
|
newpoints[l + ps + m] = newpoints[l + m];
|
||||||
|
newpoints[l + 1] = newpoints[l - ps + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datapoints.points = newpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
plot.hooks.processDatapoints.push(stackData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: options,
|
||||||
|
name: 'stack',
|
||||||
|
version: '1.2'
|
||||||
|
});
|
||||||
|
})(jQuery);
|
424
src/vendor/timeserieswidget/jquery.flot.time.js
vendored
Normal file
424
src/vendor/timeserieswidget/jquery.flot.time.js
vendored
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
/* Pretty handling of time axes.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
Set axis.mode to "time" to enable. See the section "Time series data" in
|
||||||
|
API.txt for details.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
xaxis: {
|
||||||
|
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||||
|
timeformat: null, // format string to use
|
||||||
|
twelveHourClock: false, // 12 or 24 time in time mode
|
||||||
|
monthNames: null // list of names of months
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// round to nearby lower multiple of base
|
||||||
|
|
||||||
|
function floorInBase(n, base) {
|
||||||
|
return base * Math.floor(n / base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string with the date d formatted according to fmt.
|
||||||
|
// A subset of the Open Group's strftime format is supported.
|
||||||
|
|
||||||
|
function formatDate(d, fmt, monthNames, dayNames) {
|
||||||
|
|
||||||
|
if (typeof d.strftime == "function") {
|
||||||
|
return d.strftime(fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftPad = function(n, pad) {
|
||||||
|
n = "" + n;
|
||||||
|
pad = "" + (pad == null ? "0" : pad);
|
||||||
|
return n.length == 1 ? pad + n : n;
|
||||||
|
};
|
||||||
|
|
||||||
|
var r = [];
|
||||||
|
var escape = false;
|
||||||
|
var hours = d.getHours();
|
||||||
|
var isAM = hours < 12;
|
||||||
|
|
||||||
|
if (monthNames == null) {
|
||||||
|
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayNames == null) {
|
||||||
|
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var hours12;
|
||||||
|
|
||||||
|
if (hours > 12) {
|
||||||
|
hours12 = hours - 12;
|
||||||
|
} else if (hours == 0) {
|
||||||
|
hours12 = 12;
|
||||||
|
} else {
|
||||||
|
hours12 = hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < fmt.length; ++i) {
|
||||||
|
|
||||||
|
var c = fmt.charAt(i);
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
switch (c) {
|
||||||
|
case 'a': c = "" + dayNames[d.getDay()]; break;
|
||||||
|
case 'b': c = "" + monthNames[d.getMonth()]; break;
|
||||||
|
case 'd': c = leftPad(d.getDate()); break;
|
||||||
|
case 'e': c = leftPad(d.getDate(), " "); break;
|
||||||
|
case 'H': c = leftPad(hours); break;
|
||||||
|
case 'I': c = leftPad(hours12); break;
|
||||||
|
case 'l': c = leftPad(hours12, " "); break;
|
||||||
|
case 'm': c = leftPad(d.getMonth() + 1); break;
|
||||||
|
case 'M': c = leftPad(d.getMinutes()); break;
|
||||||
|
// quarters not in Open Group's strftime specification
|
||||||
|
case 'q':
|
||||||
|
c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
|
||||||
|
case 'S': c = leftPad(d.getSeconds()); break;
|
||||||
|
case 'y': c = leftPad(d.getFullYear() % 100); break;
|
||||||
|
case 'Y': c = "" + d.getFullYear(); break;
|
||||||
|
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
|
||||||
|
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
|
||||||
|
case 'w': c = "" + d.getDay(); break;
|
||||||
|
}
|
||||||
|
r.push(c);
|
||||||
|
escape = false;
|
||||||
|
} else {
|
||||||
|
if (c == "%") {
|
||||||
|
escape = true;
|
||||||
|
} else {
|
||||||
|
r.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// To have a consistent view of time-based data independent of which time
|
||||||
|
// zone the client happens to be in we need a date-like object independent
|
||||||
|
// of time zones. This is done through a wrapper that only calls the UTC
|
||||||
|
// versions of the accessor methods.
|
||||||
|
|
||||||
|
function makeUtcWrapper(d) {
|
||||||
|
|
||||||
|
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
|
||||||
|
sourceObj[sourceMethod] = function() {
|
||||||
|
return targetObj[targetMethod].apply(targetObj, arguments);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var utc = {
|
||||||
|
date: d
|
||||||
|
};
|
||||||
|
|
||||||
|
// support strftime, if found
|
||||||
|
|
||||||
|
if (d.strftime != undefined) {
|
||||||
|
addProxyMethod(utc, "strftime", d, "strftime");
|
||||||
|
}
|
||||||
|
|
||||||
|
addProxyMethod(utc, "getTime", d, "getTime");
|
||||||
|
addProxyMethod(utc, "setTime", d, "setTime");
|
||||||
|
|
||||||
|
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
|
||||||
|
|
||||||
|
for (var p = 0; p < props.length; p++) {
|
||||||
|
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
|
||||||
|
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return utc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// select time zone strategy. This returns a date-like object tied to the
|
||||||
|
// desired timezone
|
||||||
|
|
||||||
|
function dateGenerator(ts, opts) {
|
||||||
|
if (opts.timezone == "browser") {
|
||||||
|
return new Date(ts);
|
||||||
|
} else if (!opts.timezone || opts.timezone == "utc") {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
|
||||||
|
var d = new timezoneJS.Date();
|
||||||
|
// timezone-js is fickle, so be sure to set the time zone before
|
||||||
|
// setting the time.
|
||||||
|
d.setTimezone(opts.timezone);
|
||||||
|
d.setTime(ts);
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map of app. size of time units in milliseconds
|
||||||
|
|
||||||
|
var timeUnitSize = {
|
||||||
|
"second": 1000,
|
||||||
|
"minute": 60 * 1000,
|
||||||
|
"hour": 60 * 60 * 1000,
|
||||||
|
"day": 24 * 60 * 60 * 1000,
|
||||||
|
"month": 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"year": 365.2425 * 24 * 60 * 60 * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// the allowed tick sizes, after 1 year we use
|
||||||
|
// an integer algorithm
|
||||||
|
|
||||||
|
var baseSpec = [
|
||||||
|
[1, "second"], [2, "second"], [5, "second"], [10, "second"],
|
||||||
|
[30, "second"],
|
||||||
|
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
|
||||||
|
[30, "minute"],
|
||||||
|
[1, "hour"], [2, "hour"], [4, "hour"],
|
||||||
|
[8, "hour"], [12, "hour"],
|
||||||
|
[1, "day"], [2, "day"], [3, "day"],
|
||||||
|
[0.25, "month"], [0.5, "month"], [1, "month"],
|
||||||
|
[2, "month"]
|
||||||
|
];
|
||||||
|
|
||||||
|
// we don't know which variant(s) we'll need yet, but generating both is
|
||||||
|
// cheap
|
||||||
|
|
||||||
|
var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
|
||||||
|
[1, "year"]]);
|
||||||
|
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
|
||||||
|
[1, "year"]]);
|
||||||
|
|
||||||
|
function init(plot) {
|
||||||
|
plot.hooks.processDatapoints.push(function (plot, series, datapoints) {
|
||||||
|
$.each(plot.getAxes(), function(axisName, axis) {
|
||||||
|
|
||||||
|
var opts = axis.options;
|
||||||
|
|
||||||
|
if (opts.mode == "time") {
|
||||||
|
axis.tickGenerator = function(axis) {
|
||||||
|
|
||||||
|
var ticks = [];
|
||||||
|
var d = dateGenerator(axis.min, opts);
|
||||||
|
var minSize = 0;
|
||||||
|
|
||||||
|
// make quarter use a possibility if quarters are
|
||||||
|
// mentioned in either of these options
|
||||||
|
|
||||||
|
var spec = (opts.tickSize && opts.tickSize[1] ===
|
||||||
|
"quarter") ||
|
||||||
|
(opts.minTickSize && opts.minTickSize[1] ===
|
||||||
|
"quarter") ? specQuarters : specMonths;
|
||||||
|
|
||||||
|
if (opts.minTickSize != null) {
|
||||||
|
if (typeof opts.tickSize == "number") {
|
||||||
|
minSize = opts.tickSize;
|
||||||
|
} else {
|
||||||
|
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < spec.length - 1; ++i) {
|
||||||
|
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
|
||||||
|
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
|
||||||
|
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = spec[i][0];
|
||||||
|
var unit = spec[i][1];
|
||||||
|
|
||||||
|
// special-case the possibility of several years
|
||||||
|
|
||||||
|
if (unit == "year") {
|
||||||
|
|
||||||
|
// if given a minTickSize in years, just use it,
|
||||||
|
// ensuring that it's an integer
|
||||||
|
|
||||||
|
if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
|
||||||
|
size = Math.floor(opts.minTickSize[0]);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
|
||||||
|
var norm = (axis.delta / timeUnitSize.year) / magn;
|
||||||
|
|
||||||
|
if (norm < 1.5) {
|
||||||
|
size = 1;
|
||||||
|
} else if (norm < 3) {
|
||||||
|
size = 2;
|
||||||
|
} else if (norm < 7.5) {
|
||||||
|
size = 5;
|
||||||
|
} else {
|
||||||
|
size = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
size *= magn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimum size for years is 1
|
||||||
|
|
||||||
|
if (size < 1) {
|
||||||
|
size = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axis.tickSize = opts.tickSize || [size, unit];
|
||||||
|
var tickSize = axis.tickSize[0];
|
||||||
|
unit = axis.tickSize[1];
|
||||||
|
|
||||||
|
var step = tickSize * timeUnitSize[unit];
|
||||||
|
|
||||||
|
if (unit == "second") {
|
||||||
|
d.setSeconds(floorInBase(d.getSeconds(), tickSize));
|
||||||
|
} else if (unit == "minute") {
|
||||||
|
d.setMinutes(floorInBase(d.getMinutes(), tickSize));
|
||||||
|
} else if (unit == "hour") {
|
||||||
|
d.setHours(floorInBase(d.getHours(), tickSize));
|
||||||
|
} else if (unit == "month") {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), tickSize));
|
||||||
|
} else if (unit == "quarter") {
|
||||||
|
d.setMonth(3 * floorInBase(d.getMonth() / 3,
|
||||||
|
tickSize));
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(floorInBase(d.getFullYear(), tickSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset smaller components
|
||||||
|
|
||||||
|
d.setMilliseconds(0);
|
||||||
|
|
||||||
|
if (step >= timeUnitSize.minute) {
|
||||||
|
d.setSeconds(0);
|
||||||
|
} else if (step >= timeUnitSize.hour) {
|
||||||
|
d.setMinutes(0);
|
||||||
|
} else if (step >= timeUnitSize.day) {
|
||||||
|
d.setHours(0);
|
||||||
|
} else if (step >= timeUnitSize.day * 4) {
|
||||||
|
d.setDate(1);
|
||||||
|
} else if (step >= timeUnitSize.month * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 3));
|
||||||
|
} else if (step >= timeUnitSize.quarter * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 6));
|
||||||
|
} else if (step >= timeUnitSize.year) {
|
||||||
|
d.setMonth(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var carry = 0;
|
||||||
|
var v = Number.NaN;
|
||||||
|
var prev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
prev = v;
|
||||||
|
v = d.getTime();
|
||||||
|
ticks.push(v);
|
||||||
|
|
||||||
|
if (unit == "month" || unit == "quarter") {
|
||||||
|
if (tickSize < 1) {
|
||||||
|
|
||||||
|
// a bit complicated - we'll divide the
|
||||||
|
// month/quarter up but we need to take
|
||||||
|
// care of fractions so we don't end up in
|
||||||
|
// the middle of a day
|
||||||
|
|
||||||
|
d.setDate(1);
|
||||||
|
var start = d.getTime();
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
(unit == "quarter" ? 3 : 1));
|
||||||
|
var end = d.getTime();
|
||||||
|
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
|
||||||
|
carry = d.getHours();
|
||||||
|
d.setHours(0);
|
||||||
|
} else {
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
tickSize * (unit == "quarter" ? 3 : 1));
|
||||||
|
}
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(d.getFullYear() + tickSize);
|
||||||
|
} else {
|
||||||
|
d.setTime(v + step);
|
||||||
|
}
|
||||||
|
} while (v < axis.max && v != prev);
|
||||||
|
|
||||||
|
return ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
axis.tickFormatter = function (v, axis) {
|
||||||
|
|
||||||
|
var d = dateGenerator(v, axis.options);
|
||||||
|
|
||||||
|
// first check global format
|
||||||
|
|
||||||
|
if (opts.timeformat != null) {
|
||||||
|
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// possibly use quarters if quarters are mentioned in
|
||||||
|
// any of these places
|
||||||
|
|
||||||
|
var useQuarters = (axis.options.tickSize &&
|
||||||
|
axis.options.tickSize[1] == "quarter") ||
|
||||||
|
(axis.options.minTickSize &&
|
||||||
|
axis.options.minTickSize[1] == "quarter");
|
||||||
|
|
||||||
|
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
|
||||||
|
var span = axis.max - axis.min;
|
||||||
|
var suffix = (opts.twelveHourClock) ? " %p" : "";
|
||||||
|
var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
|
||||||
|
var fmt;
|
||||||
|
|
||||||
|
if (t < timeUnitSize.minute) {
|
||||||
|
fmt = hourCode + ":%M:%S" + suffix;
|
||||||
|
} else if (t < timeUnitSize.day) {
|
||||||
|
if (span < 2 * timeUnitSize.day) {
|
||||||
|
fmt = hourCode + ":%M" + suffix;
|
||||||
|
} else {
|
||||||
|
fmt = "%b %d " + hourCode + ":%M" + suffix;
|
||||||
|
}
|
||||||
|
} else if (t < timeUnitSize.month) {
|
||||||
|
fmt = "%b %d";
|
||||||
|
} else if ((useQuarters && t < timeUnitSize.quarter) ||
|
||||||
|
(!useQuarters && t < timeUnitSize.year)) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "%b";
|
||||||
|
} else {
|
||||||
|
fmt = "%b %Y";
|
||||||
|
}
|
||||||
|
} else if (useQuarters && t < timeUnitSize.year) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "Q%q";
|
||||||
|
} else {
|
||||||
|
fmt = "Q%q %Y";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt = "%Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
|
||||||
|
|
||||||
|
return rt;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: options,
|
||||||
|
name: 'time',
|
||||||
|
version: '1.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Time-axis support used to be in Flot core, which exposed the
|
||||||
|
// formatDate function on the plot object. Various plugins depend
|
||||||
|
// on the function, so we need to re-expose it here.
|
||||||
|
|
||||||
|
$.plot.formatDate = formatDate;
|
||||||
|
|
||||||
|
})(jQuery);
|
838
src/vendor/timeserieswidget/jquery.tswidget.js
vendored
Normal file
838
src/vendor/timeserieswidget/jquery.tswidget.js
vendored
Normal file
@ -0,0 +1,838 @@
|
|||||||
|
function strip_ending_slash(str) {
|
||||||
|
if(str.substr(-1) == '/') {
|
||||||
|
return str.substr(0, str.length - 1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncate_str(str) {
|
||||||
|
if (str.length >= 147) {
|
||||||
|
return str.substring(0, 148) + "...";
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_graphite_options(options, raw) {
|
||||||
|
raw = raw || false;
|
||||||
|
var clean_options = [];
|
||||||
|
internal_options = ['_t'];
|
||||||
|
graphite_options = ['target', 'targets', 'from', 'until', 'rawData', 'format'];
|
||||||
|
graphite_png_options = ['areaMode', 'width', 'height', 'template', 'margin', 'bgcolor',
|
||||||
|
'fgcolor', 'fontName', 'fontSize', 'fontBold', 'fontItalic',
|
||||||
|
'yMin', 'yMax', 'colorList', 'title', 'vtitle', 'lineMode',
|
||||||
|
'lineWith', 'hideLegend', 'hideAxes', 'hideGrid', 'minXstep',
|
||||||
|
'majorGridlineColor', 'minorGridLineColor', 'minorY',
|
||||||
|
'thickness', 'min', 'max', 'tz'];
|
||||||
|
|
||||||
|
if(raw) {
|
||||||
|
options['format'] = 'json';
|
||||||
|
} else {
|
||||||
|
// use random parameter to force image refresh
|
||||||
|
options["_t"] = options["_t"] || Math.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(options, function (key, value) {
|
||||||
|
if(raw) {
|
||||||
|
if ($.inArray(key, graphite_options) == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($.inArray(key, graphite_options) == -1 && $.inArray(key, graphite_png_options) == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key === "targets") {
|
||||||
|
$.each(value, function (index, value) {
|
||||||
|
if (raw) {
|
||||||
|
// it's normally pointless to use alias() in raw mode, because we apply an alias (name) ourself
|
||||||
|
// in the client rendering step. we just need graphite to return the target.
|
||||||
|
// but graphite sometimes alters the name of the target in the returned data
|
||||||
|
// (https://github.com/graphite-project/graphite-web/issues/248)
|
||||||
|
// so we need a good string identifier and set it using alias() (which graphite will honor)
|
||||||
|
// so that we recognize the returned output. simplest is just to include the target spec again
|
||||||
|
// though this duplicates a lot of info in the url.
|
||||||
|
clean_options.push("target=alias(" + encodeURIComponent(value.target) + ",'" + value.target +"')");
|
||||||
|
} else {
|
||||||
|
clean_options.push("target=alias(color(" +encodeURIComponent(value.target + ",'" + value.color) +"'),'" + value.name +"')");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (value !== null) {
|
||||||
|
clean_options.push(key + "=" + encodeURIComponent(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return clean_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build url for an image. but GET url's are limited in length, so if you have many/long params, some may be missing.
|
||||||
|
// could be made smarter for example to favor non-target options because we usually don't want to loose any of those.
|
||||||
|
function build_graphite_url(options) {
|
||||||
|
var limit = 2000; // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
|
||||||
|
var url = options.graphite_url + "?";
|
||||||
|
|
||||||
|
options = build_graphite_options(options, false);
|
||||||
|
$.map(options, function(option) {
|
||||||
|
if (url.length + option.length < limit) {
|
||||||
|
url += '&' + option;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url.replace(/\?&/, "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_anthracite_url(options) {
|
||||||
|
url = strip_ending_slash(options.anthracite_url) + '/events/json';
|
||||||
|
if ('events_query' in options) {
|
||||||
|
url += '?q=' + options['events_query'];
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_definition (target_graphite, options) {
|
||||||
|
var matching_i = undefined;
|
||||||
|
for (var cfg_i = 0; cfg_i < options.targets.length && matching_i == undefined; cfg_i++) {
|
||||||
|
// string match (no globbing)
|
||||||
|
if(options.targets[cfg_i].target == target_graphite.target) {
|
||||||
|
matching_i = cfg_i;
|
||||||
|
}
|
||||||
|
// glob match?
|
||||||
|
else if(target_graphite.target.graphiteGlob(options.targets[cfg_i].target)) {
|
||||||
|
matching_i = cfg_i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matching_i == undefined) {
|
||||||
|
console.error ("internal error: could not figure out which target_option target_graphite '" +
|
||||||
|
target_graphite.target + "' comes from");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return options.targets[matching_i];
|
||||||
|
}
|
||||||
|
|
||||||
|
(function ($) {
|
||||||
|
/*
|
||||||
|
from graphite-web-0.9.9/graphTemplates.conf.example:
|
||||||
|
|
||||||
|
[default]
|
||||||
|
background = black
|
||||||
|
foreground = white
|
||||||
|
majorLine = white
|
||||||
|
minorLine = grey
|
||||||
|
lineColors = blue,green,red,purple,brown,yellow,aqua,grey,magenta,pink,gold,rose
|
||||||
|
fontName = Sans
|
||||||
|
fontSize = 10
|
||||||
|
fontBold = False
|
||||||
|
fontItalic = False
|
||||||
|
|
||||||
|
definitions below are from http://graphite.readthedocs.org/en/1.0/url-api.html
|
||||||
|
*/
|
||||||
|
var default_graphite_options = {
|
||||||
|
'bgcolor': '#000000', // background color of the graph
|
||||||
|
'fgcolor' : '#ffffff', // title, legend text, and axis labels
|
||||||
|
'majorLine': '#ffffff',
|
||||||
|
'minorLine': '#afafaf'
|
||||||
|
}
|
||||||
|
var default_tswidget_options = {
|
||||||
|
'events_color': '#ccff66',
|
||||||
|
'es_events_color': '#ff0066',
|
||||||
|
'events_text_color': '#5C991F'
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.graphite = function (options) {
|
||||||
|
if (options === "update") {
|
||||||
|
$.fn.graphite.update(this, arguments[1]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize plugin //
|
||||||
|
options = options || {};
|
||||||
|
var settings = $.extend({}, $.fn.graphite.defaults, options);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
$this = $(this);
|
||||||
|
|
||||||
|
$this.data("graphOptions", settings);
|
||||||
|
$.fn.graphite.render($this, settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphite.render = function($img, options) {
|
||||||
|
$img.attr("src", build_graphite_url(options));
|
||||||
|
$img.attr("height", options.height);
|
||||||
|
$img.attr("width", options.width);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphite.update = function($img, options) {
|
||||||
|
options = options || {};
|
||||||
|
$img.each(function () {
|
||||||
|
$this = $(this);
|
||||||
|
var settings = $.extend({}, $this.data("graphOptions"), options);
|
||||||
|
$this.data("graphOptions", settings);
|
||||||
|
$.fn.graphite.render($this, settings);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// note: graphite json output is a list of dicts like:
|
||||||
|
// {"datapoints": [...], "target": "<metricname>" }
|
||||||
|
// if you did alias(series, "foo") then "target" will contain the alias
|
||||||
|
// (loosing the metricname which is bad, esp. when you had a glob with an alias, then you don't know what's what)
|
||||||
|
// rickshaw: options.series is a list of dicts like:
|
||||||
|
// { name: "alias", color: "foo", data: [{x: (...), y: (...)} , ...]}
|
||||||
|
// we basically tell users to use this dict, with extra 'target' to specify graphite target string
|
||||||
|
// flot: d = [[<ts>, <val>], (...)]
|
||||||
|
// plot ($(..), [d], ..)
|
||||||
|
$.fn.graphiteRick = function (options, on_error) {
|
||||||
|
options = options || {};
|
||||||
|
var settings = $.extend({}, default_graphite_options, default_tswidget_options, $.fn.graphite.defaults, options);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
$this = $(this);
|
||||||
|
$this.data("graphOptions", settings);
|
||||||
|
$.fn.graphiteRick.render(this, settings, on_error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphiteFlot = function (options, on_error) {
|
||||||
|
if ('zoneFileBasePath' in options) {
|
||||||
|
timezoneJS.timezone.zoneFileBasePath = options['zoneFileBasePath'];
|
||||||
|
timezoneJS.timezone.init();
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
var settings = $.extend({}, default_graphite_options, default_tswidget_options, $.fn.graphite.defaults, options);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
$this = $(this);
|
||||||
|
$this.data("graphOptions", settings);
|
||||||
|
$.fn.graphiteFlot.render(this, settings, on_error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphiteHighcharts = function (options, on_error) {
|
||||||
|
if ('zoneFileBasePath' in options) {
|
||||||
|
timezoneJS.timezone.zoneFileBasePath = options['zoneFileBasePath'];
|
||||||
|
timezoneJS.timezone.init();
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
var settings = $.extend({}, default_graphite_options, default_tswidget_options, $.fn.graphite.defaults, options);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
$this = $(this);
|
||||||
|
$this.data("graphOptions", settings);
|
||||||
|
$.fn.graphiteHighcharts.render(this, settings, on_error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphiteFlot.render = function(div, options, on_error) {
|
||||||
|
var id = div.getAttribute('id');
|
||||||
|
$div = $(div);
|
||||||
|
$div.height(options.height);
|
||||||
|
$div.width(options.width);
|
||||||
|
var events = [];
|
||||||
|
var es_events = [];
|
||||||
|
var all_targets = [];
|
||||||
|
var add_targets = function(response_data) {
|
||||||
|
for (var res_i = 0; res_i < response_data.length; res_i++) {
|
||||||
|
var target = find_definition(response_data[res_i], options);
|
||||||
|
target.label = target.name; // flot wants 'label'
|
||||||
|
target.data = [];
|
||||||
|
var nulls = 0;
|
||||||
|
var non_nulls = 0;
|
||||||
|
for (var i in response_data[res_i].datapoints) {
|
||||||
|
if(response_data[res_i].datapoints[i][0] == null) {
|
||||||
|
nulls++;
|
||||||
|
if('drawNullAsZero' in options && options['drawNullAsZero']) {
|
||||||
|
response_data[res_i].datapoints[i][0] = 0;
|
||||||
|
} else {
|
||||||
|
// don't tell flot about null values, it prevents adjacent non-null values from
|
||||||
|
// being rendered correctly
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
non_nulls++;
|
||||||
|
}
|
||||||
|
target.data.push([response_data[res_i].datapoints[i][1] * 1000, response_data[res_i].datapoints[i][0]]);
|
||||||
|
}
|
||||||
|
if (nulls/non_nulls > 0.3) {
|
||||||
|
console.log("warning: rendered target contains " + nulls + " null values, " + non_nulls + " non_nulls");
|
||||||
|
}
|
||||||
|
all_targets.push(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawFlot = function(es_data, anthracite_data) {
|
||||||
|
|
||||||
|
// default config state modifiers (you can override them in your config objects)
|
||||||
|
var states = {
|
||||||
|
'stacked': {
|
||||||
|
'series': {'stack': true, 'lines': {'show': true, 'lineWidth': 0, 'fill': 1}},
|
||||||
|
},
|
||||||
|
'lines': {
|
||||||
|
// flot lib wants 0 or null. not false o_O
|
||||||
|
'series': {'stack': null, 'lines': { 'show': true, 'lineWidth': 0.6, 'fill': false }}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if(!('states' in options)) {
|
||||||
|
options['states'] = {};
|
||||||
|
}
|
||||||
|
$.extend(options['states'], states);
|
||||||
|
|
||||||
|
function suffixFormatterSI(val, axis) {
|
||||||
|
range = axis.max - axis.min;
|
||||||
|
lowest = Math.min (range,val);
|
||||||
|
if (lowest >= Math.pow(10,12))
|
||||||
|
return (val / Math.pow(10,12)).toFixed(axis.tickDecimals) + " T";
|
||||||
|
if (lowest >= Math.pow(10,9))
|
||||||
|
return (val / Math.pow(10,9)).toFixed(axis.tickDecimals) + " G";
|
||||||
|
if (lowest >= Math.pow(10,6))
|
||||||
|
return (val / Math.pow(10,6)).toFixed(axis.tickDecimals) + " M";
|
||||||
|
if (lowest >= Math.pow(10,3))
|
||||||
|
return (val / Math.pow(10,3)).toFixed(axis.tickDecimals) + " k";
|
||||||
|
return val.toFixed(axis.tickDecimals);
|
||||||
|
}
|
||||||
|
function suffixFormatterBinary(val, axis) {
|
||||||
|
range = axis.max - axis.min;
|
||||||
|
lowest = Math.min (range,val);
|
||||||
|
if (lowest >= Math.pow(2,40))
|
||||||
|
return (val / Math.pow(2,40)).toFixed(axis.tickDecimals) + " Ti";
|
||||||
|
if (lowest >= Math.pow(2,30))
|
||||||
|
return (val / Math.pow(2,30)).toFixed(axis.tickDecimals) + " Gi";
|
||||||
|
if (lowest >= Math.pow(2,20))
|
||||||
|
return (val / Math.pow(2,20)).toFixed(axis.tickDecimals) + " Mi";
|
||||||
|
if (lowest >= Math.pow(2,10))
|
||||||
|
return (val / Math.pow(2,10)).toFixed(axis.tickDecimals) + " Ki";
|
||||||
|
return val.toFixed(axis.tickDecimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildFlotOptions = function(options) {
|
||||||
|
// xaxis color = title color and horizontal lines in grid
|
||||||
|
// yaxis color = vtitle color and vertical lines in grid
|
||||||
|
// xaxis tickcolor = override vertical lines in grid
|
||||||
|
// yaxis tickcolor = override horizontal lines in grid
|
||||||
|
// note: flot doesn't distinguish between major and minor line
|
||||||
|
// so i use minor, because graphite uses very thin lines which make them look less intense,
|
||||||
|
// in flot they seem to be a bit thicker, so generally make them less intense to have them look similar.
|
||||||
|
// although they still look more intense than in graphite though.
|
||||||
|
// tuning tickLength doesn't seem to help (and no lineWidth for axis..). maybe a graphite bug
|
||||||
|
options['tickColor'] = options['minorLine'];
|
||||||
|
options['xaxis'] = options['xaxis'] || {};
|
||||||
|
$.extend(options['xaxis'], { color: options['fgcolor'], tickColor: options['tickColor'], mode: 'time'});
|
||||||
|
if ('tz' in options) {
|
||||||
|
options['xaxis']['timezone'] = options['tz'];
|
||||||
|
}
|
||||||
|
options['yaxis'] = options['yaxis'] || {};
|
||||||
|
$.extend(options['yaxis'], { color: options['fgcolor'], tickColor: options['tickColor'], tickFormatter: suffixFormatterSI});
|
||||||
|
if('suffixes' in options) {
|
||||||
|
if(options['suffixes'] == 'binary') {
|
||||||
|
options['yaxis']['tickFormatter'] = suffixFormatterBinary;
|
||||||
|
} else if(!options['suffixes']) {
|
||||||
|
delete options['yaxis']['tickFormatter'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if('title' in options) {
|
||||||
|
options['xaxes'] = [{axisLabel: options['title']}];
|
||||||
|
}
|
||||||
|
if('vtitle' in options) {
|
||||||
|
options['yaxes'] = [{position: 'left', axisLabel: options['vtitle']}];
|
||||||
|
}
|
||||||
|
for (i = 0; i < options['targets'].length; i++ ) {
|
||||||
|
options['targets'][i]['color'] = options['targets'][i]['color'];
|
||||||
|
}
|
||||||
|
if(!('grid' in options)) {
|
||||||
|
options['grid'] = {};
|
||||||
|
}
|
||||||
|
if(options['hover_details']) {
|
||||||
|
options['grid']['hoverable'] = true;
|
||||||
|
options['grid']['autoHighlight'] = true; // show datapoint being hilighted. true by default but hardcode to make sure
|
||||||
|
}
|
||||||
|
if(!('markings' in options['grid'])) {
|
||||||
|
options['grid']['markings'] = [];
|
||||||
|
}
|
||||||
|
if(!('selection' in options)) {
|
||||||
|
options['selection'] = {
|
||||||
|
'mode': "xy"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
x = events[i].date * 1000;
|
||||||
|
options['grid']['markings'].push({ color: options['events_color'], lineWidth: 1, xaxis: { from: x, to: x} });
|
||||||
|
}
|
||||||
|
// custom es_events loop
|
||||||
|
for (var i = 0; i < es_events.length; i++) {
|
||||||
|
x = Date.parse(es_events[i]['_source']['@timestamp']);
|
||||||
|
options['grid']['markings'].push({ color: options['es_events_color'], lineWidth: 1, xaxis: { from: x, to: x} });
|
||||||
|
}
|
||||||
|
state = options['state'] || 'lines';
|
||||||
|
return $.extend(options, options['states'][state]);
|
||||||
|
}
|
||||||
|
var plot = $.plot(div, all_targets, buildFlotOptions(options));
|
||||||
|
$div.bind('plotselected', function (event, ranges) {
|
||||||
|
// clamp the zooming to prevent eternal zoom
|
||||||
|
|
||||||
|
if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
|
||||||
|
ranges.xaxis.to = ranges.xaxis.from + 0.00001;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
|
||||||
|
ranges.yaxis.to = ranges.yaxis.from + 0.00001;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the zooming
|
||||||
|
zoomed_options = buildFlotOptions(options);
|
||||||
|
zoomed_options['xaxis']['min'] = ranges.xaxis.from;
|
||||||
|
zoomed_options['xaxis']['max'] = ranges.xaxis.to;
|
||||||
|
zoomed_options['yaxis']['min'] = ranges.yaxis.from;
|
||||||
|
zoomed_options['yaxis']['max'] = ranges.yaxis.to;
|
||||||
|
plot = $.plot(div, all_targets, zoomed_options);
|
||||||
|
});
|
||||||
|
// add labels
|
||||||
|
var o;
|
||||||
|
/*for (var i = 0; i < events.length; i++) {
|
||||||
|
o = plot.pointOffset({ x: events[i].date * 1000, y: 0});
|
||||||
|
msg = '<div style="position:absolute;left:' + (o.left) + 'px;top:' + ( o.top + 35 ) + 'px;';
|
||||||
|
msg += 'color:' + options['events_text_color'] + ';font-size:smaller">';
|
||||||
|
msg += '<b>' + events[i].type + '</b></br>';
|
||||||
|
msg += events[i].desc
|
||||||
|
msg += '</div>';
|
||||||
|
$div.append(msg);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < es_events.length; i++) {
|
||||||
|
o = plot.pointOffset({ x: Date.parse(es_events[i]['_source']['@timestamp']), y: 0});
|
||||||
|
msg = '<div style="background-color:#40FF00;position:absolute;left:' + (o.left) + 'px;top:' + ( o.top + 35 ) + 'px;';
|
||||||
|
msg += 'color:' + '#FF0066' + ';font-size:smaller">';
|
||||||
|
msg += '<b>tags</b>: ' + es_events[i]['_source']['@tags'].join(' ') + '</br>';
|
||||||
|
msg += "<b>env</b>: " + es_events[i]['_source']['@fields']['environment'] + '</br>';
|
||||||
|
msg += "<b>msg</b>: " + es_events[i]['_source']['@message'] + '</br>';
|
||||||
|
msg += '</div>';
|
||||||
|
$div.append(msg);
|
||||||
|
}*/
|
||||||
|
if (options['line_stack_toggle']) {
|
||||||
|
var form = document.getElementById(options['line_stack_toggle']);
|
||||||
|
if(options['state'] == 'stacked') {
|
||||||
|
lines_checked = '';
|
||||||
|
stacked_checked = ' checked';
|
||||||
|
} else {
|
||||||
|
lines_checked = ' checked';
|
||||||
|
stacked_checked = '';
|
||||||
|
}
|
||||||
|
form.innerHTML= '<input type="radio" name="offset" id="lines" value="lines"'+ lines_checked +'>' +
|
||||||
|
'<label class="lines" for="lines">lines</label>' +
|
||||||
|
'<br/><input type="radio" name="offset" id="stacked" value="stacked"' + stacked_checked + '>' +
|
||||||
|
'<label class="stack" for="stack">stack</label>';
|
||||||
|
|
||||||
|
form.addEventListener('change', function(e) {
|
||||||
|
var mode = e.target.value;
|
||||||
|
options['state'] = mode;
|
||||||
|
$.plot(div, all_targets, buildFlotOptions(options));
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
function showTooltip(x, y, contents) {
|
||||||
|
$("<div id='tooltip_" + id + "'>" + contents + "</div>").css({
|
||||||
|
position: "absolute",
|
||||||
|
display: "none",
|
||||||
|
top: y + 5,
|
||||||
|
left: x + 5,
|
||||||
|
border: "1px solid #fdd",
|
||||||
|
padding: "2px",
|
||||||
|
"background-color": "#fee",
|
||||||
|
opacity: 0.80
|
||||||
|
}).appendTo("body").fadeIn(200);
|
||||||
|
}
|
||||||
|
var previousPoint = null;
|
||||||
|
$(div).bind("plothover", function (event, pos, item) {
|
||||||
|
if (item) {
|
||||||
|
if (previousPoint != item.dataIndex) {
|
||||||
|
previousPoint = item.dataIndex;
|
||||||
|
$("#tooltip_" + id).remove();
|
||||||
|
var x = item.datapoint[0],
|
||||||
|
y = item.datapoint[1].toFixed(2);
|
||||||
|
var date = new Date(x);
|
||||||
|
showTooltip(item.pageX, item.pageY,
|
||||||
|
"Series: " + item.series.label +
|
||||||
|
"<br/>Local Time: " + date.toLocaleString() +
|
||||||
|
"<br/>UTC Time: " + date.toUTCString() + ")" +
|
||||||
|
"<br/>Value: " + y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$("#tooltip_" + id).remove();
|
||||||
|
previousPoint = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
data = build_graphite_options(options, true);
|
||||||
|
var requests = [];
|
||||||
|
requests.push($.ajax({
|
||||||
|
accepts: {text: 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType: 'json',
|
||||||
|
url: options['graphite_url'],
|
||||||
|
type: "POST",
|
||||||
|
data: data.join('&'),
|
||||||
|
success: function(data, textStatus, jqXHR ) {
|
||||||
|
if(data.length == 0 ) {
|
||||||
|
console.warn("no data in graphite response");
|
||||||
|
}
|
||||||
|
add_targets(data);
|
||||||
|
},
|
||||||
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
|
on_error("Failed to do graphite POST request to " + truncate_str(options['graphite_url']) +
|
||||||
|
": " + textStatus + ": " + errorThrown);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if('anthracite_url' in options){
|
||||||
|
anthracite_url = build_anthracite_url(options, true);
|
||||||
|
requests.push($.ajax({
|
||||||
|
accepts: {text: 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType: 'json',
|
||||||
|
url: anthracite_url,
|
||||||
|
success: function(data, textStatus, jqXHR ) {
|
||||||
|
events = data.events;
|
||||||
|
},
|
||||||
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
|
on_error("Failed to do anthracite GET request to " + truncate_str(anthracite_url) +
|
||||||
|
": " + textStatus + ": " + errorThrown);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if('es_url' in options){
|
||||||
|
requests.push($.ajax({
|
||||||
|
accepts: {text: 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType: 'json',
|
||||||
|
jsonp: 'json',
|
||||||
|
url: options['es_url'],
|
||||||
|
success: function(data, textStatus, jqXHR ) { es_events = data.hits.hits },
|
||||||
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
|
on_error("Failed to do elasticsearch request to " + truncate_str(options['es_url']) +
|
||||||
|
": " + textStatus + ": " + errorThrown);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
$.when.apply($, requests).done(drawFlot);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphiteRick.render = function(div, options, on_error) {
|
||||||
|
$div = $(div);
|
||||||
|
$div.attr("height", options.height);
|
||||||
|
$div.attr("width", options.width);
|
||||||
|
var drawRick = function(resp_graphite) {
|
||||||
|
// note that resp_graphite.length can be != options.targets.length. let's call:
|
||||||
|
// * target_graphite a targetstring as returned by graphite
|
||||||
|
// * target_option a targetstring configuration
|
||||||
|
// if a target_option contains * graphite will return all matches separately unless you use something to aggregate like sumSeries()
|
||||||
|
// we must render all target_graphite's, but we must merge in the config from the corresponding target_option.
|
||||||
|
// example: for a target_graphite 'stats.foo.bar' we must find a target_option 'stats.foo.bar' *or*
|
||||||
|
// anything that causes graphite to match it, such as 'stats.*.bar' (this would be a bit cleaner if graphite's json
|
||||||
|
// would include also the originally specified target string)
|
||||||
|
// note that this code assumes each target_graphite can only be originating from one target_option,
|
||||||
|
// in some unlikely cases this is not correct (there might be overlap between different target_options with globs)
|
||||||
|
// but in that case I don't see why taking the settings of any of the possible originating target_options wouldn't be fine.
|
||||||
|
var all_targets = [];
|
||||||
|
if(resp_graphite.length == 0 ) {
|
||||||
|
console.warn("no data in graphite response");
|
||||||
|
}
|
||||||
|
for (var res_i = 0; res_i < resp_graphite.length; res_i++) {
|
||||||
|
var target = find_definition(resp_graphite[res_i], options);
|
||||||
|
target.data = [];
|
||||||
|
for (var i in resp_graphite[res_i].datapoints) {
|
||||||
|
target.data[i] = { x: resp_graphite[res_i].datapoints[i][1], y: resp_graphite[res_i].datapoints[i][0] || 0 };
|
||||||
|
}
|
||||||
|
all_targets.push(target);
|
||||||
|
}
|
||||||
|
options['element'] = div;
|
||||||
|
options['series'] = all_targets
|
||||||
|
for (i = 0; i < options['targets'].length; i++ ) {
|
||||||
|
options['targets'][i]['color'] = options['targets'][i]['color'];
|
||||||
|
}
|
||||||
|
var graph = new Rickshaw.Graph(options);
|
||||||
|
if(options['x_axis']) {
|
||||||
|
var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } );
|
||||||
|
}
|
||||||
|
if(options['y_axis']) {
|
||||||
|
var y_axis = new Rickshaw.Graph.Axis.Y( {
|
||||||
|
graph: graph,
|
||||||
|
orientation: 'left',
|
||||||
|
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
|
||||||
|
element: document.getElementById(options['y_axis']),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(options['hover_details']) {
|
||||||
|
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
|
||||||
|
graph: graph
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
var setRickshawOptions = function (options, graph) {
|
||||||
|
if ('state' in options && options['state'] == 'stacked') {
|
||||||
|
graph.setRenderer('stack');
|
||||||
|
graph.offset = 'zero';
|
||||||
|
}
|
||||||
|
else { // 'state' is lines
|
||||||
|
graph.setRenderer('line');
|
||||||
|
graph.offset = 'zero';
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
graph = setRickshawOptions(options, graph);
|
||||||
|
graph.render();
|
||||||
|
if (options['legend']) {
|
||||||
|
var legend = new Rickshaw.Graph.Legend({
|
||||||
|
graph: graph,
|
||||||
|
element: document.getElementById(options['legend'])
|
||||||
|
});
|
||||||
|
if(options['legend_toggle']) {
|
||||||
|
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
|
||||||
|
graph: graph,
|
||||||
|
legend: legend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(options['legend_reorder']) {
|
||||||
|
var order = new Rickshaw.Graph.Behavior.Series.Order({
|
||||||
|
graph: graph,
|
||||||
|
legend: legend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(options['legend_highlight']) {
|
||||||
|
var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({
|
||||||
|
graph: graph,
|
||||||
|
legend: legend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options['line_stack_toggle']) {
|
||||||
|
var form = document.getElementById(options['line_stack_toggle']);
|
||||||
|
if(!options['renderer'] || options['renderer'] == 'area') {
|
||||||
|
lines_checked = '';
|
||||||
|
stack_checked = ' checked';
|
||||||
|
} else {
|
||||||
|
lines_checked = ' checked';
|
||||||
|
stack_checked = '';
|
||||||
|
}
|
||||||
|
form.innerHTML= '<input type="radio" name="mode" id="lines" value="lines"'+ lines_checked +'>' +
|
||||||
|
'<label class="lines" for="lines">lines</label>' +
|
||||||
|
'<br/><input type="radio" name="mode" id="stacked" value="stacked"' + stack_checked + '>' +
|
||||||
|
'<label class="stack" for="stacked">stacked</label>';
|
||||||
|
|
||||||
|
form.addEventListener('change', function(e) {
|
||||||
|
options['state'] = e.target.value;
|
||||||
|
graph = setRickshawOptions(options, graph);
|
||||||
|
graph.render();
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = build_graphite_options(options, true);
|
||||||
|
$.ajax({
|
||||||
|
accepts: {text: 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType: 'json',
|
||||||
|
type: 'POST',
|
||||||
|
data: data.join('&'),
|
||||||
|
url: options['graphite_url'],
|
||||||
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
|
on_error("Failed to do graphite POST request to " + truncate_str(options['graphite_url']) +
|
||||||
|
": " + textStatus + ": " + errorThrown);
|
||||||
|
}
|
||||||
|
}).done(drawRick);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.graphiteHighcharts.render = function(div, options, on_error) {
|
||||||
|
var id = div.getAttribute('id');
|
||||||
|
$div = $(div);
|
||||||
|
$div.height(options.height);
|
||||||
|
$div.width(options.width);
|
||||||
|
var drawHighcharts = function(resp_graphite) {
|
||||||
|
var hsoptions = {
|
||||||
|
chart: {
|
||||||
|
renderTo: id,
|
||||||
|
type: 'area',
|
||||||
|
zoomType: 'xy',
|
||||||
|
backgroundColor: options.bgcolor,
|
||||||
|
animation: false
|
||||||
|
},
|
||||||
|
exporting: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
credits: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
borderWidth: 0,
|
||||||
|
useHTML: true,
|
||||||
|
itemHoverStyle: {
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: options.fgcolor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
line: {
|
||||||
|
lineWidth: 0.8,
|
||||||
|
marker: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spline: {
|
||||||
|
lineWidth: 0.8,
|
||||||
|
marker: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
stacking: 'normal',
|
||||||
|
marker: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
lineWidth: 0.8
|
||||||
|
},
|
||||||
|
areaspline: {
|
||||||
|
stacking: 'normal',
|
||||||
|
marker: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
lineWidth: 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: options.title,
|
||||||
|
style: {
|
||||||
|
color: options.fgcolor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
tickPixelInterval: 50,
|
||||||
|
labels: {
|
||||||
|
rotation: -45,
|
||||||
|
align: 'right'
|
||||||
|
},
|
||||||
|
lineColor: '#777',
|
||||||
|
tickColor: '#777',
|
||||||
|
maxPadding: 0.01,
|
||||||
|
minPadding: 0.01,
|
||||||
|
gridLineWidth: 0.2
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridLineColor: 'rgba(255, 255, 255, .3)',
|
||||||
|
minorGridLineColor: 'rgba(255,255,255,0.1)',
|
||||||
|
title: {
|
||||||
|
text: options.vtitle,
|
||||||
|
useHTML: true
|
||||||
|
},
|
||||||
|
maxPadding: 0.01,
|
||||||
|
minPadding: 0.01
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: options.hover_details,
|
||||||
|
crosshairs:[{width:1, color:'#ccc'},{width:1, color:'#ccc'}],
|
||||||
|
borderWidth: 0,
|
||||||
|
backgroundColor: {
|
||||||
|
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
|
||||||
|
stops: [
|
||||||
|
[0, 'rgba(96, 96, 96, .8)'],
|
||||||
|
[1, 'rgba(16, 16, 16, .8)']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
color: '#FFF'
|
||||||
|
},
|
||||||
|
useHTML: true,
|
||||||
|
formatter: function() {
|
||||||
|
return "Series: " + this.series.name +
|
||||||
|
"<br/>Local Time: " + Highcharts.dateFormat('%A %B %e %Y %H:%M', this.x) +
|
||||||
|
"<br/>Value: " + Highcharts.numberFormat(this.y, 2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
for (var res_i = 0; res_i < resp_graphite.length; res_i++) {
|
||||||
|
var target = find_definition(resp_graphite[res_i], options);
|
||||||
|
var hstarget = {
|
||||||
|
data: [],
|
||||||
|
events: {
|
||||||
|
click: function() {
|
||||||
|
var q;
|
||||||
|
if($.isArray(this.graphite_metric)) {
|
||||||
|
q = '^' + this.options.graphite_metric.join('$|^') + '$';
|
||||||
|
} else {
|
||||||
|
q = '^' + this.options.graphite_metric + '$';
|
||||||
|
}
|
||||||
|
window.location = "/inspect/" + q;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: "line",
|
||||||
|
animation: false
|
||||||
|
};
|
||||||
|
if (options.legend && options.legend.labelFormatter) {
|
||||||
|
hstarget.name = options.legend.labelFormatter(target.name);
|
||||||
|
}
|
||||||
|
hstarget.graphite_metric = target.graphite_metric;
|
||||||
|
if (options["series"] && options["series"].stack) {
|
||||||
|
hstarget.type = "area";
|
||||||
|
}
|
||||||
|
for (var i in resp_graphite[res_i].datapoints) {
|
||||||
|
hstarget.data.push([
|
||||||
|
resp_graphite[res_i].datapoints[i][1] * 1000,
|
||||||
|
resp_graphite[res_i].datapoints[i][0]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
hsoptions.series.push(hstarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hschart = new Highcharts.Chart(hsoptions);
|
||||||
|
if (options['line_stack_toggle'])
|
||||||
|
{
|
||||||
|
var form = document.getElementById(options['line_stack_toggle']);
|
||||||
|
var optionshtml = '';
|
||||||
|
|
||||||
|
if (options["series"] && options["series"].stack)
|
||||||
|
{
|
||||||
|
optionshtml += '<option value="stack">stack</option>';
|
||||||
|
optionshtml += '<option value="line">lines</option>';
|
||||||
|
} else {
|
||||||
|
optionshtml += '<option value="line">lines</option>';
|
||||||
|
optionshtml += '<option value="stack">stack</option>';
|
||||||
|
}
|
||||||
|
form.innerHTML = '<select>' + optionshtml + '</select>';
|
||||||
|
|
||||||
|
$("select", form).change(function() {
|
||||||
|
for (var i in hsoptions.series) {
|
||||||
|
var series = hsoptions.series[i];
|
||||||
|
series.stack = i;
|
||||||
|
if (this.value == "stack") {
|
||||||
|
series.type = "area";
|
||||||
|
series.stack = 1;
|
||||||
|
} else {
|
||||||
|
series.type = this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hschart = new Highcharts.Chart(hsoptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
data = build_graphite_options(options, true);
|
||||||
|
$.ajax({
|
||||||
|
accepts: {text: 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType: 'json',
|
||||||
|
url: options['graphite_url'],
|
||||||
|
type: "POST",
|
||||||
|
data: data.join('&'),
|
||||||
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
|
on_error("Failed to do graphite POST request to " + truncate_str(options['graphite_url']) +
|
||||||
|
": " + textStatus + ": " + errorThrown);
|
||||||
|
}
|
||||||
|
}).done(drawHighcharts);
|
||||||
|
};
|
||||||
|
// Default settings.
|
||||||
|
// Override with the options argument for per-case setup
|
||||||
|
// or set $.fn.graphite.defaults.<value> for global changes
|
||||||
|
$.fn.graphite.defaults = {
|
||||||
|
from: "-1hour",
|
||||||
|
height: "300",
|
||||||
|
until: "now",
|
||||||
|
graphite_url: "/render/",
|
||||||
|
width: "940"
|
||||||
|
};
|
||||||
|
|
||||||
|
}(jQuery));
|
Loading…
Reference in New Issue
Block a user