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