Browse Source

Problems with singlestat and piechart panels #42 (#44)

master
rozetko 6 years ago committed by GitHub
parent
commit
39e54226ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      src/graph_renderer.ts
  2. 2
      src/threshold_manager.ts
  3. 176
      src/vendor/flot/jquery.flot.crosshair.js
  4. 236
      src/vendor/flot/jquery.flot.dashes.js
  5. 288
      src/vendor/flot/jquery.flot.fillbelow.js
  6. 225
      src/vendor/flot/jquery.flot.fillbetween.js
  7. 960
      src/vendor/flot/jquery.flot.gauge.js
  8. 3198
      src/vendor/flot/jquery.flot.js
  9. 817
      src/vendor/flot/jquery.flot.pie.js
  10. 380
      src/vendor/flot/jquery.flot.selection.js
  11. 190
      src/vendor/flot/jquery.flot.stack.js
  12. 126
      src/vendor/flot/jquery.flot.stackpercent.js
  13. 431
      src/vendor/flot/jquery.flot.time.js

29
src/graph_renderer.ts

@ -12,15 +12,17 @@ import {
import { GraphCtrl } from './module';
import './vendor/flot/jquery.flot';
import './vendor/flot/jquery.flot.time';
import './vendor/flot/jquery.flot.selection';
import './vendor/flot/jquery.flot.stack';
import './vendor/flot/jquery.flot.stackpercent';
import './vendor/flot/jquery.flot.fillbelow';
import './vendor/flot/jquery.flot.crosshair';
import './vendor/flot/jquery.flot.dashes';
import './vendor/flot/jquery.flot.events';
import 'grafana/vendor/flot/jquery.flot.js';
import 'grafana/vendor/flot/jquery.flot.time.js';
import 'grafana/vendor/flot/jquery.flot.selection.js';
import 'grafana/vendor/flot/jquery.flot.stack.js';
import 'grafana/vendor/flot/jquery.flot.stackpercent.js';
import 'grafana/vendor/flot/jquery.flot.fillbelow.js';
import 'grafana/vendor/flot/jquery.flot.crosshair.js';
import 'grafana/vendor/flot/jquery.flot.dashes.js';
import 'grafana/vendor/flot/jquery.flot.gauge.js';
import 'grafana/vendor/flot/jquery.flot.pie.js';
import './vendor/flot/jquery.flot.events.js';
// import { EventManager } from 'grafana/app/features/annotations/event_manager';
import TimeSeries from 'grafana/app/core/time_series2';
@ -76,11 +78,11 @@ export class GraphRenderer {
if(this._ananlyticController === undefined) {
throw new Error('ananlyticController is undefined');
}
// this.annotations = [];
this.panelWidth = 0;
// this.eventManager = new EventManager(this.ctrl);
this.flotOptions = {}
this.thresholdManager = new ThresholdManager(this.ctrl);
@ -349,7 +351,7 @@ export class GraphRenderer {
var strokeAlpha = 0.4;
if(this._isAnomalyEvent(e)) {
if(this._ananlyticController.labelingDeleteMode) {
color = this.contextSrv.user.lightTheme ?
color = this.contextSrv.user.lightTheme ?
ANOMALY_REGION_DELETE_COLOR_LIGHT :
ANOMALY_REGION_DELETE_COLOR_DARK;
} else {
@ -841,4 +843,3 @@ function updateLegendValues(data: TimeSeries[], panel) {
}
}
}

2
src/threshold_manager.ts

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

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

@ -1,176 +0,0 @@
/* 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.
*/
(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);

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

@ -1,236 +0,0 @@
/*
* 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]>
* }
* }
* }
*/
(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)

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

@ -1,288 +0,0 @@
(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);

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

@ -1,225 +0,0 @@
/* 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);

960
src/vendor/flot/jquery.flot.gauge.js vendored

@ -1,960 +0,0 @@
/*!
* jquery.flot.gauge v1.1.0 *
*
* Flot plugin for rendering gauge charts.
*
* Copyright (c) 2015 @toyoty99.
* Licensed under the MIT license.
*/
/**
* @module flot.gauge
*/
(function($) {
/**
* Gauge class
*
* @class Gauge
*/
var Gauge = (function() {
/**
* context of canvas
*
* @property context
* @type Object
*/
var context;
/**
* placeholder of canvas
*
* @property placeholder
* @type Object
*/
var placeholder;
/**
* options of plot
*
* @property options
* @type Object
*/
var options;
/**
* options of gauge
*
* @property gaugeOptions
* @type Object
*/
var gaugeOptions;
/**
* data series
*
* @property series
* @type Array
*/
var series;
/**
* logger
*
* @property logger
* @type Object
*/
var logger;
/**
* constructor
*
* @class Gauge
* @constructor
* @param {Object} gaugeOptions gauge options
*/
var Gauge = function(plot, ctx) {
context = ctx;
placeholder = plot.getPlaceholder();
options = plot.getOptions();
gaugeOptions = options.series.gauges;
series = plot.getData();
logger = getLogger(gaugeOptions.debug);
}
/**
* calculate layout
*
* @method calculateLayout
* @return the calculated layout properties
*/
Gauge.prototype.calculateLayout = function() {
var canvasWidth = placeholder.width();
var canvasHeight = placeholder.height();
// calculate cell size
var columns = Math.min(series.length, gaugeOptions.layout.columns);
var rows = Math.ceil(series.length / columns);
var margin = gaugeOptions.layout.margin;
var hMargin = gaugeOptions.layout.hMargin;
var vMargin = gaugeOptions.layout.vMargin;
var cellWidth = (canvasWidth - (margin * 2) - (hMargin * (columns - 1))) / columns;
var cellHeight = (canvasHeight - (margin * 2) - (vMargin * (rows - 1))) / rows;
if (gaugeOptions.layout.square) {
var cell = Math.min(cellWidth, cellHeight);
cellWidth = cell;
cellHeight = cell;
}
// calculate 'auto' values
calculateAutoValues(gaugeOptions, cellWidth);
// calculate maximum radius
var cellMargin = gaugeOptions.cell.margin;
var labelMargin = 0;
var labelFontSize = 0;
if (gaugeOptions.label.show) {
labelMargin = gaugeOptions.label.margin;
labelFontSize = gaugeOptions.label.font.size;
}
var valueMargin = 0;
var valueFontSize = 0;
if (gaugeOptions.value.show) {
valueMargin = gaugeOptions.value.margin;
valueFontSize = gaugeOptions.value.font.size;
}
var thresholdWidth = 0;
if (gaugeOptions.threshold.show) {
thresholdWidth = gaugeOptions.threshold.width;
}
var thresholdLabelMargin = 0;
var thresholdLabelFontSize = 0;
if (gaugeOptions.threshold.label.show) {
thresholdLabelMargin = gaugeOptions.threshold.label.margin;
thresholdLabelFontSize = gaugeOptions.threshold.label.font.size;
}
var maxRadiusH = (cellWidth / 2) - cellMargin - thresholdWidth - (thresholdLabelMargin * 2) - thresholdLabelFontSize;
var startAngle = gaugeOptions.gauge.startAngle;
var endAngle = gaugeOptions.gauge.endAngle;
var dAngle = (endAngle - startAngle) / 100;
var heightRatioV = -1;
for (var a = startAngle; a < endAngle; a += dAngle) {
heightRatioV = Math.max(heightRatioV, Math.sin(toRad(a)));
}
heightRatioV = Math.max(heightRatioV, Math.sin(toRad(endAngle)));
var outerRadiusV = (cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize) / (1 + heightRatioV);
if (outerRadiusV * heightRatioV < valueMargin + (valueFontSize / 2)) {
outerRadiusV = cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize - valueMargin - (valueFontSize / 2);
}
var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth;
var radius = Math.min(maxRadiusH, maxRadiusV);
var width = gaugeOptions.gauge.width;
if (width >= radius) {
width = Math.max(3, radius / 3);
}
var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius;
var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2));
return {
canvasWidth: canvasWidth,
canvasHeight: canvasHeight,
margin: margin,
hMargin: hMargin,
vMargin: vMargin,
columns: columns,
rows: rows,
cellWidth: cellWidth,
cellHeight: cellHeight,
cellMargin: cellMargin,
labelMargin: labelMargin,
labelFontSize: labelFontSize,
valueMargin: valueMargin,
valueFontSize: valueFontSize,
width: width,
radius: radius,
thresholdWidth: thresholdWidth,
thresholdLabelMargin: thresholdLabelMargin,
thresholdLabelFontSize: thresholdLabelFontSize,
gaugeOuterHeight: gaugeOuterHeight
};
}
/**
* calculate the values which are set as 'auto'
*
* @method calculateAutoValues
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Number} cellWidth the width of cell
*/
function calculateAutoValues(gaugeOptionsi, cellWidth) {
if (gaugeOptionsi.gauge.width === "auto") {
gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8);
}
if (gaugeOptionsi.label.margin === "auto") {
gaugeOptionsi.label.margin = Math.max(1, cellWidth / 20);
}
if (gaugeOptionsi.label.font.size === "auto") {
gaugeOptionsi.label.font.size = Math.max(5, cellWidth / 8);
}
if (gaugeOptionsi.value.margin === "auto") {
gaugeOptionsi.value.margin = Math.max(1, cellWidth / 30);
}
if (gaugeOptionsi.value.font.size === "auto") {
gaugeOptionsi.value.font.size = Math.max(5, cellWidth / 9);
}
if (gaugeOptionsi.threshold.width === "auto") {
gaugeOptionsi.threshold.width = Math.max(3, cellWidth / 100);
}
if (gaugeOptionsi.threshold.label.margin === "auto") {
gaugeOptionsi.threshold.label.margin = Math.max(3, cellWidth / 40);
}
if (gaugeOptionsi.threshold.label.font.size === "auto") {
gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15);
}
}
Gauge.prototype.calculateAutoValues = calculateAutoValues;
/**
* calculate the layout of the cell inside
*
* @method calculateCellLayout
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Number} cellWidth the width of cell
* @param {Number} i the index of the series
* @return the calculated cell layout properties
*/
Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) {
// calculate top, left and center
var c = col(layout.columns, i);
var r = row(layout.columns, i);
var x = layout.margin + (layout.cellWidth + layout.hMargin) * c;
var y = layout.margin + (layout.cellHeight + layout.vMargin) * r;
var cx = x + (layout.cellWidth / 2);
var cy = y + layout.cellMargin + (layout.labelMargin * 2) + layout.labelFontSize + layout.thresholdWidth
+ layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2) + layout.radius;
var blank = layout.cellHeight - (layout.cellMargin * 2) - (layout.labelMargin * 2) - layout.labelFontSize - layout.gaugeOuterHeight;
var offsetY = 0;
if (gaugeOptionsi.cell.vAlign === "middle") {
offsetY = (blank / 2);
} else if (gaugeOptionsi.cell.vAlign === "bottom") {
offsetY = blank;
}
cy += offsetY;
return {
col: c,
row: r,
x: x,
y: y,
offsetY: offsetY,
cellWidth: layout.cellWidth,
cellHeight: layout.cellHeight,
cellMargin: layout.cellMargin,
cx: cx,
cy: cy
}
}
/**
* draw the background of chart
*
* @method drawBackground
* @param {Object} layout the layout properties
*/
Gauge.prototype.drawBackground = function(layout) {
if (!gaugeOptions.frame.show) {
return;
}
context.save();
context.strokeStyle = options.grid.borderColor;
context.lineWidth = options.grid.borderWidth;
context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight);
if (options.grid.backgroundColor) {
context.fillStyle = options.grid.backgroundColor;
context.fillRect(0, 0, layout.canvasWidth, layout.canvasHeight);
}
context.restore();
}
/**
* draw the background of cell
*
* @method drawCellBackground
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} cellLayout the cell layout properties
*/
Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) {
context.save();
if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) {
context.strokeStyle = gaugeOptionsi.cell.border.color;
context.lineWidth = gaugeOptionsi.cell.border.width;
context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight);
}
if (gaugeOptionsi.cell.background && gaugeOptionsi.cell.background.color) {
context.fillStyle = gaugeOptionsi.cell.background.color;
context.fillRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight);
}
context.restore();
}
/**
* draw the gauge
*
* @method drawGauge
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
* @param {String} label the label of data
* @param {Number} data the value of the gauge
*/
Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) {
var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0;
// draw gauge frame
drawArcWithShadow(
cellLayout.cx, // center x
cellLayout.cy, // center y
layout.radius,
layout.width,
toRad(gaugeOptionsi.gauge.startAngle),
toRad(gaugeOptionsi.gauge.endAngle),
gaugeOptionsi.gauge.border.color, // line color
gaugeOptionsi.gauge.border.width, // line width
gaugeOptionsi.gauge.background.color, // fill color
blur);
// draw gauge
var c1 = getColor(gaugeOptionsi, data);
var a2 = calculateAngle(gaugeOptionsi, layout, data);
drawArcWithShadow(
cellLayout.cx, // center x
cellLayout.cy, // center y
layout.radius - 1,
layout.width - 2,
toRad(gaugeOptionsi.gauge.startAngle),
toRad(a2),
c1, // line color
1, // line width
c1, // fill color
blur);
}
/**
* decide the color of the data from the threshold options
*
* @method getColor
* @private
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Number} data the value of the gauge
*/
function getColor(gaugeOptionsi, data) {
var color;
for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) {
var threshold = gaugeOptionsi.threshold.values[i];
color = threshold.color;
if (data < threshold.value) {
break;
}
}
return color;
}
/**
* calculate the angle of the data
*
* @method calculateAngle
* @private
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Number} data the value of the gauge
*/
function calculateAngle(gaugeOptionsi, layout, data) {
var a =
gaugeOptionsi.gauge.startAngle
+ (gaugeOptionsi.gauge.endAngle - gaugeOptionsi.gauge.startAngle)
* ((data - gaugeOptionsi.gauge.min) / (gaugeOptionsi.gauge.max - gaugeOptionsi.gauge.min));
if (a < gaugeOptionsi.gauge.startAngle) {
a = gaugeOptionsi.gauge.startAngle;
} else if (a > gaugeOptionsi.gauge.endAngle) {
a = gaugeOptionsi.gauge.endAngle;
}
return a;
}
/**
* draw the arc of the threshold
*
* @method drawThreshold
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
*/
Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) {
var a1 = gaugeOptionsi.gauge.startAngle;
for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) {
var threshold = gaugeOptionsi.threshold.values[i];
c1 = threshold.color;
a2 = calculateAngle(gaugeOptionsi, layout, threshold.value);
drawArc(
context,
cellLayout.cx, // center x
cellLayout.cy, // center y
layout.radius + layout.thresholdWidth,
layout.thresholdWidth - 2,
toRad(a1),
toRad(a2),
c1, // line color
1, // line width
c1); // fill color
a1 = a2;
}
}
/**
* draw an arc with a shadow
*
* @method drawArcWithShadow
* @private
* @param {Number} cx the x position of the center
* @param {Number} cy the y position of the center
* @param {Number} r the radius of an arc
* @param {Number} w the width of an arc
* @param {Number} rd1 the start angle of an arc in radians
* @param {Number} rd2 the end angle of an arc in radians
* @param {String} lc the color of a line
* @param {Number} lw the widht of a line
* @param {String} fc the fill color of an arc
* @param {Number} blur the shdow blur
*/
function drawArcWithShadow(cx, cy, r, w, rd1, rd2, lc, lw, fc, blur) {
if (rd1 === rd2) {
return;
}
context.save();
drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc);
if (blur) {
drawArc(context, cx, cy, r, w, rd1, rd2);
context.clip();
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 10;
context.shadowColor = "gray";
drawArc(context, cx, cy, r + 1, w + 2, rd1, rd2, lc, 1);
}
context.restore();
}
/**
* draw the label of the gauge
*
* @method drawLable
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
* @param {Number} i the index of the series
* @param {Object} item the item of the series
*/
Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) {
drawText(
cellLayout.cx,
cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY,
"flotGagueLabel" + i,
gaugeOptionsi.label.formatter ? gaugeOptionsi.label.formatter(item.label, item.data[0][1]) : text,
gaugeOptionsi.label);
}
/**
* draw the value of the gauge
*
* @method drawValue
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
* @param {Number} i the index of the series
* @param {Object} item the item of the series
*/
Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) {
drawText(
cellLayout.cx,
cellLayout.cy - (gaugeOptionsi.value.font.size / 2),
"flotGagueValue" + i,
gaugeOptionsi.value.formatter ? gaugeOptionsi.value.formatter(item.label, item.data[0][1]) : text,
gaugeOptionsi.value);
}
/**
* draw the values of the threshold
*
* @method drawThresholdValues
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
* @param {Number} i the index of the series
*/
Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) {
// min, max
drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Min" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle);
drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Max" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle);
// threshold values
for (var j = 0; j < gaugeOptionsi.threshold.values.length; j++) {
var threshold = gaugeOptionsi.threshold.values[j];
if (threshold.value > gaugeOptionsi.gauge.min && threshold.value < gaugeOptionsi.gauge.max) {
var a = calculateAngle(gaugeOptionsi, layout, threshold.value);
drawThresholdValue(gaugeOptionsi, layout, cellLayout, i + "_" + j, threshold.value, a);
}
}
}
/**
* draw the value of the threshold
*
* @method drawThresholdValue
* @param {Object} gaugeOptionsi the options of the gauge
* @param {Object} layout the layout properties
* @param {Object} cellLayout the cell layout properties
* @param {Number} i the index of the series
* @param {Number} value the value of the threshold
* @param {Number} a the angle of the value drawn
*/
function drawThresholdValue(gaugeOptionsi, layout, cellLayout, i, value, a) {
drawText(
cellLayout.cx
+ ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius)
* Math.cos(toRad(a))),
cellLayout.cy
+ ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius)
* Math.sin(toRad(a))),
"flotGagueThresholdValue" + i,
gaugeOptionsi.threshold.label.formatter ? gaugeOptionsi.threshold.label.formatter(value) : value,
gaugeOptionsi.threshold.label,
a);
}
/**
* draw a text
*
* the textOptions is assumed as follows:
*
* textOptions: {
* background: {
* color: null,
* opacity: 0
* },
* font: {
* size: "auto"
* family: "\"MS ゴシック\",sans-serif"
* },
* color: null
* }
*
* @method drawText
* @private
* @param {Number} x the x position of the text drawn (left top)
* @param {Number} y the y position of the text drawn (left top)
* @param {String} id the id of the dom element
* @param {String} text the text drawn
* @param {Object} textOptions the option of the text
* @param {Number} [a] the angle of the value drawn
*/
function drawText(x, y, id, text, textOptions, a) {
var span = $("." + id, placeholder);
var exists = span.length;
if (!exists) {
span = $("<span></span>")
span.attr("id", id);
span.css("position", "absolute");
span.css("top", y + "px");
if (textOptions.font.size) {
span.css("font-size", textOptions.font.size + "px");
}
if (textOptions.font.family) {
span.css("font-family", textOptions.font.family);
}
if (textOptions.color) {
span.css("color", textOptions.color);
}
if (textOptions.background.color) {
span.css("background-color", textOptions.background.color);
}
if (textOptions.background.opacity) {
span.css("opacity", textOptions.background.opacity);
}
placeholder.append(span);
}
span.text(text);
// after append, readjust the left position
span.css("left", x + "px"); // for redraw, resetting the left position is needed here
span.css("left", (parseInt(span.css("left")) - (span.width()/ 2)) + "px");
// at last, set angle
if (!exists && a) {
span.css("top", (parseInt(span.css("top")) - (span.height()/ 2)) + "px");
span.css("transform", "rotate(" + ((180 * a) + 90) + "deg)"); // not supported for ie8
}
}
return Gauge;
})();
/**
* get a instance of Logger
*
* @method getLogger
* @for flot.gauge
* @private
* @param {Object} debugOptions the options of debug
*/
function getLogger(debugOptions) {
return typeof Logger !== "undefined" ? new Logger(debugOptions) : null;
}
/**
* calculate the index of columns for the specified data
*
* @method col
* @for flot.gauge
* @param {Number} columns the number of columns
* @param {Number} i the index of the series
* @return the index of columns
*/
function col(columns, i) {
return i % columns;
}
/**
* calculate the index of rows for the specified data
*
* @method row
* @for flot.gauge
* @param {Number} columns the number of rows
* @param {Number} i the index of the series
* @return the index of rows
*/
function row(columns, i) {
return Math.floor(i / columns);
}
/**
* calculate the angle in radians
*
* internally, use a number without PI (0 - 2).
* so, in this function, multiply PI
*
* @method toRad
* @for flot.gauge
* @param {Number} a the number of angle without PI
* @return the angle in radians
*/
function toRad(a) {
return a * Math.PI;
}
/**
* draw an arc
*
* @method drawArc
* @for flot.gauge
* @param {Object} context the context of canvas
* @param {Number} cx the x position of the center
* @param {Number} cy the y position of the center
* @param {Number} r the radius of an arc
* @param {Number} w the width of an arc
* @param {Number} rd1 the start angle of an arc in radians
* @param {Number} rd2 the end angle of an arc in radians
* @param {String} lc the color of a line
* @param {Number} lw the widht of a line
* @param {String} fc the fill color of an arc
*/
function drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc) {
if (rd1 === rd2) {
return;
}
var counterClockwise = false;
context.save();
context.beginPath();
context.arc(cx, cy, r, rd1, rd2, counterClockwise);
context.lineTo(cx + (r - w) * Math.cos(rd2),
cy + (r - w) * Math.sin(rd2));
context.arc(cx, cy, r - w, rd2, rd1, !counterClockwise);
context.closePath();
if (lw) {
context.lineWidth = lw;
}
if (lc) {
context.strokeStyle = lc;
context.stroke();
}
if (fc) {
context.fillStyle = fc;
context.fill();
}
context.restore();
}
/**
* initialize plugin
*
* @method init
* @for flot.gauge
* @private
* @param {Object} plot a instance of plot
*/
function init (plot) {
// add processOptions hook
plot.hooks.processOptions.push(function(plot, options) {
var logger = getLogger(options.series.gauges.debug);
// turn 'grid' and 'legend' off
if (options.series.gauges.show) {
options.grid.show = false;
options.legend.show = false;
}
// sort threshold
var thresholds = options.series.gauges.threshold.values;
thresholds.sort(function(a, b) {
if (a.value < b.value) {
return -1;
} else if (a.value > b.value) {
return 1;
} else {
return 0;
}
});
});
// add draw hook
plot.hooks.draw.push(function(plot, context) {
var options = plot.getOptions();
var gaugeOptions = options.series.gauges;
var logger = getLogger(gaugeOptions.debug);
if (!gaugeOptions.show) {
return;
}
var series = plot.getData();
if (!series || !series.length) {
return; // if no series were passed
}
var gauge = new Gauge(plot, context);
// calculate layout
var layout = gauge.calculateLayout();
// debug layout
if (gaugeOptions.debug.layout) {
}
// draw background
gauge.drawBackground(layout)
// draw cells (label, gauge, value, threshold)
for (var i = 0; i < series.length; i++) {
var item = series[i];
var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges);
if (item.gauges) {
// re-calculate 'auto' values
gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth);
}
// calculate cell layout
var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i);
// draw cell background
gauge.drawCellBackground(gaugeOptionsi, cellLayout)
// debug layout
if (gaugeOptionsi.debug.layout) {
}
// draw label
if (gaugeOptionsi.label.show) {
gauge.drawLable(gaugeOptionsi, layout, cellLayout, i, item);
}
// draw gauge
gauge.drawGauge(gaugeOptionsi, layout, cellLayout, item.label, item.data[0][1]);
// draw threshold
if (gaugeOptionsi.threshold.show) {
gauge.drawThreshold(gaugeOptionsi, layout, cellLayout);
}
if (gaugeOptionsi.threshold.label.show) {
gauge.drawThresholdValues(gaugeOptionsi, layout, cellLayout, i)
}
// draw value
if (gaugeOptionsi.value.show) {
gauge.drawValue(gaugeOptionsi, layout, cellLayout, i, item);
}
}
});
}
/**
* [defaults description]
*
* @property defaults
* @type {Object}
*/
var defaults = {
series: {
gauges: {
debug: {
log: false,
layout: false,
alert: false
},
show: false,
layout: {
margin: 5,
columns: 3,
hMargin: 5,
vMargin: 5,
square: false
},
frame: {
show: true
},
cell: {
background: {
color: null
},
border: {
show: true,
color: "black",
width: 1
},
margin: 5,
vAlign: "middle" // 'top' or 'middle' or 'bottom'
},
gauge: {
width: "auto", // a specified number, or 'auto'
startAngle: 0.9, // 0 - 2 factor of the radians
endAngle: 2.1, // 0 - 2 factor of the radians
min: 0,
max: 100,
background: {
color: "white"
},
border: {
color: "lightgray",
width: 2
},
shadow: {
show: true,
blur: 5
}
},
label: {
show: true,
margin: "auto", // a specified number, or 'auto'
background: {
color: null,
opacity: 0
},
font: {
size: "auto", // a specified number, or 'auto'
family: "sans-serif"
},
color: null,
formatter: function(label, value) {
return label;
}
},
value: {
show: true,
margin: "auto", // a specified number, or 'auto'
background: {
color: null,
opacity: 0
},
font: {
size: "auto", // a specified number, or 'auto'
family: "sans-serif"
},
color: null,
formatter: function(label, value) {
return parseInt(value);
}
},
threshold: {
show: true,
width: "auto", // a specified number, or 'auto'
label: {
show: true,
margin: "auto", // a specified number, or 'auto'
background: {
color: null,
opacity: 0
},
font: {
size: "auto", // a specified number, or 'auto'
family: ",sans-serif"
},
color: null,
formatter: function(value) {
return value;
}
},
values: [
{
value: 50,
color: "lightgreen"
}, {
value: 80,
color: "yellow"
}, {
value: 100,
color: "red"
}
]
}
}
}
};
// register the gauge plugin
$.plot.plugins.push({
init: init,
options: defaults,
name: "gauge",
version: "1.1.0"
});
})(jQuery);

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

File diff suppressed because it is too large Load Diff

817
src/vendor/flot/jquery.flot.pie.js vendored

@ -1,817 +0,0 @@
/* Flot plugin for rendering pie charts.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes that each series has a single data value, and that each
value is a positive integer or zero. Negative numbers don't make sense for a
pie chart, and have unpredictable results. The values do NOT need to be
passed in as percentages; the plugin will calculate the total and per-slice
percentages internally.
* Created by Brian Medendorp
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
The plugin supports these options:
series: {
pie: {
show: true/false
radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
offset: {
top: integer value to move the pie up or down
left: integer value to move the pie left or right, or 'auto'
},
stroke: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
width: integer pixel width of the stroke
},
label: {
show: true/false, or 'auto'
formatter: a user-defined function that modifies the text/style of the label text
radius: 0-1 for percentage of fullsize, or a specified pixel length
background: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
opacity: 0-1
},
threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
},
combine: {
threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
label: any text value of what the combined slice should be labeled
}
highlight: {
opacity: 0-1
}
}
}
More detail and specific examples can be found in the included HTML file.
*/
(function($) {
// Maximum redraw attempts when fitting labels within the plot
var REDRAW_ATTEMPTS = 10;
// Factor by which to shrink the pie when fitting labels within the plot
var REDRAW_SHRINK = 0.95;
function init(plot) {
var canvas = null,
target = null,
maxRadius = null,
centerLeft = null,
centerTop = null,
processed = false,
ctx = null;
// interactive variables
var highlights = [];
// add hook to determine if pie plugin in enabled, and then perform necessary operations
plot.hooks.processOptions.push(function(plot, options) {
if (options.series.pie.show) {
options.grid.show = false;
// set labels.show
if (options.series.pie.label.show == "auto") {
if (options.legend.show) {
options.series.pie.label.show = false;
} else {
options.series.pie.label.show = true;
}
}
// set radius
if (options.series.pie.radius == "auto") {
if (options.series.pie.label.show) {
options.series.pie.radius = 3/4;
} else {
options.series.pie.radius = 1;
}
}
// ensure sane tilt
if (options.series.pie.tilt > 1) {
options.series.pie.tilt = 1;
} else if (options.series.pie.tilt < 0) {
options.series.pie.tilt = 0;
}
}
});
plot.hooks.bindEvents.push(function(plot, eventHolder) {
var options = plot.getOptions();
if (options.series.pie.show) {
if (options.grid.hoverable) {
eventHolder.unbind("mousemove").mousemove(onMouseMove);
}
if (options.grid.clickable) {
eventHolder.unbind("click").click(onClick);
}
}
});
plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
var options = plot.getOptions();
if (options.series.pie.show) {
processDatapoints(plot, series, data, datapoints);
}
});
plot.hooks.drawOverlay.push(function(plot, octx) {
var options = plot.getOptions();
if (options.series.pie.show) {
drawOverlay(plot, octx);
}
});
plot.hooks.draw.push(function(plot, newCtx) {
var options = plot.getOptions();
if (options.series.pie.show) {
draw(plot, newCtx);
}
});
function processDatapoints(plot, series, datapoints) {
if (!processed) {
processed = true;
canvas = plot.getCanvas();
target = $(canvas).parent();
options = plot.getOptions();
plot.setData(combine(plot.getData()));
}
}
function combine(data) {
var total = 0,
combined = 0,
numCombined = 0,
color = options.series.pie.combine.color,
newdata = [];
// Fix up the raw data from Flot, ensuring the data is numeric
for (var i = 0; i < data.length; ++i) {
var value = data[i].data;
// If the data is an array, we'll assume that it's a standard
// Flot x-y pair, and are concerned only with the second value.
// Note how we use the original array, rather than creating a
// new one; this is more efficient and preserves any extra data
// that the user may have stored in higher indexes.
if ($.isArray(value) && value.length == 1) {
value = value[0];
}
if ($.isArray(value)) {
// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
value[1] = +value[1];
} else {
value[1] = 0;
}
} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
value = [1, +value];
} else {
value = [1, 0];
}
data[i].data = [value];
}
// Sum up all the slices, so we can calculate percentages for each
for (var i = 0; i < data.length; ++i) {
total += data[i].data[0][1];
}
// Count the number of slices with percentages below the combine
// threshold; if it turns out to be just one, we won't combine.
for (var i = 0; i < data.length; ++i) {
var value = data[i].data[0][1];
if (value / total <= options.series.pie.combine.threshold) {
combined += value;
numCombined++;
if (!color) {
color = data[i].color;
}
}
}
for (var i = 0; i < data.length; ++i) {
var value = data[i].data[0][1];
if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
newdata.push({
data: [[1, value]],
color: data[i].color,
label: data[i].label,
angle: value * Math.PI * 2 / total,
percent: value / (total / 100)
});
}
}
if (numCombined > 1) {
newdata.push({
data: [[1, combined]],
color: color,
label: options.series.pie.combine.label,
angle: combined * Math.PI * 2 / total,
percent: combined / (total / 100)
});
}
return newdata;
}
function draw(plot, newCtx) {
if (!target) {
return; // if no series were passed
}
var canvasWidth = plot.getPlaceholder().width(),
canvasHeight = plot.getPlaceholder().height(),
legendWidth = target.children().filter(".legend").children().width() || 0;
ctx = newCtx;
// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
// When combining smaller slices into an 'other' slice, we need to
// add a new series. Since Flot gives plugins no way to modify the
// list of series, the pie plugin uses a hack where the first call
// to processDatapoints results in a call to setData with the new
// list of series, then subsequent processDatapoints do nothing.
// The plugin-global 'processed' flag is used to control this hack;
// it starts out false, and is set to true after the first call to
// processDatapoints.
// Unfortunately this turns future setData calls into no-ops; they
// call processDatapoints, the flag is true, and nothing happens.
// To fix this we'll set the flag back to false here in draw, when
// all series have been processed, so the next sequence of calls to
// processDatapoints once again starts out with a slice-combine.
// This is really a hack; in 0.9 we need to give plugins a proper
// way to modify series before any processing begins.
processed = false;
// calculate maximum radius and center point
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
centerTop = canvasHeight / 2 + options.series.pie.offset.top;
centerLeft = canvasWidth / 2;
if (options.series.pie.offset.left == "auto") {
if (options.legend.position.match("w")) {
centerLeft += legendWidth / 2;
} else {
centerLeft -= legendWidth / 2;
}
} else {
centerLeft += options.series.pie.offset.left;
}
if (centerLeft < maxRadius) {
centerLeft = maxRadius;
} else if (centerLeft > canvasWidth - maxRadius) {
centerLeft = canvasWidth - maxRadius;
}
var slices = plot.getData(),
attempts = 0;
// Keep shrinking the pie's radius until drawPie returns true,
// indicating that all the labels fit, or we try too many times.
do {
if (attempts > 0) {
maxRadius *= REDRAW_SHRINK;
}
attempts += 1;
clear();
if (options.series.pie.tilt <= 0.8) {
drawShadow();
}
} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
if (attempts >= REDRAW_ATTEMPTS) {
clear();
target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
}
if (plot.setSeries && plot.insertLegend) {
plot.setSeries(slices);
plot.insertLegend();
}
// we're actually done at this point, just defining internal functions at this point
function clear() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
target.children().filter(".pieLabel, .pieLabelBackground").remove();
}
function drawShadow() {
var shadowLeft = options.series.pie.shadow.left;
var shadowTop = options.series.pie.shadow.top;
var edge = 10;
var alpha = options.series.pie.shadow.alpha;
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
return; // shadow would be outside canvas, so don't draw it
}
ctx.save();
ctx.translate(shadowLeft,shadowTop);
ctx.globalAlpha = alpha;
ctx.fillStyle = "#000";
// center and rotate to starting position
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//radius -= edge;
for (var i = 1; i <= edge; i++) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
ctx.fill();
radius -= i;
}
ctx.restore();
}
function drawPie() {
var startAngle = Math.PI * options.series.pie.startAngle;
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
// center and rotate to starting position
ctx.save();
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
// draw slices
ctx.save();
var currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i) {
slices[i].startAngle = currentAngle;
drawSlice(slices[i].angle, slices[i].color, true);
}
ctx.restore();
// draw slice outlines
if (options.series.pie.stroke.width > 0) {
ctx.save();
ctx.lineWidth = options.series.pie.stroke.width;
currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i) {
drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
}
ctx.restore();
}
// draw donut hole
drawDonutHole(ctx);
ctx.restore();
// Draw the labels, returning true if they fit within the plot
if (options.series.pie.label.show) {
return drawLabels();
} else return true;
function drawSlice(angle, color, fill) {
if (angle <= 0 || isNaN(angle)) {
return;
}
if (fill) {
ctx.fillStyle = color;
} else {
ctx.strokeStyle = color;
ctx.lineJoin = "round";
}
ctx.beginPath();
if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
ctx.moveTo(0, 0); // Center of the pie
}
//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
ctx.closePath();
//ctx.rotate(angle); // This doesn't work properly in Opera
currentAngle += angle;
if (fill) {
ctx.fill();
} else {
ctx.stroke();
}
}
function drawLabels() {
var currentAngle = startAngle;
var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
for (var i = 0; i < slices.length; ++i) {
if (slices[i].percent >= options.series.pie.label.threshold * 100) {
if (!drawLabel(slices[i], currentAngle, i)) {
return false;
}
}
currentAngle += slices[i].angle;
}
return true;
function drawLabel(slice, startAngle, index) {
if (slice.data[0][1] == 0) {
return true;
}
// format label text
var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
if (lf) {
text = lf(slice.label, slice);
} else {
text = slice.label;
}
if (plf) {
text = plf(text, slice);
}
var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
target.append(html);
var label = target.children("#pieLabel" + index);
var labelTop = (y - label.height() / 2);
var labelLeft = (x - label.width() / 2);
label.css("top", labelTop);
label.css("left", labelLeft);
// check to make sure that the label is not outside the canvas
if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
return false;
}
if (options.series.pie.label.background.opacity != 0) {
// put in the transparent background separately to avoid blended labels and label boxes
var c = options.series.pie.label.background.color;
if (c == null) {
c = slice.color;
}
var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
.css("opacity", options.series.pie.label.background.opacity)
.insertBefore(label);
}
return true;
} // end individual label function
} // end drawLabels function
} // end drawPie function
} // end draw function
// Placed here because it needs to be accessed from multiple locations
function drawDonutHole(layer) {
if (options.series.pie.innerRadius > 0) {
// subtract the center
layer.save();
var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
layer.beginPath();
layer.fillStyle = options.series.pie.stroke.color;
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
layer.fill();
layer.closePath();
layer.restore();
// add inner stroke
layer.save();
layer.beginPath();
layer.strokeStyle = options.series.pie.stroke.color;
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
layer.stroke();
layer.closePath();
layer.restore();
// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
}
}
//-- Additional Interactive related functions --
function isPointInPoly(poly, pt) {
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
&& (c = !c);
return c;
}
function findNearbySlice(mouseX, mouseY) {
var slices = plot.getData(),
options = plot.getOptions(),
radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
x, y;
for (var i = 0; i < slices.length; ++i) {
var s = slices[i];
if (s.pie.show) {
ctx.save();
ctx.beginPath();
ctx.moveTo(0, 0); // Center of the pie
//ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
ctx.closePath();
x = mouseX - centerLeft;
y = mouseY - centerTop;
if (ctx.isPointInPath) {
if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
ctx.restore();
return {
datapoint: [s.percent, s.data],
dataIndex: 0,
series: s,
seriesIndex: i
};
}
} else {
// excanvas for IE doesn;t support isPointInPath, this is a workaround.
var p1X = radius * Math.cos(s.startAngle),
p1Y = radius * Math.sin(s.startAngle),
p2X = radius * Math.cos(s.startAngle + s.angle / 4),
p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
p3X = radius * Math.cos(s.startAngle + s.angle / 2),
p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
p5X = radius * Math.cos(s.startAngle + s.angle),
p5Y = radius * Math.sin(s.startAngle + s.angle),
arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
arrPoint = [x, y];
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
if (isPointInPoly(arrPoly, arrPoint)) {
ctx.restore();
return {
datapoint: [s.percent, s.data],
dataIndex: 0,
series: s,
seriesIndex: i
};
}
}
ctx.restore();
}
}
return null;
}
function onMouseMove(e) {
triggerClickHoverEvent("plothover", e);
}
function onClick(e) {
triggerClickHoverEvent("plotclick", e);
}
// trigger click or hover event (they send the same parameters so we share their code)
function triggerClickHoverEvent(eventname, e) {
var offset = plot.offset();
var canvasX = parseInt(e.pageX - offset.left);
var canvasY = parseInt(e.pageY - offset.top);
var item = findNearbySlice(canvasX, canvasY);
if (options.grid.autoHighlight) {
// clear auto-highlights
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.auto == eventname && !(item && h.series == item.series)) {
unhighlight(h.series);
}
}
}
// highlight the slice
if (item) {
highlight(item.series, eventname);
}
// trigger any hover bind events
var pos = { pageX: e.pageX, pageY: e.pageY };
target.trigger(eventname, [pos, item]);
}
function highlight(s, auto) {
//if (typeof s == "number") {
// s = series[s];
//}
var i = indexOfHighlight(s);
if (i == -1) {
highlights.push({ series: s, auto: auto });
plot.triggerRedrawOverlay();
} else if (!auto) {
highlights[i].auto = false;
}
}
function unhighlight(s) {
if (s == null) {
highlights = [];
plot.triggerRedrawOverlay();
}
//if (typeof s == "number") {
// s = series[s];
//}
var i = indexOfHighlight(s);
if (i != -1) {
highlights.splice(i, 1);
plot.triggerRedrawOverlay();
}
}
function indexOfHighlight(s) {
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.series == s)
return i;
}
return -1;
}
function drawOverlay(plot, octx) {
var options = plot.getOptions();
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
octx.save();
octx.translate(centerLeft, centerTop);
octx.scale(1, options.series.pie.tilt);
for (var i = 0; i < highlights.length; ++i) {
drawHighlight(highlights[i].series);
}
drawDonutHole(octx);
octx.restore();
function drawHighlight(series) {
if (series.angle <= 0 || isNaN(series.angle)) {
return;
}
//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
octx.beginPath();
if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
octx.moveTo(0, 0); // Center of the pie
}
octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
octx.closePath();
octx.fill();
}
}
} // end init (plugin body)
// define pie specific options and their default values
var options = {
series: {
pie: {
show: false,
radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
innerRadius: 0, /* for donut */
startAngle: 3/2,
tilt: 1,
shadow: {
left: 5, // shadow left offset
top: 15, // shadow top offset
alpha: 0.02 // shadow alpha
},
offset: {
top: 0,
left: "auto"
},
stroke: {
color: "#fff",
width: 1
},
label: {
show: "auto",
formatter: function(label, slice) {
return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
}, // formatter function
radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
background: {
color: null,
opacity: 0
},
threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
},
combine: {
threshold: -1, // percentage at which to combine little slices into one larger slice
color: null, // color to give the new slice (auto-generated if null)
label: "Other" // label to give the new slice
},
highlight: {
//color: "#fff", // will add this functionality once parseColor is available
opacity: 0.5
}
}
}
};
$.plot.plugins.push({
init: init,
options: options,
name: "pie",
version: "1.1"
});
})(jQuery);

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

@ -1,380 +0,0 @@
/* 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', o.selection.strokeAlpha).toString();
ctx.lineWidth = 1;
ctx.lineJoin = o.selection.shape;
ctx.fillStyle = c.scale('a', o.selection.fillAlpha).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
strokeAlpha: 0.8,
fillAlpha: 0.4,
}
},
name: 'selection',
version: '1.1'
});
})(jQuery);

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

@ -1,190 +0,0 @@
/* 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);

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

@ -1,126 +0,0 @@
(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);

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

@ -1,431 +0,0 @@
/* 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