got flot graph showing!

This commit is contained in:
Torkel Ödegaard 2013-12-05 22:13:20 +01:00
parent 50e42c8bdd
commit bbcc1ef9ee
12 changed files with 5308 additions and 32 deletions

View File

@ -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,
}); });

View File

@ -50,7 +50,8 @@
"group": [ "group": [
"default" "default"
], ],
"type": "graph" "type": "graph",
"someprop": "hej from config"
} }
], ],
"notice": true "notice": true

View File

@ -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>

View File

@ -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();
}
});*/
}
};
});
}); });

View File

@ -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'
] ]
}); });
}); });

View 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');
}
*/

View 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

File diff suppressed because it is too large Load Diff

View 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);

View 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);

View 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);

View 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));