13 changed files with 16 additions and 7042 deletions
@ -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); |
@ -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) |
@ -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); |
@ -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); |
@ -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); |
File diff suppressed because it is too large
Load Diff
@ -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); |
@ -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); |
@ -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); |
@ -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); |
@ -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…
Reference in new issue