Browse Source

No crosshair #50 (#53)

* Install jquery and imports-loader for loading flot-plugins

* Remove jquery from externals and load flot-plugins using imports-loader

* Fix imports

* Add flot-plugins to vendor/flot
master
rozetko 6 years ago committed by GitHub
parent
commit
68a6069fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      build/webpack.base.conf.js
  2. 6
      package.json
  3. 2
      src/graph_legend.ts
  4. 20
      src/graph_renderer.ts
  5. 3
      src/graph_tooltip.ts
  6. 3
      src/threshold_manager.ts
  7. 178
      src/vendor/flot/jquery.flot.crosshair.js
  8. 238
      src/vendor/flot/jquery.flot.dashes.js
  9. 130
      src/vendor/flot/jquery.flot.events.js
  10. 289
      src/vendor/flot/jquery.flot.fillbelow.js
  11. 226
      src/vendor/flot/jquery.flot.fillbetween.js
  12. 3218
      src/vendor/flot/jquery.flot.js
  13. 379
      src/vendor/flot/jquery.flot.selection.js
  14. 190
      src/vendor/flot/jquery.flot.stack.js
  15. 127
      src/vendor/flot/jquery.flot.stackpercent.js
  16. 432
      src/vendor/flot/jquery.flot.time.js

22
build/webpack.base.conf.js

@ -11,13 +11,13 @@ module.exports = {
context: resolve('src'), context: resolve('src'),
entry: './module.ts', entry: './module.ts',
output: { output: {
filename: "module.js", filename: 'module.js',
path: resolve('dist'), path: resolve('dist'),
libraryTarget: "amd" libraryTarget: 'amd'
}, },
externals: [ externals: [
// remove the line below if you don't want to use buildin versions // remove the line below if you don't want to use buildin versions
'jquery', 'lodash', 'moment', 'angular', 'lodash', 'moment', 'angular',
function(context, request, callback) { function(context, request, callback) {
var prefix = 'grafana/'; var prefix = 'grafana/';
if (request.indexOf(prefix) === 0) { if (request.indexOf(prefix) === 0) {
@ -35,16 +35,28 @@ module.exports = {
]) ])
], ],
resolve: { resolve: {
extensions: [".ts", ".js"], extensions: ['.ts', '.js'],
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
loaders: [ loaders: [
"ts-loader" 'ts-loader'
], ],
exclude: /node_modules/, exclude: /node_modules/,
},
{
test: /jquery\.flot\.(?!events)/,
loaders: [
'imports-loader?jQuery=jquery'
]
},
{
test: /jquery\.flot\.events/,
loaders: [
'imports-loader?jQuery=jquery,lodash=lodash,angular=angular,tetherDrop=tether-drop'
]
} }
] ]
} }

6
package.json

@ -27,14 +27,16 @@
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"imports-loader": "^0.8.0",
"jest": "^23.4.1", "jest": "^23.4.1",
"jquery": "^3.3.1",
"loader-utils": "^1.1.0", "loader-utils": "^1.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"ts-jest": "^22.4.6",
"ts-loader": "^4.2.0", "ts-loader": "^4.2.0",
"typescript": "^2.8.3", "typescript": "^2.8.3",
"webpack": "^4.7.0", "webpack": "^4.7.0",
"webpack-cli": "^2.1.2", "webpack-cli": "^2.1.2"
"ts-jest": "^22.4.6"
}, },
"dependencies": { "dependencies": {
"perfect-scrollbar": "^1.3.0", "perfect-scrollbar": "^1.3.0",

2
src/graph_legend.ts

@ -1,6 +1,6 @@
import PerfectScrollbar from 'perfect-scrollbar'; import PerfectScrollbar from 'perfect-scrollbar';
import * as $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import 'jquery';
export class GraphLegend { export class GraphLegend {

20
src/graph_renderer.ts

@ -12,16 +12,15 @@ import {
import { GraphCtrl } from './module'; import { GraphCtrl } from './module';
import 'grafana/vendor/flot/jquery.flot.js'; import 'jquery';
import 'grafana/vendor/flot/jquery.flot.time.js'; import './vendor/flot/jquery.flot.js';
import 'grafana/vendor/flot/jquery.flot.selection.js'; import './vendor/flot/jquery.flot.selection.js';
import 'grafana/vendor/flot/jquery.flot.stack.js'; import './vendor/flot/jquery.flot.time.js';
import 'grafana/vendor/flot/jquery.flot.stackpercent.js'; import './vendor/flot/jquery.flot.stack.js';
import 'grafana/vendor/flot/jquery.flot.fillbelow.js'; import './vendor/flot/jquery.flot.stackpercent.js';
import 'grafana/vendor/flot/jquery.flot.crosshair.js'; import './vendor/flot/jquery.flot.fillbelow.js';
import 'grafana/vendor/flot/jquery.flot.dashes.js'; import './vendor/flot/jquery.flot.crosshair.js';
import 'grafana/vendor/flot/jquery.flot.gauge.js'; import './vendor/flot/jquery.flot.dashes.js';
import 'grafana/vendor/flot/jquery.flot.pie.js';
import './vendor/flot/jquery.flot.events.js'; import './vendor/flot/jquery.flot.events.js';
// import { EventManager } from './vendor/grafana/event_manager'; // import { EventManager } from './vendor/grafana/event_manager';
@ -30,7 +29,6 @@ import { tickStep } from './vendor/grafana/ticks';
import { appEvents } from 'grafana/app/core/core'; import { appEvents } from 'grafana/app/core/core';
import kbn from 'grafana/app/core/utils/kbn'; import kbn from 'grafana/app/core/utils/kbn';
import * as $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';

3
src/graph_tooltip.ts

@ -1,4 +1,5 @@
import { AnalyticSegmentsSearcher } from "models/analytic_unit"; import { AnalyticSegmentsSearcher } from 'models/analytic_unit';
export class GraphTooltip { export class GraphTooltip {

3
src/threshold_manager.ts

@ -1,5 +1,4 @@
import 'grafana/vendor/flot/jquery.flot.js'; import 'jquery';
import * as $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
export class ThresholdManager { export class ThresholdManager {

178
src/vendor/flot/jquery.flot.crosshair.js vendored

@ -0,0 +1,178 @@
/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin supports these options:
crosshair: {
mode: null or "x" or "y" or "xy"
color: color
lineWidth: number
}
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
crosshair that lets you trace the values on the x axis, "y" enables a
horizontal crosshair and "xy" enables them both. "color" is the color of the
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
the drawn lines (default is 1).
The plugin also adds four public methods:
- setCrosshair( pos )
Set the position of the crosshair. Note that this is cleared if the user
moves the mouse. "pos" is in coordinates of the plot and should be on the
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
axes), which is coincidentally the same format as what you get from a
"plothover" event. If "pos" is null, the crosshair is cleared.
- clearCrosshair()
Clear the crosshair.
- lockCrosshair(pos)
Cause the crosshair to lock to the current location, no longer updating if
the user moves the mouse. Optionally supply a position (passed on to
setCrosshair()) to move it to.
Example usage:
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
$("#graph").bind( "plothover", function ( evt, position, item ) {
if ( item ) {
// Lock the crosshair to the data point being hovered
myFlot.lockCrosshair({
x: item.datapoint[ 0 ],
y: item.datapoint[ 1 ]
});
} else {
// Return normal crosshair operation
myFlot.unlockCrosshair();
}
});
- unlockCrosshair()
Free the crosshair to move again after locking it.
*/
(
// the actual Flot code
function ($) {
var options = {
crosshair: {
mode: null, // one of null, "x", "y" or "xy",
color: "rgba(170, 0, 0, 0.80)",
lineWidth: 1
}
};
function init(plot) {
// position of crosshair in pixels
var crosshair = { x: -1, y: -1, locked: false };
plot.setCrosshair = function setCrosshair(pos) {
if (!pos)
crosshair.x = -1;
else {
var o = plot.p2c(pos);
crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
}
plot.triggerRedrawOverlay();
};
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
plot.lockCrosshair = function lockCrosshair(pos) {
if (pos)
plot.setCrosshair(pos);
crosshair.locked = true;
};
plot.unlockCrosshair = function unlockCrosshair() {
crosshair.locked = false;
};
function onMouseOut(e) {
if (crosshair.locked)
return;
if (crosshair.x != -1) {
crosshair.x = -1;
plot.triggerRedrawOverlay();
}
}
function onMouseMove(e) {
if (crosshair.locked)
return;
if (plot.getSelection && plot.getSelection()) {
crosshair.x = -1; // hide the crosshair while selecting
return;
}
var offset = plot.offset();
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
plot.triggerRedrawOverlay();
}
plot.hooks.bindEvents.push(function (plot, eventHolder) {
if (!plot.getOptions().crosshair.mode)
return;
eventHolder.mouseout(onMouseOut);
eventHolder.mousemove(onMouseMove);
});
plot.hooks.drawOverlay.push(function (plot, ctx) {
var c = plot.getOptions().crosshair;
if (!c.mode)
return;
var plotOffset = plot.getPlotOffset();
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
if (crosshair.x != -1) {
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
ctx.strokeStyle = c.color;
ctx.lineWidth = c.lineWidth;
ctx.lineJoin = "round";
ctx.beginPath();
if (c.mode.indexOf("x") != -1) {
var drawX = Math.floor(crosshair.x) + adj;
ctx.moveTo(drawX, 0);
ctx.lineTo(drawX, plot.height());
}
if (c.mode.indexOf("y") != -1) {
var drawY = Math.floor(crosshair.y) + adj;
ctx.moveTo(0, drawY);
ctx.lineTo(plot.width(), drawY);
}
ctx.stroke();
}
ctx.restore();
});
plot.hooks.shutdown.push(function (plot, eventHolder) {
eventHolder.unbind("mouseout", onMouseOut);
eventHolder.unbind("mousemove", onMouseMove);
});
}
$.plot.plugins.push({
init: init,
options: options,
name: 'crosshair',
version: '1.0'
});
})(jQuery);

238
src/vendor/flot/jquery.flot.dashes.js vendored

@ -0,0 +1,238 @@
/*
* jQuery.flot.dashes
*
* options = {
* series: {
* dashes: {
*
* // show
* // default: false
* // Whether to show dashes for the series.
* show: <boolean>,
*
* // lineWidth
* // default: 2
* // The width of the dashed line in pixels.
* lineWidth: <number>,
*
* // dashLength
* // default: 10
* // Controls the length of the individual dashes and the amount of
* // space between them.
* // If this is a number, the dashes and spaces will have that length.
* // If this is an array, it is read as [ dashLength, spaceLength ]
* dashLength: <number> or <array[2]>
* }
* }
* }
*/
(
// the actual Flot code
function($){
function init(plot) {
plot.hooks.processDatapoints.push(function(plot, series, datapoints) {
if (!series.dashes.show) return;
plot.hooks.draw.push(function(plot, ctx) {
var plotOffset = plot.getPlotOffset(),
axisx = series.xaxis,
axisy = series.yaxis;
function plotDashes(xoffset, yoffset) {
var points = datapoints.points,
ps = datapoints.pointsize,
prevx = null,
prevy = null,
dashRemainder = 0,
dashOn = true,
dashOnLength,
dashOffLength;
if (series.dashes.dashLength[0]) {
dashOnLength = series.dashes.dashLength[0];
if (series.dashes.dashLength[1]) {
dashOffLength = series.dashes.dashLength[1];
} else {
dashOffLength = dashOnLength;
}
} else {
dashOffLength = dashOnLength = series.dashes.dashLength;
}
ctx.beginPath();
for (var i = ps; i < points.length; i += ps) {
var x1 = points[i - ps],
y1 = points[i - ps + 1],
x2 = points[i],
y2 = points[i + 1];
if (x1 == null || x2 == null) continue;
// clip with ymin
if (y1 <= y2 && y1 < axisy.min) {
if (y2 < axisy.min) continue; // line segment is outside
// compute new intersection point
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = axisy.min;
} else if (y2 <= y1 && y2 < axisy.min) {
if (y1 < axisy.min) continue;
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = axisy.min;
}
// clip with ymax
if (y1 >= y2 && y1 > axisy.max) {
if (y2 > axisy.max) continue;
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = axisy.max;
} else if (y2 >= y1 && y2 > axisy.max) {
if (y1 > axisy.max) continue;
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = axisy.max;
}
// clip with xmin
if (x1 <= x2 && x1 < axisx.min) {
if (x2 < axisx.min) continue;
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = axisx.min;
} else if (x2 <= x1 && x2 < axisx.min) {
if (x1 < axisx.min) continue;
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = axisx.min;
}
// clip with xmax
if (x1 >= x2 && x1 > axisx.max) {
if (x2 > axisx.max) continue;
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = axisx.max;
} else if (x2 >= x1 && x2 > axisx.max) {
if (x1 > axisx.max) continue;
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = axisx.max;
}
if (x1 != prevx || y1 != prevy) {
ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
}
var ax1 = axisx.p2c(x1) + xoffset,
ay1 = axisy.p2c(y1) + yoffset,
ax2 = axisx.p2c(x2) + xoffset,
ay2 = axisy.p2c(y2) + yoffset,
dashOffset;
function lineSegmentOffset(segmentLength) {
var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
if (c <= segmentLength) {
return {
deltaX: ax2 - ax1,
deltaY: ay2 - ay1,
distance: c,
remainder: segmentLength - c
}
} else {
var xsign = ax2 > ax1 ? 1 : -1,
ysign = ay2 > ay1 ? 1 : -1;
return {
deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
distance: segmentLength,
remainder: 0
};
}
}
//-end lineSegmentOffset
do {
dashOffset = lineSegmentOffset(
dashRemainder > 0 ? dashRemainder :
dashOn ? dashOnLength : dashOffLength);
if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
if (dashOn) {
ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
} else {
ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
}
}
dashOn = !dashOn;
dashRemainder = dashOffset.remainder;
ax1 += dashOffset.deltaX;
ay1 += dashOffset.deltaY;
} while (dashOffset.distance > 0);
prevx = x2;
prevy = y2;
}
ctx.stroke();
}
//-end plotDashes
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineJoin = 'round';
var lw = series.dashes.lineWidth,
sw = series.shadowSize;
// FIXME: consider another form of shadow when filling is turned on
if (lw > 0 && sw > 0) {
// draw shadow as a thick and thin line with transparency
ctx.lineWidth = sw;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
// position shadow at angle from the mid of line
var angle = Math.PI/18;
plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));
ctx.lineWidth = sw/2;
plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));
}
ctx.lineWidth = lw;
ctx.strokeStyle = series.color;
if (lw > 0) {
plotDashes(0, 0);
}
ctx.restore();
});
//-end draw hook
});
//-end processDatapoints hook
}
//-end init
$.plot.plugins.push({
init: init,
options: {
series: {
dashes: {
show: false,
lineWidth: 2,
dashLength: 10
}
}
},
name: 'dashes',
version: '0.1'
});
})(jQuery);

130
src/vendor/flot/jquery.flot.events.js vendored

@ -1,10 +1,4 @@
define([ (function ($, _, angular, Drop) {
'jquery',
'lodash',
'angular',
'tether-drop',
],
function ($, _, angular, Drop) {
'use strict'; 'use strict';
function createAnnotationToolip(element, event, plot) { function createAnnotationToolip(element, event, plot) {
@ -12,11 +6,11 @@ function ($, _, angular, Drop) {
var content = document.createElement('div'); var content = document.createElement('div');
content.innerHTML = '<annotation-tooltip event="event" on-edit="onEdit()"></annotation-tooltip>'; content.innerHTML = '<annotation-tooltip event="event" on-edit="onEdit()"></annotation-tooltip>';
injector.invoke(["$compile", "$rootScope", function($compile, $rootScope) { injector.invoke(["$compile", "$rootScope", function ($compile, $rootScope) {
var eventManager = plot.getOptions().events.manager; var eventManager = plot.getOptions().events.manager;
var tmpScope = $rootScope.$new(true); var tmpScope = $rootScope.$new(true);
tmpScope.event = event; tmpScope.event = event;
tmpScope.onEdit = function() { tmpScope.onEdit = function () {
eventManager.editEvent(event); eventManager.editEvent(event);
}; };
@ -32,14 +26,14 @@ function ($, _, angular, Drop) {
openOn: 'hover', openOn: 'hover',
hoverCloseDelay: 200, hoverCloseDelay: 200,
tetherOptions: { tetherOptions: {
constraints: [{to: 'window', pin: true, attachment: "both"}] constraints: [{ to: 'window', pin: true, attachment: "both" }]
} }
}); });
drop.open(); drop.open();
drop.on('close', function() { drop.on('close', function () {
setTimeout(function() { setTimeout(function () {
drop.destroy(); drop.destroy();
}); });
}); });
@ -52,30 +46,30 @@ function ($, _, angular, Drop) {
var eventManager = plot.getOptions().events.manager; var eventManager = plot.getOptions().events.manager;
if (eventManager.editorOpen) { if (eventManager.editorOpen) {
// update marker element to attach to (needed in case of legend on the right // update marker element to attach to (needed in case of legend on the right
// when there is a double render pass and the inital marker element is removed) // when there is a double render pass and the initial marker element is removed)
markerElementToAttachTo = element; markerElementToAttachTo = element;
return; return;
} }
// mark as openend // mark as openend
eventManager.editorOpened(); eventManager.editorOpened();
// set marker elment to attache to // set marker element to attache to
markerElementToAttachTo = element; markerElementToAttachTo = element;
// wait for element to be attached and positioned // wait for element to be attached and positioned
setTimeout(function() { setTimeout(function () {
var injector = angular.element(document).injector(); var injector = angular.element(document).injector();
var content = document.createElement('div'); var content = document.createElement('div');
content.innerHTML = '<event-editor panel-ctrl="panelCtrl" event="event" close="close()"></event-editor>'; content.innerHTML = '<event-editor panel-ctrl="panelCtrl" event="event" close="close()"></event-editor>';
injector.invoke(["$compile", "$rootScope", function($compile, $rootScope) { injector.invoke(["$compile", "$rootScope", function ($compile, $rootScope) {
var scope = $rootScope.$new(true); var scope = $rootScope.$new(true);
var drop; var drop;
scope.event = event; scope.event = event;
scope.panelCtrl = eventManager.panelCtrl; scope.panelCtrl = eventManager.panelCtrl;
scope.close = function() { scope.close = function () {
drop.close(); drop.close();
}; };
@ -89,16 +83,16 @@ function ($, _, angular, Drop) {
classes: 'drop-popover drop-popover--form', classes: 'drop-popover drop-popover--form',
openOn: 'click', openOn: 'click',
tetherOptions: { tetherOptions: {
constraints: [{to: 'window', pin: true, attachment: "both"}] constraints: [{ to: 'window', pin: true, attachment: "both" }]
} }
}); });
drop.open(); drop.open();
eventManager.editorOpened(); eventManager.editorOpened();
drop.on('close', function() { drop.on('close', function () {
// need timeout here in order call drop.destroy // need timeout here in order call drop.destroy
setTimeout(function() { setTimeout(function () {
eventManager.editorClosed(); eventManager.editorClosed();
scope.$destroy(); scope.$destroy();
drop.destroy(); drop.destroy();
@ -127,7 +121,7 @@ function ($, _, angular, Drop) {
/** /**
* A class that allows for the drawing an remove of some object * A class that allows for the drawing an remove of some object
*/ */
var DrawableEvent = function(object, drawFunc, clearFunc, moveFunc, left, top, width, height) { var DrawableEvent = function (object, drawFunc, clearFunc, moveFunc, left, top, width, height) {
var _object = object; var _object = object;
var _drawFunc = drawFunc; var _drawFunc = drawFunc;
var _clearFunc = clearFunc; var _clearFunc = clearFunc;
@ -136,13 +130,13 @@ function ($, _, angular, Drop) {
var _width = width; var _width = width;
var _height = height; var _height = height;
this.width = function() { return _width; }; this.width = function () { return _width; };
this.height = function() { return _height; }; this.height = function () { return _height; };
this.position = function() { return _position; }; this.position = function () { return _position; };
this.draw = function() { _drawFunc(_object); }; this.draw = function () { _drawFunc(_object); };
this.clear = function() { _clearFunc(_object); }; this.clear = function () { _clearFunc(_object); };
this.getObject = function() { return _object; }; this.getObject = function () { return _object; };
this.moveTo = function(position) { this.moveTo = function (position) {
_position = position; _position = position;
_moveFunc(_object, _position); _moveFunc(_object, _position);
}; };
@ -151,48 +145,48 @@ function ($, _, angular, Drop) {
/** /**
* Event class that stores options (eventType, min, max, title, description) and the object to draw. * Event class that stores options (eventType, min, max, title, description) and the object to draw.
*/ */
var VisualEvent = function(options, drawableEvent) { var VisualEvent = function (options, drawableEvent) {
var _parent; var _parent;
var _options = options; var _options = options;
var _drawableEvent = drawableEvent; var _drawableEvent = drawableEvent;
var _hidden = false; var _hidden = false;
this.visual = function() { return _drawableEvent; }; this.visual = function () { return _drawableEvent; };
this.getOptions = function() { return _options; }; this.getOptions = function () { return _options; };
this.getParent = function() { return _parent; }; this.getParent = function () { return _parent; };
this.isHidden = function() { return _hidden; }; this.isHidden = function () { return _hidden; };
this.hide = function() { _hidden = true; }; this.hide = function () { _hidden = true; };
this.unhide = function() { _hidden = false; }; this.unhide = function () { _hidden = false; };
}; };
/** /**
* A Class that handles the event-markers inside the given plot * A Class that handles the event-markers inside the given plot
*/ */
var EventMarkers = function(plot) { var EventMarkers = function (plot) {
var _events = []; var _events = [];
this._types = []; this._types = [];
this._plot = plot; this._plot = plot;
this.eventsEnabled = false; this.eventsEnabled = false;
this.getEvents = function() { this.getEvents = function () {
return _events; return _events;
}; };
this.setTypes = function(types) { this.setTypes = function (types) {
return this._types = types; return this._types = types;
}; };
/** /**
* create internal objects for the given events * create internal objects for the given events
*/ */
this.setupEvents = function(events) { this.setupEvents = function (events) {
var that = this; var that = this;
var parts = _.partition(events, 'isRegion'); var parts = _.partition(events, 'isRegion');
var regions = parts[0]; var regions = parts[0];
events = parts[1]; events = parts[1];
$.each(events, function(index, event) { $.each(events, function (index, event) {
var ve = new VisualEvent(event, that._buildDiv(event)); var ve = new VisualEvent(event, that._buildDiv(event));
_events.push(ve); _events.push(ve);
}); });
@ -202,7 +196,7 @@ function ($, _, angular, Drop) {
_events.push(vre); _events.push(vre);
}); });
_events.sort(function(a, b) { _events.sort(function (a, b) {
var ao = a.getOptions(), bo = b.getOptions(); var ao = a.getOptions(), bo = b.getOptions();
if (ao.min > bo.min) { return 1; } if (ao.min > bo.min) { return 1; }
if (ao.min < bo.min) { return -1; } if (ao.min < bo.min) { return -1; }
@ -213,11 +207,11 @@ function ($, _, angular, Drop) {
/** /**
* draw the events to the plot * draw the events to the plot
*/ */
this.drawEvents = function() { this.drawEvents = function () {
var that = this; var that = this;
// var o = this._plot.getPlotOffset(); // var o = this._plot.getPlotOffset();
$.each(_events, function(index, event) { $.each(_events, function (index, event) {
// check event is inside the graph range // check event is inside the graph range
if (that._insidePlot(event.getOptions().min) && !event.isHidden()) { if (that._insidePlot(event.getOptions().min) && !event.isHidden()) {
event.visual().draw(); event.visual().draw();
@ -230,12 +224,12 @@ function ($, _, angular, Drop) {
/** /**
* update the position of the event-markers (e.g. after scrolling or zooming) * update the position of the event-markers (e.g. after scrolling or zooming)
*/ */
this.updateEvents = function() { this.updateEvents = function () {
var that = this; var that = this;
var o = this._plot.getPlotOffset(), left, top; var o = this._plot.getPlotOffset(), left, top;
var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1]; var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];
$.each(_events, function(index, event) { $.each(_events, function (index, event) {
top = o.top + that._plot.height() - event.visual().height(); top = o.top + that._plot.height() - event.visual().height();
left = xaxis.p2c(event.getOptions().min) + o.left - event.visual().width() / 2; left = xaxis.p2c(event.getOptions().min) + o.left - event.visual().width() / 2;
event.visual().moveTo({ top: top, left: left }); event.visual().moveTo({ top: top, left: left });
@ -245,8 +239,8 @@ function ($, _, angular, Drop) {
/** /**
* remove all events from the plot * remove all events from the plot
*/ */
this._clearEvents = function() { this._clearEvents = function () {
$.each(_events, function(index, val) { $.each(_events, function (index, val) {
val.visual().clear(); val.visual().clear();
}); });
_events = []; _events = [];
@ -255,7 +249,7 @@ function ($, _, angular, Drop) {
/** /**
* create a DOM element for the given event * create a DOM element for the given event
*/ */
this._buildDiv = function(event) { this._buildDiv = function (event) {
var that = this; var that = this;
var container = this._plot.getPlaceholder(); var container = this._plot.getPlaceholder();
@ -336,22 +330,22 @@ function ($, _, angular, Drop) {
"line-height": 0, "line-height": 0,
"width": 0, "width": 0,
"height": 0, "height": 0,
"border-left": markerSize+"px solid transparent", "border-left": markerSize + "px solid transparent",
"border-right": markerSize+"px solid transparent" "border-right": markerSize + "px solid transparent"
}); });
marker.appendTo(line); marker.appendTo(line);
if (this._types[eventTypeId] && this._types[eventTypeId].position && this._types[eventTypeId].position.toUpperCase() === 'BOTTOM') { if (this._types[eventTypeId] && this._types[eventTypeId].position && this._types[eventTypeId].position.toUpperCase() === 'BOTTOM') {
marker.css({ marker.css({
"top": top-markerSize-8 +"px", "top": top - markerSize - 8 + "px",
"border-top": "none", "border-top": "none",
"border-bottom": markerSize+"px solid " + color "border-bottom": markerSize + "px solid " + color
}); });
} else { } else {
marker.css({ marker.css({
"top": "0px", "top": "0px",
"border-top": markerSize+"px solid " + color, "border-top": markerSize + "px solid " + color,
"border-bottom": "none" "border-bottom": "none"
}); });
} }
@ -360,7 +354,7 @@ function ($, _, angular, Drop) {
"event": event "event": event
}); });
var mouseenter = function() { var mouseenter = function () {
createAnnotationToolip(marker, $(this).data("event"), that._plot); createAnnotationToolip(marker, $(this).data("event"), that._plot);
}; };
@ -368,7 +362,7 @@ function ($, _, angular, Drop) {
createEditPopover(marker, event.editModel, that._plot); createEditPopover(marker, event.editModel, that._plot);
} }
var mouseleave = function() { var mouseleave = function () {
that._plot.clearSelection(); that._plot.clearSelection();
}; };
@ -381,8 +375,8 @@ function ($, _, angular, Drop) {
var drawableEvent = new DrawableEvent( var drawableEvent = new DrawableEvent(
line, line,
function drawFunc(obj) { obj.show(); }, function drawFunc(obj) { obj.show(); },
function(obj) { obj.remove(); }, function (obj) { obj.remove(); },
function(obj, position) { function (obj, position) {
obj.css({ obj.css({
top: position.top, top: position.top,
left: position.left left: position.left
@ -449,7 +443,7 @@ function ($, _, angular, Drop) {
var right = xaxis.p2c(timeTo) + o.left; var right = xaxis.p2c(timeTo) + o.left;
regionWidth = right - left; regionWidth = right - left;
_.each([left, right], function(position) { _.each([left, right], function (position) {
var line = $('<div class="events_line flot-temp-elem"></div>').css({ var line = $('<div class="events_line flot-temp-elem"></div>').css({
"position": "absolute", "position": "absolute",
"opacity": 0.8, "opacity": 0.8,
@ -521,7 +515,7 @@ function ($, _, angular, Drop) {
/** /**
* check if the event is inside visible range * check if the event is inside visible range
*/ */
this._insidePlot = function(x) { this._insidePlot = function (x) {
var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1]; var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];
var xc = xaxis.p2c(x); var xc = xaxis.p2c(x);
return xc > 0 && xc < xaxis.p2c(xaxis.max); return xc > 0 && xc < xaxis.p2c(xaxis.max);
@ -536,19 +530,19 @@ function ($, _, angular, Drop) {
var that = this; var that = this;
var eventMarkers = new EventMarkers(plot); var eventMarkers = new EventMarkers(plot);
plot.getEvents = function() { plot.getEvents = function () {
return eventMarkers._events; return eventMarkers._events;
}; };
plot.hideEvents = function() { plot.hideEvents = function () {
$.each(eventMarkers._events, function(index, event) { $.each(eventMarkers._events, function (index, event) {
event.visual().getObject().hide(); event.visual().getObject().hide();
}); });
}; };
plot.showEvents = function() { plot.showEvents = function () {
plot.hideEvents(); plot.hideEvents();
$.each(eventMarkers._events, function(index, event) { $.each(eventMarkers._events, function (index, event) {
event.hide(); event.hide();
}); });
@ -556,20 +550,20 @@ function ($, _, angular, Drop) {
}; };
// change events on an existing plot // change events on an existing plot
plot.setEvents = function(events) { plot.setEvents = function (events) {
if (eventMarkers.eventsEnabled) { if (eventMarkers.eventsEnabled) {
eventMarkers.setupEvents(events); eventMarkers.setupEvents(events);
} }
}; };
plot.hooks.processOptions.push(function(plot, options) { plot.hooks.processOptions.push(function (plot, options) {
// enable the plugin // enable the plugin
if (options.events.data != null) { if (options.events.data != null) {
eventMarkers.eventsEnabled = true; eventMarkers.eventsEnabled = true;
} }
}); });
plot.hooks.draw.push(function(plot) { plot.hooks.draw.push(function (plot) {
var options = plot.getOptions(); var options = plot.getOptions();
if (eventMarkers.eventsEnabled) { if (eventMarkers.eventsEnabled) {
@ -601,4 +595,4 @@ function ($, _, angular, Drop) {
name: "events", name: "events",
version: "0.2.5" version: "0.2.5"
}); });
}); })(jQuery, lodash, angular, tetherDrop);

289
src/vendor/flot/jquery.flot.fillbelow.js vendored

@ -0,0 +1,289 @@
(
function($) {
"use strict";
var options = {
series: {
fillBelowTo: null
}
};
function init(plot) {
function findBelowSeries( series, allseries ) {
var i;
for ( i = 0; i < allseries.length; ++i ) {
if ( allseries[ i ].id === series.fillBelowTo ) {
return allseries[ i ];
}
}
return null;
}
/* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */
/* this is a vector cross product operation */
function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) {
var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y,
s, t;
top_delta_x = top_right_x - top_left_x;
top_delta_y = top_right_y - top_left_y;
bottom_delta_x = bottom_right_x - bottom_left_x;
bottom_delta_y = bottom_right_y - bottom_left_y;
s = (
(-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y))
) / (
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
);
t = (
(bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x))
) / (
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
);
// Collision detected
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
return [
top_left_x + (t * top_delta_x), // X
top_left_y + (t * top_delta_y) // Y
];
}
// No collision
return null;
}
function plotDifferenceArea(plot, ctx, series) {
if ( series.fillBelowTo === null ) {
return;
}
var otherseries,
ps,
points,
otherps,
otherpoints,
plotOffset,
fillStyle;
function openPolygon(x, y) {
ctx.beginPath();
ctx.moveTo(
series.xaxis.p2c(x) + plotOffset.left,
series.yaxis.p2c(y) + plotOffset.top
);
}
function closePolygon() {
ctx.closePath();
ctx.fill();
}
function validateInput() {
if (points.length/ps !== otherpoints.length/otherps) {
console.error("Refusing to graph inconsistent number of points");
return false;
}
var i;
for (i = 0; i < (points.length / ps); i++) {
if (
points[i * ps] !== null &&
otherpoints[i * otherps] !== null &&
points[i * ps] !== otherpoints[i * otherps]
) {
console.error("Refusing to graph points without matching value");
return false;
}
}
return true;
}
function findNextStart(start_i, end_i) {
console.assert(end_i > start_i, "expects the end index to be greater than the start index");
var start = (
start_i === 0 ||
points[start_i - 1] === null ||
otherpoints[start_i - 1] === null
),
equal = false,
i,
intersect;
for (i = start_i; i < end_i; i++) {
// Take note of null points
if (
points[(i * ps) + 1] === null ||
otherpoints[(i * ps) + 1] === null
) {
equal = false;
start = true;
}
// Take note of equal points
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
equal = true;
start = false;
}
else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) {
// If we begin above the desired point
if (start) {
openPolygon(points[i * ps], points[(i * ps) + 1]);
}
// If an equal point preceeds this, start the polygon at that equal point
else if (equal) {
openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]);
}
// Otherwise, find the intersection point, and start it there
else {
intersect = intersectionPoint(i);
openPolygon(intersect[0], intersect[1]);
}
topTraversal(i, end_i);
return;
}
// If we go below equal, equal at any preceeding point is irrelevant
else {
start = false;
equal = false;
}
}
}
function intersectionPoint(right_i) {
console.assert(right_i > 0, "expects the second point in the series line segment");
var i, intersect;
for (i = 1; i < (otherpoints.length/otherps); i++) {
intersect = segmentIntersection(
points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1],
points[right_i * ps], points[(right_i * ps) + 1],
otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1],
otherpoints[i * otherps], otherpoints[(i * otherps) + 1]
);
if (intersect !== null) {
return intersect;
}
}
console.error("intersectionPoint() should only be called when an intersection happens");
}
function bottomTraversal(start_i, end_i) {
console.assert(start_i >= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
var i;
for (i = start_i; i >= end_i; i--) {
ctx.lineTo(
otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left,
otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top
);
}
closePolygon();
}
function topTraversal(start_i, end_i) {
console.assert(start_i <= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
var i,
intersect;
for (i = start_i; i < end_i; i++) {
if (points[(i * ps) + 1] === null && i > start_i) {
bottomTraversal(i - 1, start_i);
findNextStart(i, end_i);
return;
}
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
bottomTraversal(i, start_i);
findNextStart(i, end_i);
return;
}
else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) {
intersect = intersectionPoint(i);
ctx.lineTo(
series.xaxis.p2c(intersect[0]) + plotOffset.left,
series.yaxis.p2c(intersect[1]) + plotOffset.top
);
bottomTraversal(i, start_i);
findNextStart(i, end_i);
return;
}
else {
ctx.lineTo(
series.xaxis.p2c(points[i * ps]) + plotOffset.left,
series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top
);
}
}
bottomTraversal(end_i, start_i);
}
// Begin processing
otherseries = findBelowSeries( series, plot.getData() );
if ( !otherseries ) {
return;
}
ps = series.datapoints.pointsize;
points = series.datapoints.points;
otherps = otherseries.datapoints.pointsize;
otherpoints = otherseries.datapoints.points;
plotOffset = plot.getPlotOffset();
if (!validateInput()) {
return;
}
// Flot's getFillStyle() should probably be exposed somewhere
fillStyle = $.color.parse(series.color);
fillStyle.a = 0.4;
fillStyle.normalize();
ctx.fillStyle = fillStyle.toString();
// Begin recursive bi-directional traversal
findNextStart(0, points.length/ps);
}
plot.hooks.drawSeries.push(plotDifferenceArea);
}
$.plot.plugins.push({
init: init,
options: options,
name: "fillbelow",
version: "0.1.0"
});
})(jQuery);

226
src/vendor/flot/jquery.flot.fillbetween.js vendored

@ -0,0 +1,226 @@
/* Flot plugin for computing bottoms for filled line and bar charts.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The case: you've got two series that you want to fill the area between. In Flot
terms, you need to use one as the fill bottom of the other. You can specify the
bottom of each data point as the third coordinate manually, or you can use this
plugin to compute it for you.
In order to name the other series, you need to give it an id, like this:
var dataset = [
{ data: [ ... ], id: "foo" } , // use default bottom
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
];
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
As a convenience, if the id given is a number that doesn't appear as an id in
the series, it is interpreted as the index in the array instead (so fillBetween:
0 can also mean the first series).
Internally, the plugin modifies the datapoints in each series. For line series,
extra data points might be inserted through interpolation. Note that at points
where the bottom line is not defined (due to a null point or start/end of line),
the current line will show a gap too. The algorithm comes from the
jquery.flot.stack.js plugin, possibly some code could be shared.
*/
(
function ( $ ) {
var options = {
series: {
fillBetween: null // or number
}
};
function init( plot ) {
function findBottomSeries( s, allseries ) {
var i;
for ( i = 0; i < allseries.length; ++i ) {
if ( allseries[ i ].id === s.fillBetween ) {
return allseries[ i ];
}
}
if ( typeof s.fillBetween === "number" ) {
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
return null;
}
return allseries[ s.fillBetween ];
}
return null;
}
function computeFillBottoms( plot, s, datapoints ) {
if ( s.fillBetween == null ) {
return;
}
var other = findBottomSeries( 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,
withbottom = ps > 2 && datapoints.format[2].y,
withsteps = withlines && s.lines.steps,
fromgap = true,
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 ];
py = points[ i + 1 ];
qx = otherpoints[ j ];
qy = otherpoints[ j + 1 ];
bottom = 0;
if ( px === qx ) {
for ( m = 0; m < ps; ++m ) {
newpoints.push( points[ i + m ] );
}
//newpoints[ l + 1 ] += 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 + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
newpoints.push( qx );
newpoints.push( intery );
for ( m = 2; m < ps; ++m ) {
newpoints.push( points[ i + m ] );
}
bottom = qy;
}
j += otherps;
} else { // px < qx
// if we come from a gap, we just skip this point
if ( fromgap && withlines ) {
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 + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
}
//newpoints[l + 1] += 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( computeFillBottoms );
}
$.plot.plugins.push({
init: init,
options: options,
name: "fillbetween",
version: "1.0"
});
})(jQuery);

3218
src/vendor/flot/jquery.flot.js vendored

File diff suppressed because it is too large Load Diff

379
src/vendor/flot/jquery.flot.selection.js vendored

@ -0,0 +1,379 @@
/* 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(e);
else {
// this counts as a clear
plot.getPlaceholder().trigger("plotunselected", [ ]);
plot.getPlaceholder().trigger("plotselecting", [ null ]);
}
setTimeout(function() {
plot.isSelecting = false;
}, 10);
return false;
}
function getSelection() {
if (!selectionIsSane())
return null;
if (!selection.show) return null;
var r = {}, c1 = selection.first, c2 = selection.second;
var axes = plot.getAxes();
// look if no axis is used
var noAxisInUse = true;
$.each(axes, function (name, axis) {
if (axis.used) {
anyUsed = false;
}
})
$.each(axes, function (name, axis) {
if (axis.used || noAxisInUse) {
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(event) {
var r = getSelection();
// Add ctrlKey and metaKey to event
r.ctrlKey = event.ctrlKey;
r.metaKey = event.metaKey;
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()) {
plot.isSelecting = true;
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);

190
src/vendor/flot/jquery.flot.stack.js vendored

@ -0,0 +1,190 @@
/* Flot plugin for stacking data sets rather than overlyaing them.
Copyright (c) 2007-2014 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,
keyOffset = horizontal ? 1 : 0,
accumulateOffset = horizontal ? 0 : 1,
i = 0, j = 0, l, m;
while (true) {
if (i >= points.length && j >= otherpoints.length)
break;
l = newpoints.length;
if (i < points.length && points[i] == null) {
// copy gaps
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
i += ps;
}
else if (i >= points.length) {
// take the remaining points from the previous series
for (m = 0; m < ps; ++m)
newpoints.push(otherpoints[j + m]);
if (withbottom)
newpoints[l + 2] = otherpoints[j + accumulateOffset];
j += otherps;
}
else if (j >= otherpoints.length) {
// take the remaining points from the current series
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
i += ps;
}
else if (j < otherpoints.length && otherpoints[j] == null) {
// ignore point
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) {
// take the point from the previous series so that next series will correctly stack
if (i == 0) {
for (m = 0; m < ps; ++m)
newpoints.push(otherpoints[j + m]);
bottom = qy;
}
// we got past point below, might need to
// insert interpolated extra point
if (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
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 (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);

127
src/vendor/flot/jquery.flot.stackpercent.js vendored

@ -0,0 +1,127 @@
(
function ($) {
var options = {
series: {
stackpercent: null
} // or number/string
};
function init(plot) {
// will be built up dynamically as a hash from x-value, or y-value if horizontal
var stackBases = {};
var processed = false;
var stackSums = {};
//set percentage for stacked chart
function processRawData(plot, series, data, datapoints) {
if (!processed) {
processed = true;
stackSums = getStackSums(plot.getData());
}
if (series.stackpercent == true) {
var num = data.length;
series.percents = [];
var key_idx = 0;
var value_idx = 1;
if (series.bars && series.bars.horizontal && series.bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
for (var j = 0; j < num; j++) {
var sum = stackSums[data[j][key_idx] + ""];
if (sum > 0) {
series.percents.push(data[j][value_idx] * 100 / sum);
} else {
series.percents.push(0);
}
}
}
}
//calculate summary
function getStackSums(_data) {
var data_len = _data.length;
var sums = {};
if (data_len > 0) {
//caculate summary
for (var i = 0; i < data_len; i++) {
if (_data[i].stackpercent) {
var key_idx = 0;
var value_idx = 1;
if (_data[i].bars && _data[i].bars.horizontal && _data[i].bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
var num = _data[i].data.length;
for (var j = 0; j < num; j++) {
var value = 0;
if (_data[i].data[j][1] != null) {
value = _data[i].data[j][value_idx];
}
if (sums[_data[i].data[j][key_idx] + ""]) {
sums[_data[i].data[j][key_idx] + ""] += value;
} else {
sums[_data[i].data[j][key_idx] + ""] = value;
}
}
}
}
}
return sums;
}
function stackData(plot, s, datapoints) {
if (!s.stackpercent) return;
if (!processed) {
stackSums = getStackSums(plot.getData());
}
var newPoints = [];
var key_idx = 0;
var value_idx = 1;
if (s.bars && s.bars.horizontal && s.bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
for (var i = 0; i < datapoints.points.length; i += 3) {
// note that the values need to be turned into absolute y-values.
// in other words, if you were to stack (x, y1), (x, y2), and (x, y3),
// (each from different series, which is where stackBases comes in),
// you'd want the new points to be (x, y1, 0), (x, y1+y2, y1), (x, y1+y2+y3, y1+y2)
// generally, (x, thisValue + (base up to this point), + (base up to this point))
if (!stackBases[datapoints.points[i + key_idx]]) {
stackBases[datapoints.points[i + key_idx]] = 0;
}
newPoints[i + key_idx] = datapoints.points[i + key_idx];
newPoints[i + value_idx] = datapoints.points[i + value_idx] + stackBases[datapoints.points[i + key_idx]];
newPoints[i + 2] = stackBases[datapoints.points[i + key_idx]];
stackBases[datapoints.points[i + key_idx]] += datapoints.points[i + value_idx];
// change points to percentage values
// you may need to set yaxis:{ max = 100 }
if ( stackSums[newPoints[i+key_idx]+""] > 0 ){
newPoints[i + value_idx] = newPoints[i + value_idx] * 100 / stackSums[newPoints[i + key_idx] + ""];
newPoints[i + 2] = newPoints[i + 2] * 100 / stackSums[newPoints[i + key_idx] + ""];
} else {
newPoints[i + value_idx] = 0;
newPoints[i + 2] = 0;
}
}
datapoints.points = newPoints;
}
plot.hooks.processRawData.push(processRawData);
plot.hooks.processDatapoints.push(stackData);
}
$.plot.plugins.push({
init: init,
options: options,
name: 'stackpercent',
version: '0.1'
});
})(jQuery);

432
src/vendor/flot/jquery.flot.time.js vendored

@ -0,0 +1,432 @@
/* 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': // For back-compat with 0.7; remove in 1.0
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.processOptions.push(function (plot, options) {
$.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);
}
if (step >= timeUnitSize.hour) {
d.setMinutes(0);
}
if (step >= timeUnitSize.day) {
d.setHours(0);
}
if (step >= timeUnitSize.day * 4) {
d.setDate(1);
}
if (step >= timeUnitSize.month * 2) {
d.setMonth(floorInBase(d.getMonth(), 3));
}
if (step >= timeUnitSize.quarter * 2) {
d.setMonth(floorInBase(d.getMonth(), 6));
}
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);
Loading…
Cancel
Save