Browse Source
* Install jquery and imports-loader for loading flot-plugins * Remove jquery from externals and load flot-plugins using imports-loader * Fix imports * Add flot-plugins to vendor/flotmaster
16 changed files with 5395 additions and 112 deletions
@ -0,0 +1,178 @@
|
||||
/* Flot plugin for showing crosshairs when the mouse hovers over the plot. |
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
Licensed under the MIT license. |
||||
|
||||
The plugin supports these options: |
||||
|
||||
crosshair: { |
||||
mode: null or "x" or "y" or "xy" |
||||
color: color |
||||
lineWidth: number |
||||
} |
||||
|
||||
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical |
||||
crosshair that lets you trace the values on the x axis, "y" enables a |
||||
horizontal crosshair and "xy" enables them both. "color" is the color of the |
||||
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of |
||||
the drawn lines (default is 1). |
||||
|
||||
The plugin also adds four public methods: |
||||
|
||||
- setCrosshair( pos ) |
||||
|
||||
Set the position of the crosshair. Note that this is cleared if the user |
||||
moves the mouse. "pos" is in coordinates of the plot and should be on the |
||||
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple |
||||
axes), which is coincidentally the same format as what you get from a |
||||
"plothover" event. If "pos" is null, the crosshair is cleared. |
||||
|
||||
- clearCrosshair() |
||||
|
||||
Clear the crosshair. |
||||
|
||||
- lockCrosshair(pos) |
||||
|
||||
Cause the crosshair to lock to the current location, no longer updating if |
||||
the user moves the mouse. Optionally supply a position (passed on to |
||||
setCrosshair()) to move it to. |
||||
|
||||
Example usage: |
||||
|
||||
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; |
||||
$("#graph").bind( "plothover", function ( evt, position, item ) { |
||||
if ( item ) { |
||||
// Lock the crosshair to the data point being hovered
|
||||
myFlot.lockCrosshair({ |
||||
x: item.datapoint[ 0 ], |
||||
y: item.datapoint[ 1 ] |
||||
}); |
||||
} else { |
||||
// Return normal crosshair operation
|
||||
myFlot.unlockCrosshair(); |
||||
} |
||||
}); |
||||
|
||||
- unlockCrosshair() |
||||
|
||||
Free the crosshair to move again after locking it. |
||||
*/ |
||||
|
||||
( |
||||
// the actual Flot code
|
||||
function ($) { |
||||
var options = { |
||||
crosshair: { |
||||
mode: null, // one of null, "x", "y" or "xy",
|
||||
color: "rgba(170, 0, 0, 0.80)", |
||||
lineWidth: 1 |
||||
} |
||||
}; |
||||
|
||||
function init(plot) { |
||||
// position of crosshair in pixels
|
||||
var crosshair = { x: -1, y: -1, locked: false }; |
||||
|
||||
plot.setCrosshair = function setCrosshair(pos) { |
||||
if (!pos) |
||||
crosshair.x = -1; |
||||
else { |
||||
var o = plot.p2c(pos); |
||||
crosshair.x = Math.max(0, Math.min(o.left, plot.width())); |
||||
crosshair.y = Math.max(0, Math.min(o.top, plot.height())); |
||||
} |
||||
|
||||
plot.triggerRedrawOverlay(); |
||||
}; |
||||
|
||||
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
|
||||
|
||||
plot.lockCrosshair = function lockCrosshair(pos) { |
||||
if (pos) |
||||
plot.setCrosshair(pos); |
||||
crosshair.locked = true; |
||||
}; |
||||
|
||||
plot.unlockCrosshair = function unlockCrosshair() { |
||||
crosshair.locked = false; |
||||
}; |
||||
|
||||
function onMouseOut(e) { |
||||
if (crosshair.locked) |
||||
return; |
||||
|
||||
if (crosshair.x != -1) { |
||||
crosshair.x = -1; |
||||
plot.triggerRedrawOverlay(); |
||||
} |
||||
} |
||||
|
||||
function onMouseMove(e) { |
||||
if (crosshair.locked) |
||||
return; |
||||
|
||||
if (plot.getSelection && plot.getSelection()) { |
||||
crosshair.x = -1; // hide the crosshair while selecting
|
||||
return; |
||||
} |
||||
|
||||
var offset = plot.offset(); |
||||
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); |
||||
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); |
||||
plot.triggerRedrawOverlay(); |
||||
} |
||||
|
||||
plot.hooks.bindEvents.push(function (plot, eventHolder) { |
||||
if (!plot.getOptions().crosshair.mode) |
||||
return; |
||||
|
||||
eventHolder.mouseout(onMouseOut); |
||||
eventHolder.mousemove(onMouseMove); |
||||
}); |
||||
|
||||
plot.hooks.drawOverlay.push(function (plot, ctx) { |
||||
var c = plot.getOptions().crosshair; |
||||
if (!c.mode) |
||||
return; |
||||
|
||||
var plotOffset = plot.getPlotOffset(); |
||||
|
||||
ctx.save(); |
||||
ctx.translate(plotOffset.left, plotOffset.top); |
||||
|
||||
if (crosshair.x != -1) { |
||||
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; |
||||
|
||||
ctx.strokeStyle = c.color; |
||||
ctx.lineWidth = c.lineWidth; |
||||
ctx.lineJoin = "round"; |
||||
|
||||
ctx.beginPath(); |
||||
if (c.mode.indexOf("x") != -1) { |
||||
var drawX = Math.floor(crosshair.x) + adj; |
||||
ctx.moveTo(drawX, 0); |
||||
ctx.lineTo(drawX, plot.height()); |
||||
} |
||||
if (c.mode.indexOf("y") != -1) { |
||||
var drawY = Math.floor(crosshair.y) + adj; |
||||
ctx.moveTo(0, drawY); |
||||
ctx.lineTo(plot.width(), drawY); |
||||
} |
||||
ctx.stroke(); |
||||
} |
||||
ctx.restore(); |
||||
}); |
||||
|
||||
plot.hooks.shutdown.push(function (plot, eventHolder) { |
||||
eventHolder.unbind("mouseout", onMouseOut); |
||||
eventHolder.unbind("mousemove", onMouseMove); |
||||
}); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: 'crosshair', |
||||
version: '1.0' |
||||
}); |
||||
})(jQuery); |
@ -0,0 +1,238 @@
|
||||
/* |
||||
* jQuery.flot.dashes |
||||
* |
||||
* options = { |
||||
* series: { |
||||
* dashes: { |
||||
* |
||||
* // show
|
||||
* // default: false
|
||||
* // Whether to show dashes for the series.
|
||||
* show: <boolean>, |
||||
* |
||||
* // lineWidth
|
||||
* // default: 2
|
||||
* // The width of the dashed line in pixels.
|
||||
* lineWidth: <number>, |
||||
* |
||||
* // dashLength
|
||||
* // default: 10
|
||||
* // Controls the length of the individual dashes and the amount of
|
||||
* // space between them.
|
||||
* // If this is a number, the dashes and spaces will have that length.
|
||||
* // If this is an array, it is read as [ dashLength, spaceLength ]
|
||||
* dashLength: <number> or <array[2]> |
||||
* } |
||||
* } |
||||
* } |
||||
*/ |
||||
( |
||||
// the actual Flot code
|
||||
function($){ |
||||
|
||||
function init(plot) { |
||||
|
||||
plot.hooks.processDatapoints.push(function(plot, series, datapoints) { |
||||
|
||||
if (!series.dashes.show) return; |
||||
|
||||
plot.hooks.draw.push(function(plot, ctx) { |
||||
|
||||
var plotOffset = plot.getPlotOffset(), |
||||
axisx = series.xaxis, |
||||
axisy = series.yaxis; |
||||
|
||||
function plotDashes(xoffset, yoffset) { |
||||
|
||||
var points = datapoints.points, |
||||
ps = datapoints.pointsize, |
||||
prevx = null, |
||||
prevy = null, |
||||
dashRemainder = 0, |
||||
dashOn = true, |
||||
dashOnLength, |
||||
dashOffLength; |
||||
|
||||
if (series.dashes.dashLength[0]) { |
||||
dashOnLength = series.dashes.dashLength[0]; |
||||
if (series.dashes.dashLength[1]) { |
||||
dashOffLength = series.dashes.dashLength[1]; |
||||
} else { |
||||
dashOffLength = dashOnLength; |
||||
} |
||||
} else { |
||||
dashOffLength = dashOnLength = series.dashes.dashLength; |
||||
} |
||||
|
||||
ctx.beginPath(); |
||||
|
||||
for (var i = ps; i < points.length; i += ps) { |
||||
|
||||
var x1 = points[i - ps], |
||||
y1 = points[i - ps + 1], |
||||
x2 = points[i], |
||||
y2 = points[i + 1]; |
||||
|
||||
if (x1 == null || x2 == null) continue; |
||||
|
||||
// clip with ymin
|
||||
if (y1 <= y2 && y1 < axisy.min) { |
||||
if (y2 < axisy.min) continue; // line segment is outside
|
||||
// compute new intersection point
|
||||
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; |
||||
y1 = axisy.min; |
||||
} else if (y2 <= y1 && y2 < axisy.min) { |
||||
if (y1 < axisy.min) continue; |
||||
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; |
||||
y2 = axisy.min; |
||||
} |
||||
|
||||
// clip with ymax
|
||||
if (y1 >= y2 && y1 > axisy.max) { |
||||
if (y2 > axisy.max) continue; |
||||
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; |
||||
y1 = axisy.max; |
||||
} else if (y2 >= y1 && y2 > axisy.max) { |
||||
if (y1 > axisy.max) continue; |
||||
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; |
||||
y2 = axisy.max; |
||||
} |
||||
|
||||
// clip with xmin
|
||||
if (x1 <= x2 && x1 < axisx.min) { |
||||
if (x2 < axisx.min) continue; |
||||
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; |
||||
x1 = axisx.min; |
||||
} else if (x2 <= x1 && x2 < axisx.min) { |
||||
if (x1 < axisx.min) continue; |
||||
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; |
||||
x2 = axisx.min; |
||||
} |
||||
|
||||
// clip with xmax
|
||||
if (x1 >= x2 && x1 > axisx.max) { |
||||
if (x2 > axisx.max) continue; |
||||
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; |
||||
x1 = axisx.max; |
||||
} else if (x2 >= x1 && x2 > axisx.max) { |
||||
if (x1 > axisx.max) continue; |
||||
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; |
||||
x2 = axisx.max; |
||||
} |
||||
|
||||
if (x1 != prevx || y1 != prevy) { |
||||
ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); |
||||
} |
||||
|
||||
var ax1 = axisx.p2c(x1) + xoffset, |
||||
ay1 = axisy.p2c(y1) + yoffset, |
||||
ax2 = axisx.p2c(x2) + xoffset, |
||||
ay2 = axisy.p2c(y2) + yoffset, |
||||
dashOffset; |
||||
|
||||
function lineSegmentOffset(segmentLength) { |
||||
|
||||
var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2)); |
||||
|
||||
if (c <= segmentLength) { |
||||
return { |
||||
deltaX: ax2 - ax1, |
||||
deltaY: ay2 - ay1, |
||||
distance: c, |
||||
remainder: segmentLength - c |
||||
} |
||||
} else { |
||||
var xsign = ax2 > ax1 ? 1 : -1, |
||||
ysign = ay2 > ay1 ? 1 : -1; |
||||
return { |
||||
deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))), |
||||
deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))), |
||||
distance: segmentLength, |
||||
remainder: 0 |
||||
}; |
||||
} |
||||
} |
||||
//-end lineSegmentOffset
|
||||
|
||||
do { |
||||
|
||||
dashOffset = lineSegmentOffset( |
||||
dashRemainder > 0 ? dashRemainder : |
||||
dashOn ? dashOnLength : dashOffLength); |
||||
|
||||
if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) { |
||||
if (dashOn) { |
||||
ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY); |
||||
} else { |
||||
ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY); |
||||
} |
||||
} |
||||
|
||||
dashOn = !dashOn; |
||||
dashRemainder = dashOffset.remainder; |
||||
ax1 += dashOffset.deltaX; |
||||
ay1 += dashOffset.deltaY; |
||||
|
||||
} while (dashOffset.distance > 0); |
||||
|
||||
prevx = x2; |
||||
prevy = y2; |
||||
} |
||||
|
||||
ctx.stroke(); |
||||
} |
||||
//-end plotDashes
|
||||
|
||||
ctx.save(); |
||||
ctx.translate(plotOffset.left, plotOffset.top); |
||||
ctx.lineJoin = 'round'; |
||||
|
||||
var lw = series.dashes.lineWidth, |
||||
sw = series.shadowSize; |
||||
|
||||
// FIXME: consider another form of shadow when filling is turned on
|
||||
if (lw > 0 && sw > 0) { |
||||
// draw shadow as a thick and thin line with transparency
|
||||
ctx.lineWidth = sw; |
||||
ctx.strokeStyle = "rgba(0,0,0,0.1)"; |
||||
// position shadow at angle from the mid of line
|
||||
var angle = Math.PI/18; |
||||
plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2)); |
||||
ctx.lineWidth = sw/2; |
||||
plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4)); |
||||
} |
||||
|
||||
ctx.lineWidth = lw; |
||||
ctx.strokeStyle = series.color; |
||||
|
||||
if (lw > 0) { |
||||
plotDashes(0, 0); |
||||
} |
||||
|
||||
ctx.restore(); |
||||
|
||||
}); |
||||
//-end draw hook
|
||||
|
||||
}); |
||||
//-end processDatapoints hook
|
||||
|
||||
} |
||||
//-end init
|
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: { |
||||
series: { |
||||
dashes: { |
||||
show: false, |
||||
lineWidth: 2, |
||||
dashLength: 10 |
||||
} |
||||
} |
||||
}, |
||||
name: 'dashes', |
||||
version: '0.1' |
||||
}); |
||||
|
||||
})(jQuery); |
@ -0,0 +1,289 @@
|
||||
( |
||||
function($) { |
||||
"use strict"; |
||||
|
||||
var options = { |
||||
series: { |
||||
fillBelowTo: null |
||||
} |
||||
}; |
||||
|
||||
function init(plot) { |
||||
function findBelowSeries( series, allseries ) { |
||||
|
||||
var i; |
||||
|
||||
for ( i = 0; i < allseries.length; ++i ) { |
||||
if ( allseries[ i ].id === series.fillBelowTo ) { |
||||
return allseries[ i ]; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */ |
||||
/* this is a vector cross product operation */ |
||||
function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) { |
||||
var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y, |
||||
s, t; |
||||
|
||||
top_delta_x = top_right_x - top_left_x; |
||||
top_delta_y = top_right_y - top_left_y; |
||||
bottom_delta_x = bottom_right_x - bottom_left_x; |
||||
bottom_delta_y = bottom_right_y - bottom_left_y; |
||||
|
||||
s = ( |
||||
(-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y)) |
||||
) / ( |
||||
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y |
||||
); |
||||
|
||||
t = ( |
||||
(bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x)) |
||||
) / ( |
||||
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y |
||||
); |
||||
|
||||
// Collision detected
|
||||
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { |
||||
return [ |
||||
top_left_x + (t * top_delta_x), // X
|
||||
top_left_y + (t * top_delta_y) // Y
|
||||
]; |
||||
} |
||||
|
||||
// No collision
|
||||
return null; |
||||
} |
||||
|
||||
function plotDifferenceArea(plot, ctx, series) { |
||||
if ( series.fillBelowTo === null ) { |
||||
return; |
||||
} |
||||
|
||||
var otherseries, |
||||
|
||||
ps, |
||||
points, |
||||
|
||||
otherps, |
||||
otherpoints, |
||||
|
||||
plotOffset, |
||||
fillStyle; |
||||
|
||||
function openPolygon(x, y) { |
||||
ctx.beginPath(); |
||||
ctx.moveTo( |
||||
series.xaxis.p2c(x) + plotOffset.left, |
||||
series.yaxis.p2c(y) + plotOffset.top |
||||
); |
||||
|
||||
} |
||||
|
||||
function closePolygon() { |
||||
ctx.closePath(); |
||||
ctx.fill(); |
||||
} |
||||
|
||||
function validateInput() { |
||||
if (points.length/ps !== otherpoints.length/otherps) { |
||||
console.error("Refusing to graph inconsistent number of points"); |
||||
return false; |
||||
} |
||||
|
||||
var i; |
||||
for (i = 0; i < (points.length / ps); i++) { |
||||
if ( |
||||
points[i * ps] !== null && |
||||
otherpoints[i * otherps] !== null && |
||||
points[i * ps] !== otherpoints[i * otherps] |
||||
) { |
||||
console.error("Refusing to graph points without matching value"); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
function findNextStart(start_i, end_i) { |
||||
console.assert(end_i > start_i, "expects the end index to be greater than the start index"); |
||||
|
||||
var start = ( |
||||
start_i === 0 || |
||||
points[start_i - 1] === null || |
||||
otherpoints[start_i - 1] === null |
||||
), |
||||
equal = false, |
||||
i, |
||||
intersect; |
||||
|
||||
for (i = start_i; i < end_i; i++) { |
||||
// Take note of null points
|
||||
if ( |
||||
points[(i * ps) + 1] === null || |
||||
otherpoints[(i * ps) + 1] === null |
||||
) { |
||||
equal = false; |
||||
start = true; |
||||
} |
||||
|
||||
// Take note of equal points
|
||||
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) { |
||||
equal = true; |
||||
start = false; |
||||
} |
||||
|
||||
|
||||
else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) { |
||||
// If we begin above the desired point
|
||||
if (start) { |
||||
openPolygon(points[i * ps], points[(i * ps) + 1]); |
||||
} |
||||
|
||||
// If an equal point preceeds this, start the polygon at that equal point
|
||||
else if (equal) { |
||||
openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]); |
||||
} |
||||
|
||||
// Otherwise, find the intersection point, and start it there
|
||||
else { |
||||
intersect = intersectionPoint(i); |
||||
openPolygon(intersect[0], intersect[1]); |
||||
} |
||||
|
||||
topTraversal(i, end_i); |
||||
return; |
||||
} |
||||
|
||||
// If we go below equal, equal at any preceeding point is irrelevant
|
||||
else { |
||||
start = false; |
||||
equal = false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function intersectionPoint(right_i) { |
||||
console.assert(right_i > 0, "expects the second point in the series line segment"); |
||||
|
||||
var i, intersect; |
||||
|
||||
for (i = 1; i < (otherpoints.length/otherps); i++) { |
||||
intersect = segmentIntersection( |
||||
points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1], |
||||
points[right_i * ps], points[(right_i * ps) + 1], |
||||
|
||||
otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1], |
||||
otherpoints[i * otherps], otherpoints[(i * otherps) + 1] |
||||
); |
||||
|
||||
if (intersect !== null) { |
||||
return intersect; |
||||
} |
||||
} |
||||
|
||||
console.error("intersectionPoint() should only be called when an intersection happens"); |
||||
} |
||||
|
||||
function bottomTraversal(start_i, end_i) { |
||||
console.assert(start_i >= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)"); |
||||
|
||||
var i; |
||||
|
||||
for (i = start_i; i >= end_i; i--) { |
||||
ctx.lineTo( |
||||
otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left, |
||||
otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top |
||||
); |
||||
} |
||||
|
||||
closePolygon(); |
||||
} |
||||
|
||||
function topTraversal(start_i, end_i) { |
||||
console.assert(start_i <= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)"); |
||||
|
||||
var i, |
||||
intersect; |
||||
|
||||
for (i = start_i; i < end_i; i++) { |
||||
if (points[(i * ps) + 1] === null && i > start_i) { |
||||
bottomTraversal(i - 1, start_i); |
||||
findNextStart(i, end_i); |
||||
return; |
||||
} |
||||
|
||||
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) { |
||||
bottomTraversal(i, start_i); |
||||
findNextStart(i, end_i); |
||||
return; |
||||
} |
||||
|
||||
else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) { |
||||
intersect = intersectionPoint(i); |
||||
ctx.lineTo( |
||||
series.xaxis.p2c(intersect[0]) + plotOffset.left, |
||||
series.yaxis.p2c(intersect[1]) + plotOffset.top |
||||
); |
||||
bottomTraversal(i, start_i); |
||||
findNextStart(i, end_i); |
||||
return; |
||||
|
||||
} |
||||
|
||||
else { |
||||
ctx.lineTo( |
||||
series.xaxis.p2c(points[i * ps]) + plotOffset.left, |
||||
series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top |
||||
); |
||||
} |
||||
} |
||||
|
||||
bottomTraversal(end_i, start_i); |
||||
} |
||||
|
||||
|
||||
// Begin processing
|
||||
|
||||
otherseries = findBelowSeries( series, plot.getData() ); |
||||
|
||||
if ( !otherseries ) { |
||||
return; |
||||
} |
||||
|
||||
ps = series.datapoints.pointsize; |
||||
points = series.datapoints.points; |
||||
otherps = otherseries.datapoints.pointsize; |
||||
otherpoints = otherseries.datapoints.points; |
||||
plotOffset = plot.getPlotOffset(); |
||||
|
||||
if (!validateInput()) { |
||||
return; |
||||
} |
||||
|
||||
|
||||
// Flot's getFillStyle() should probably be exposed somewhere
|
||||
fillStyle = $.color.parse(series.color); |
||||
fillStyle.a = 0.4; |
||||
fillStyle.normalize(); |
||||
ctx.fillStyle = fillStyle.toString(); |
||||
|
||||
|
||||
// Begin recursive bi-directional traversal
|
||||
findNextStart(0, points.length/ps); |
||||
} |
||||
|
||||
plot.hooks.drawSeries.push(plotDifferenceArea); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: "fillbelow", |
||||
version: "0.1.0" |
||||
}); |
||||
|
||||
})(jQuery); |
@ -0,0 +1,226 @@
|
||||
/* Flot plugin for computing bottoms for filled line and bar charts. |
||||
|
||||
Copyright (c) 2007-2013 IOLA and Ole Laursen. |
||||
Licensed under the MIT license. |
||||
|
||||
The case: you've got two series that you want to fill the area between. In Flot |
||||
terms, you need to use one as the fill bottom of the other. You can specify the |
||||
bottom of each data point as the third coordinate manually, or you can use this |
||||
plugin to compute it for you. |
||||
|
||||
In order to name the other series, you need to give it an id, like this: |
||||
|
||||
var dataset = [ |
||||
{ data: [ ... ], id: "foo" } , // use default bottom
|
||||
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
|
||||
]; |
||||
|
||||
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }}); |
||||
|
||||
As a convenience, if the id given is a number that doesn't appear as an id in |
||||
the series, it is interpreted as the index in the array instead (so fillBetween: |
||||
0 can also mean the first series). |
||||
|
||||
Internally, the plugin modifies the datapoints in each series. For line series, |
||||
extra data points might be inserted through interpolation. Note that at points |
||||
where the bottom line is not defined (due to a null point or start/end of line), |
||||
the current line will show a gap too. The algorithm comes from the |
||||
jquery.flot.stack.js plugin, possibly some code could be shared. |
||||
|
||||
*/ |
||||
|
||||
( |
||||
function ( $ ) { |
||||
|
||||
var options = { |
||||
series: { |
||||
fillBetween: null // or number
|
||||
} |
||||
}; |
||||
|
||||
function init( plot ) { |
||||
|
||||
function findBottomSeries( s, allseries ) { |
||||
|
||||
var i; |
||||
|
||||
for ( i = 0; i < allseries.length; ++i ) { |
||||
if ( allseries[ i ].id === s.fillBetween ) { |
||||
return allseries[ i ]; |
||||
} |
||||
} |
||||
|
||||
if ( typeof s.fillBetween === "number" ) { |
||||
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) { |
||||
return null; |
||||
} |
||||
return allseries[ s.fillBetween ]; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
function computeFillBottoms( plot, s, datapoints ) { |
||||
if ( s.fillBetween == null ) { |
||||
return; |
||||
} |
||||
|
||||
var other = findBottomSeries( s, plot.getData() ); |
||||
|
||||
if ( !other ) { |
||||
return; |
||||
} |
||||
|
||||
var ps = datapoints.pointsize, |
||||
points = datapoints.points, |
||||
otherps = other.datapoints.pointsize, |
||||
otherpoints = other.datapoints.points, |
||||
newpoints = [], |
||||
px, py, intery, qx, qy, bottom, |
||||
withlines = s.lines.show, |
||||
withbottom = ps > 2 && datapoints.format[2].y, |
||||
withsteps = withlines && s.lines.steps, |
||||
fromgap = true, |
||||
i = 0, |
||||
j = 0, |
||||
l, m; |
||||
|
||||
while ( true ) { |
||||
|
||||
if ( i >= points.length ) { |
||||
break; |
||||
} |
||||
|
||||
l = newpoints.length; |
||||
|
||||
if ( points[ i ] == null ) { |
||||
|
||||
// copy gaps
|
||||
|
||||
for ( m = 0; m < ps; ++m ) { |
||||
newpoints.push( points[ i + m ] ); |
||||
} |
||||
|
||||
i += ps; |
||||
|
||||
} else if ( j >= otherpoints.length ) { |
||||
|
||||
// for lines, we can't use the rest of the points
|
||||
|
||||
if ( !withlines ) { |
||||
for ( m = 0; m < ps; ++m ) { |
||||
newpoints.push( points[ i + m ] ); |
||||
} |
||||
} |
||||
|
||||
i += ps; |
||||
|
||||
} else if ( otherpoints[ j ] == null ) { |
||||
|
||||
// oops, got a gap
|
||||
|
||||
for ( m = 0; m < ps; ++m ) { |
||||
newpoints.push( null ); |
||||
} |
||||
|
||||
fromgap = true; |
||||
j += otherps; |
||||
|
||||
} else { |
||||
|
||||
// cases where we actually got two points
|
||||
|
||||
px = points[ i ]; |
||||
py = points[ i + 1 ]; |
||||
qx = otherpoints[ j ]; |
||||
qy = otherpoints[ j + 1 ]; |
||||
bottom = 0; |
||||
|
||||
if ( px === qx ) { |
||||
|
||||
for ( m = 0; m < ps; ++m ) { |
||||
newpoints.push( points[ i + m ] ); |
||||
} |
||||
|
||||
//newpoints[ l + 1 ] += qy;
|
||||
bottom = qy; |
||||
|
||||
i += ps; |
||||
j += otherps; |
||||
|
||||
} else if ( px > qx ) { |
||||
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
|
||||
if ( withlines && i > 0 && points[ i - ps ] != null ) { |
||||
intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); |
||||
newpoints.push( qx ); |
||||
newpoints.push( intery ); |
||||
for ( m = 2; m < ps; ++m ) { |
||||
newpoints.push( points[ i + m ] ); |
||||
} |
||||
bottom = qy; |
||||
} |
||||
|
||||
j += otherps; |
||||
|
||||
} else { // px < qx
|
||||
|
||||
// if we come from a gap, we just skip this point
|
||||
|
||||
if ( fromgap && withlines ) { |
||||
i += ps; |
||||
continue; |
||||
} |
||||
|
||||
for ( m = 0; m < ps; ++m ) { |
||||
newpoints.push( points[ i + m ] ); |
||||
} |
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
|
||||
if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { |
||||
bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); |
||||
} |
||||
|
||||
//newpoints[l + 1] += bottom;
|
||||
|
||||
i += ps; |
||||
} |
||||
|
||||
fromgap = false; |
||||
|
||||
if ( l !== newpoints.length && withbottom ) { |
||||
newpoints[ l + 2 ] = bottom; |
||||
} |
||||
} |
||||
|
||||
// maintain the line steps invariant
|
||||
|
||||
if ( withsteps && l !== newpoints.length && l > 0 && |
||||
newpoints[ l ] !== null && |
||||
newpoints[ l ] !== newpoints[ l - ps ] && |
||||
newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { |
||||
for (m = 0; m < ps; ++m) { |
||||
newpoints[ l + ps + m ] = newpoints[ l + m ]; |
||||
} |
||||
newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; |
||||
} |
||||
} |
||||
|
||||
datapoints.points = newpoints; |
||||
} |
||||
|
||||
plot.hooks.processDatapoints.push( computeFillBottoms ); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: "fillbetween", |
||||
version: "1.0" |
||||
}); |
||||
|
||||
})(jQuery); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,379 @@
|
||||
/* Flot plugin for selecting regions of a plot. |
||||
|
||||
Copyright (c) 2007-2013 IOLA and Ole Laursen. |
||||
Licensed under the MIT license. |
||||
|
||||
The plugin supports these options: |
||||
|
||||
selection: { |
||||
mode: null or "x" or "y" or "xy", |
||||
color: color, |
||||
shape: "round" or "miter" or "bevel", |
||||
minSize: number of pixels |
||||
} |
||||
|
||||
Selection support is enabled by setting the mode to one of "x", "y" or "xy". |
||||
In "x" mode, the user will only be able to specify the x range, similarly for |
||||
"y" mode. For "xy", the selection becomes a rectangle where both ranges can be |
||||
specified. "color" is color of the selection (if you need to change the color |
||||
later on, you can get to it with plot.getOptions().selection.color). "shape" |
||||
is the shape of the corners of the selection. |
||||
|
||||
"minSize" is the minimum size a selection can be in pixels. This value can |
||||
be customized to determine the smallest size a selection can be and still |
||||
have the selection rectangle be displayed. When customizing this value, the |
||||
fact that it refers to pixels, not axis units must be taken into account. |
||||
Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 |
||||
minute, setting "minSize" to 1 will not make the minimum selection size 1 |
||||
minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent |
||||
"plotunselected" events from being fired when the user clicks the mouse without |
||||
dragging. |
||||
|
||||
When selection support is enabled, a "plotselected" event will be emitted on |
||||
the DOM element you passed into the plot function. The event handler gets a |
||||
parameter with the ranges selected on the axes, like this: |
||||
|
||||
placeholder.bind( "plotselected", function( event, ranges ) { |
||||
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) |
||||
// similar for yaxis - with multiple axes, the extra ones are in
|
||||
// x2axis, x3axis, ...
|
||||
}); |
||||
|
||||
The "plotselected" event is only fired when the user has finished making the |
||||
selection. A "plotselecting" event is fired during the process with the same |
||||
parameters as the "plotselected" event, in case you want to know what's |
||||
happening while it's happening, |
||||
|
||||
A "plotunselected" event with no arguments is emitted when the user clicks the |
||||
mouse to remove the selection. As stated above, setting "minSize" to 0 will |
||||
destroy this behavior. |
||||
|
||||
The plugin allso adds the following methods to the plot object: |
||||
|
||||
- setSelection( ranges, preventEvent ) |
||||
|
||||
Set the selection rectangle. The passed in ranges is on the same form as |
||||
returned in the "plotselected" event. If the selection mode is "x", you |
||||
should put in either an xaxis range, if the mode is "y" you need to put in |
||||
an yaxis range and both xaxis and yaxis if the selection mode is "xy", like |
||||
this: |
||||
|
||||
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); |
||||
|
||||
setSelection will trigger the "plotselected" event when called. If you don't |
||||
want that to happen, e.g. if you're inside a "plotselected" handler, pass |
||||
true as the second parameter. If you are using multiple axes, you can |
||||
specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of |
||||
xaxis, the plugin picks the first one it sees. |
||||
|
||||
- clearSelection( preventEvent ) |
||||
|
||||
Clear the selection rectangle. Pass in true to avoid getting a |
||||
"plotunselected" event. |
||||
|
||||
- getSelection() |
||||
|
||||
Returns the current selection in the same format as the "plotselected" |
||||
event. If there's currently no selection, the function returns null. |
||||
|
||||
*/ |
||||
|
||||
( |
||||
function ($) { |
||||
function init(plot) { |
||||
var selection = { |
||||
first: { x: -1, y: -1}, second: { x: -1, y: -1}, |
||||
show: false, |
||||
active: false |
||||
}; |
||||
|
||||
// FIXME: The drag handling implemented here should be
|
||||
// abstracted out, there's some similar code from a library in
|
||||
// the navigation plugin, this should be massaged a bit to fit
|
||||
// the Flot cases here better and reused. Doing this would
|
||||
// make this plugin much slimmer.
|
||||
var savedhandlers = {}; |
||||
|
||||
var mouseUpHandler = null; |
||||
|
||||
function onMouseMove(e) { |
||||
if (selection.active) { |
||||
updateSelection(e); |
||||
|
||||
plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); |
||||
} |
||||
} |
||||
|
||||
function onMouseDown(e) { |
||||
if (e.which != 1) // only accept left-click
|
||||
return; |
||||
|
||||
// cancel out any text selections
|
||||
document.body.focus(); |
||||
|
||||
// prevent text selection and drag in old-school browsers
|
||||
if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { |
||||
savedhandlers.onselectstart = document.onselectstart; |
||||
document.onselectstart = function () { return false; }; |
||||
} |
||||
if (document.ondrag !== undefined && savedhandlers.ondrag == null) { |
||||
savedhandlers.ondrag = document.ondrag; |
||||
document.ondrag = function () { return false; }; |
||||
} |
||||
|
||||
setSelectionPos(selection.first, e); |
||||
|
||||
selection.active = true; |
||||
|
||||
// this is a bit silly, but we have to use a closure to be
|
||||
// able to whack the same handler again
|
||||
mouseUpHandler = function (e) { onMouseUp(e); }; |
||||
|
||||
$(document).one("mouseup", mouseUpHandler); |
||||
} |
||||
|
||||
function onMouseUp(e) { |
||||
mouseUpHandler = null; |
||||
|
||||
// revert drag stuff for old-school browsers
|
||||
if (document.onselectstart !== undefined) |
||||
document.onselectstart = savedhandlers.onselectstart; |
||||
if (document.ondrag !== undefined) |
||||
document.ondrag = savedhandlers.ondrag; |
||||
|
||||
// no more dragging
|
||||
selection.active = false; |
||||
updateSelection(e); |
||||
|
||||
if (selectionIsSane()) |
||||
triggerSelectedEvent(e); |
||||
else { |
||||
// this counts as a clear
|
||||
plot.getPlaceholder().trigger("plotunselected", [ ]); |
||||
plot.getPlaceholder().trigger("plotselecting", [ null ]); |
||||
} |
||||
|
||||
setTimeout(function() { |
||||
plot.isSelecting = false; |
||||
}, 10); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function getSelection() { |
||||
if (!selectionIsSane()) |
||||
return null; |
||||
|
||||
if (!selection.show) return null; |
||||
|
||||
var r = {}, c1 = selection.first, c2 = selection.second; |
||||
var axes = plot.getAxes(); |
||||
// look if no axis is used
|
||||
var noAxisInUse = true; |
||||
$.each(axes, function (name, axis) { |
||||
if (axis.used) { |
||||
anyUsed = false; |
||||
} |
||||
}) |
||||
|
||||
$.each(axes, function (name, axis) { |
||||
if (axis.used || noAxisInUse) { |
||||
var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); |
||||
r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; |
||||
} |
||||
}); |
||||
return r; |
||||
} |
||||
|
||||
function triggerSelectedEvent(event) { |
||||
var r = getSelection(); |
||||
|
||||
// Add ctrlKey and metaKey to event
|
||||
r.ctrlKey = event.ctrlKey; |
||||
r.metaKey = event.metaKey; |
||||
|
||||
plot.getPlaceholder().trigger("plotselected", [ r ]); |
||||
|
||||
// backwards-compat stuff, to be removed in future
|
||||
if (r.xaxis && r.yaxis) |
||||
plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); |
||||
} |
||||
|
||||
function clamp(min, value, max) { |
||||
return value < min ? min: (value > max ? max: value); |
||||
} |
||||
|
||||
function setSelectionPos(pos, e) { |
||||
var o = plot.getOptions(); |
||||
var offset = plot.getPlaceholder().offset(); |
||||
var plotOffset = plot.getPlotOffset(); |
||||
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); |
||||
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); |
||||
|
||||
if (o.selection.mode == "y") |
||||
pos.x = pos == selection.first ? 0 : plot.width(); |
||||
|
||||
if (o.selection.mode == "x") |
||||
pos.y = pos == selection.first ? 0 : plot.height(); |
||||
} |
||||
|
||||
function updateSelection(pos) { |
||||
if (pos.pageX == null) |
||||
return; |
||||
|
||||
setSelectionPos(selection.second, pos); |
||||
if (selectionIsSane()) { |
||||
plot.isSelecting = true; |
||||
selection.show = true; |
||||
plot.triggerRedrawOverlay(); |
||||
} |
||||
else |
||||
clearSelection(true); |
||||
} |
||||
|
||||
function clearSelection(preventEvent) { |
||||
if (selection.show) { |
||||
selection.show = false; |
||||
plot.triggerRedrawOverlay(); |
||||
if (!preventEvent) |
||||
plot.getPlaceholder().trigger("plotunselected", [ ]); |
||||
} |
||||
} |
||||
|
||||
// function taken from markings support in Flot
|
||||
function extractRange(ranges, coord) { |
||||
var axis, from, to, key, axes = plot.getAxes(); |
||||
|
||||
for (var k in axes) { |
||||
axis = axes[k]; |
||||
if (axis.direction == coord) { |
||||
key = coord + axis.n + "axis"; |
||||
if (!ranges[key] && axis.n == 1) |
||||
key = coord + "axis"; // support x1axis as xaxis
|
||||
if (ranges[key]) { |
||||
from = ranges[key].from; |
||||
to = ranges[key].to; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// backwards-compat stuff - to be removed in future
|
||||
if (!ranges[key]) { |
||||
axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; |
||||
from = ranges[coord + "1"]; |
||||
to = ranges[coord + "2"]; |
||||
} |
||||
|
||||
// auto-reverse as an added bonus
|
||||
if (from != null && to != null && from > to) { |
||||
var tmp = from; |
||||
from = to; |
||||
to = tmp; |
||||
} |
||||
|
||||
return { from: from, to: to, axis: axis }; |
||||
} |
||||
|
||||
function setSelection(ranges, preventEvent) { |
||||
var axis, range, o = plot.getOptions(); |
||||
|
||||
if (o.selection.mode == "y") { |
||||
selection.first.x = 0; |
||||
selection.second.x = plot.width(); |
||||
} |
||||
else { |
||||
range = extractRange(ranges, "x"); |
||||
|
||||
selection.first.x = range.axis.p2c(range.from); |
||||
selection.second.x = range.axis.p2c(range.to); |
||||
} |
||||
|
||||
if (o.selection.mode == "x") { |
||||
selection.first.y = 0; |
||||
selection.second.y = plot.height(); |
||||
} |
||||
else { |
||||
range = extractRange(ranges, "y"); |
||||
|
||||
selection.first.y = range.axis.p2c(range.from); |
||||
selection.second.y = range.axis.p2c(range.to); |
||||
} |
||||
|
||||
selection.show = true; |
||||
plot.triggerRedrawOverlay(); |
||||
if (!preventEvent && selectionIsSane()) |
||||
triggerSelectedEvent(); |
||||
} |
||||
|
||||
function selectionIsSane() { |
||||
var minSize = plot.getOptions().selection.minSize; |
||||
return Math.abs(selection.second.x - selection.first.x) >= minSize && |
||||
Math.abs(selection.second.y - selection.first.y) >= minSize; |
||||
} |
||||
|
||||
plot.clearSelection = clearSelection; |
||||
plot.setSelection = setSelection; |
||||
plot.getSelection = getSelection; |
||||
|
||||
plot.hooks.bindEvents.push(function(plot, eventHolder) { |
||||
var o = plot.getOptions(); |
||||
if (o.selection.mode != null) { |
||||
eventHolder.mousemove(onMouseMove); |
||||
eventHolder.mousedown(onMouseDown); |
||||
} |
||||
}); |
||||
|
||||
|
||||
plot.hooks.drawOverlay.push(function (plot, ctx) { |
||||
// draw selection
|
||||
if (selection.show && selectionIsSane()) { |
||||
var plotOffset = plot.getPlotOffset(); |
||||
var o = plot.getOptions(); |
||||
|
||||
ctx.save(); |
||||
ctx.translate(plotOffset.left, plotOffset.top); |
||||
|
||||
var c = $.color.parse(o.selection.color); |
||||
|
||||
ctx.strokeStyle = c.scale('a', 0.8).toString(); |
||||
ctx.lineWidth = 1; |
||||
ctx.lineJoin = o.selection.shape; |
||||
ctx.fillStyle = c.scale('a', 0.4).toString(); |
||||
|
||||
var x = Math.min(selection.first.x, selection.second.x) + 0.5, |
||||
y = Math.min(selection.first.y, selection.second.y) + 0.5, |
||||
w = Math.abs(selection.second.x - selection.first.x) - 1, |
||||
h = Math.abs(selection.second.y - selection.first.y) - 1; |
||||
|
||||
ctx.fillRect(x, y, w, h); |
||||
ctx.strokeRect(x, y, w, h); |
||||
|
||||
ctx.restore(); |
||||
} |
||||
}); |
||||
|
||||
plot.hooks.shutdown.push(function (plot, eventHolder) { |
||||
eventHolder.unbind("mousemove", onMouseMove); |
||||
eventHolder.unbind("mousedown", onMouseDown); |
||||
|
||||
if (mouseUpHandler) |
||||
$(document).unbind("mouseup", mouseUpHandler); |
||||
}); |
||||
|
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: { |
||||
selection: { |
||||
mode: null, // one of null, "x", "y" or "xy"
|
||||
color: "#e8cfac", |
||||
shape: "round", // one of "round", "miter", or "bevel"
|
||||
minSize: 5 // minimum number of pixels
|
||||
} |
||||
}, |
||||
name: 'selection', |
||||
version: '1.1' |
||||
}); |
||||
})(jQuery); |
@ -0,0 +1,190 @@
|
||||
/* Flot plugin for stacking data sets rather than overlyaing them. |
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
Licensed under the MIT license. |
||||
|
||||
The plugin assumes the data is sorted on x (or y if stacking horizontally). |
||||
For line charts, it is assumed that if a line has an undefined gap (from a |
||||
null point), then the line above it should have the same gap - insert zeros |
||||
instead of "null" if you want another behaviour. This also holds for the start |
||||
and end of the chart. Note that stacking a mix of positive and negative values |
||||
in most instances doesn't make sense (so it looks weird). |
||||
|
||||
Two or more series are stacked when their "stack" attribute is set to the same |
||||
key (which can be any number or string or just "true"). To specify the default |
||||
stack, you can set the stack option like this: |
||||
|
||||
series: { |
||||
stack: null/false, true, or a key (number/string) |
||||
} |
||||
|
||||
You can also specify it for a single series, like this: |
||||
|
||||
$.plot( $("#placeholder"), [{ |
||||
data: [ ... ], |
||||
stack: true |
||||
}]) |
||||
|
||||
The stacking order is determined by the order of the data series in the array |
||||
(later series end up on top of the previous). |
||||
|
||||
Internally, the plugin modifies the datapoints in each series, adding an |
||||
offset to the y value. For line series, extra data points are inserted through |
||||
interpolation. If there's a second y value, it's also adjusted (e.g for bar |
||||
charts or filled areas). |
||||
|
||||
*/ |
||||
( |
||||
function ($) { |
||||
var options = { |
||||
series: { stack: null } // or number/string
|
||||
}; |
||||
|
||||
function init(plot) { |
||||
function findMatchingSeries(s, allseries) { |
||||
var res = null; |
||||
for (var i = 0; i < allseries.length; ++i) { |
||||
if (s == allseries[i]) |
||||
break; |
||||
|
||||
if (allseries[i].stack == s.stack) |
||||
res = allseries[i]; |
||||
} |
||||
|
||||
return res; |
||||
} |
||||
|
||||
function stackData(plot, s, datapoints) { |
||||
if (s.stack == null || s.stack === false) |
||||
return; |
||||
|
||||
var other = findMatchingSeries(s, plot.getData()); |
||||
if (!other) |
||||
return; |
||||
|
||||
var ps = datapoints.pointsize, |
||||
points = datapoints.points, |
||||
otherps = other.datapoints.pointsize, |
||||
otherpoints = other.datapoints.points, |
||||
newpoints = [], |
||||
px, py, intery, qx, qy, bottom, |
||||
withlines = s.lines.show, |
||||
horizontal = s.bars.horizontal, |
||||
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), |
||||
withsteps = withlines && s.lines.steps, |
||||
keyOffset = horizontal ? 1 : 0, |
||||
accumulateOffset = horizontal ? 0 : 1, |
||||
i = 0, j = 0, l, m; |
||||
|
||||
while (true) { |
||||
if (i >= points.length && j >= otherpoints.length) |
||||
break; |
||||
|
||||
l = newpoints.length; |
||||
|
||||
if (i < points.length && points[i] == null) { |
||||
// copy gaps
|
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(points[i + m]); |
||||
i += ps; |
||||
} |
||||
else if (i >= points.length) { |
||||
// take the remaining points from the previous series
|
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(otherpoints[j + m]); |
||||
if (withbottom) |
||||
newpoints[l + 2] = otherpoints[j + accumulateOffset]; |
||||
j += otherps; |
||||
} |
||||
else if (j >= otherpoints.length) { |
||||
// take the remaining points from the current series
|
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(points[i + m]); |
||||
i += ps; |
||||
} |
||||
else if (j < otherpoints.length && otherpoints[j] == null) { |
||||
// ignore point
|
||||
j += otherps; |
||||
} |
||||
else { |
||||
// cases where we actually got two points
|
||||
px = points[i + keyOffset]; |
||||
py = points[i + accumulateOffset]; |
||||
qx = otherpoints[j + keyOffset]; |
||||
qy = otherpoints[j + accumulateOffset]; |
||||
bottom = 0; |
||||
|
||||
if (px == qx) { |
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(points[i + m]); |
||||
|
||||
newpoints[l + accumulateOffset] += qy; |
||||
bottom = qy; |
||||
|
||||
i += ps; |
||||
j += otherps; |
||||
} |
||||
else if (px > qx) { |
||||
// take the point from the previous series so that next series will correctly stack
|
||||
if (i == 0) { |
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(otherpoints[j + m]); |
||||
bottom = qy; |
||||
} |
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
if (i > 0 && points[i - ps] != null) { |
||||
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); |
||||
newpoints.push(qx); |
||||
newpoints.push(intery + qy); |
||||
for (m = 2; m < ps; ++m) |
||||
newpoints.push(points[i + m]); |
||||
bottom = qy; |
||||
} |
||||
|
||||
j += otherps; |
||||
} |
||||
else { // px < qx
|
||||
for (m = 0; m < ps; ++m) |
||||
newpoints.push(points[i + m]); |
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
if (j > 0 && otherpoints[j - otherps] != null) |
||||
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); |
||||
|
||||
newpoints[l + accumulateOffset] += bottom; |
||||
|
||||
i += ps; |
||||
} |
||||
|
||||
fromgap = false; |
||||
|
||||
if (l != newpoints.length && withbottom) |
||||
newpoints[l + 2] = bottom; |
||||
} |
||||
|
||||
// maintain the line steps invariant
|
||||
if (withsteps && l != newpoints.length && l > 0 |
||||
&& newpoints[l] != null |
||||
&& newpoints[l] != newpoints[l - ps] |
||||
&& newpoints[l + 1] != newpoints[l - ps + 1]) { |
||||
for (m = 0; m < ps; ++m) |
||||
newpoints[l + ps + m] = newpoints[l + m]; |
||||
newpoints[l + 1] = newpoints[l - ps + 1]; |
||||
} |
||||
} |
||||
|
||||
datapoints.points = newpoints; |
||||
} |
||||
|
||||
plot.hooks.processDatapoints.push(stackData); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: 'stack', |
||||
version: '1.2' |
||||
}); |
||||
})(jQuery); |
@ -0,0 +1,127 @@
|
||||
( |
||||
function ($) { |
||||
var options = { |
||||
series: { |
||||
stackpercent: null |
||||
} // or number/string
|
||||
}; |
||||
|
||||
function init(plot) { |
||||
|
||||
// will be built up dynamically as a hash from x-value, or y-value if horizontal
|
||||
var stackBases = {}; |
||||
var processed = false; |
||||
var stackSums = {}; |
||||
|
||||
//set percentage for stacked chart
|
||||
function processRawData(plot, series, data, datapoints) { |
||||
if (!processed) { |
||||
processed = true; |
||||
stackSums = getStackSums(plot.getData()); |
||||
} |
||||
if (series.stackpercent == true) { |
||||
var num = data.length; |
||||
series.percents = []; |
||||
var key_idx = 0; |
||||
var value_idx = 1; |
||||
if (series.bars && series.bars.horizontal && series.bars.horizontal === true) { |
||||
key_idx = 1; |
||||
value_idx = 0; |
||||
} |
||||
for (var j = 0; j < num; j++) { |
||||
var sum = stackSums[data[j][key_idx] + ""]; |
||||
if (sum > 0) { |
||||
series.percents.push(data[j][value_idx] * 100 / sum); |
||||
} else { |
||||
series.percents.push(0); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
//calculate summary
|
||||
function getStackSums(_data) { |
||||
var data_len = _data.length; |
||||
var sums = {}; |
||||
if (data_len > 0) { |
||||
//caculate summary
|
||||
for (var i = 0; i < data_len; i++) { |
||||
if (_data[i].stackpercent) { |
||||
var key_idx = 0; |
||||
var value_idx = 1; |
||||
if (_data[i].bars && _data[i].bars.horizontal && _data[i].bars.horizontal === true) { |
||||
key_idx = 1; |
||||
value_idx = 0; |
||||
} |
||||
var num = _data[i].data.length; |
||||
for (var j = 0; j < num; j++) { |
||||
var value = 0; |
||||
if (_data[i].data[j][1] != null) { |
||||
value = _data[i].data[j][value_idx]; |
||||
} |
||||
if (sums[_data[i].data[j][key_idx] + ""]) { |
||||
sums[_data[i].data[j][key_idx] + ""] += value; |
||||
} else { |
||||
sums[_data[i].data[j][key_idx] + ""] = value; |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
} |
||||
return sums; |
||||
} |
||||
|
||||
function stackData(plot, s, datapoints) { |
||||
if (!s.stackpercent) return; |
||||
if (!processed) { |
||||
stackSums = getStackSums(plot.getData()); |
||||
} |
||||
var newPoints = []; |
||||
|
||||
|
||||
var key_idx = 0; |
||||
var value_idx = 1; |
||||
if (s.bars && s.bars.horizontal && s.bars.horizontal === true) { |
||||
key_idx = 1; |
||||
value_idx = 0; |
||||
} |
||||
|
||||
for (var i = 0; i < datapoints.points.length; i += 3) { |
||||
// note that the values need to be turned into absolute y-values.
|
||||
// in other words, if you were to stack (x, y1), (x, y2), and (x, y3),
|
||||
// (each from different series, which is where stackBases comes in),
|
||||
// you'd want the new points to be (x, y1, 0), (x, y1+y2, y1), (x, y1+y2+y3, y1+y2)
|
||||
// generally, (x, thisValue + (base up to this point), + (base up to this point))
|
||||
if (!stackBases[datapoints.points[i + key_idx]]) { |
||||
stackBases[datapoints.points[i + key_idx]] = 0; |
||||
} |
||||
newPoints[i + key_idx] = datapoints.points[i + key_idx]; |
||||
newPoints[i + value_idx] = datapoints.points[i + value_idx] + stackBases[datapoints.points[i + key_idx]]; |
||||
newPoints[i + 2] = stackBases[datapoints.points[i + key_idx]]; |
||||
stackBases[datapoints.points[i + key_idx]] += datapoints.points[i + value_idx]; |
||||
// change points to percentage values
|
||||
// you may need to set yaxis:{ max = 100 }
|
||||
if ( stackSums[newPoints[i+key_idx]+""] > 0 ){ |
||||
newPoints[i + value_idx] = newPoints[i + value_idx] * 100 / stackSums[newPoints[i + key_idx] + ""]; |
||||
newPoints[i + 2] = newPoints[i + 2] * 100 / stackSums[newPoints[i + key_idx] + ""]; |
||||
} else { |
||||
newPoints[i + value_idx] = 0; |
||||
newPoints[i + 2] = 0; |
||||
} |
||||
} |
||||
|
||||
datapoints.points = newPoints; |
||||
} |
||||
|
||||
plot.hooks.processRawData.push(processRawData); |
||||
plot.hooks.processDatapoints.push(stackData); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: 'stackpercent', |
||||
version: '0.1' |
||||
}); |
||||
})(jQuery); |
@ -0,0 +1,432 @@
|
||||
/* Pretty handling of time axes. |
||||
|
||||
Copyright (c) 2007-2013 IOLA and Ole Laursen. |
||||
Licensed under the MIT license. |
||||
|
||||
Set axis.mode to "time" to enable. See the section "Time series data" in |
||||
API.txt for details. |
||||
|
||||
*/ |
||||
|
||||
( |
||||
function($) { |
||||
|
||||
var options = { |
||||
xaxis: { |
||||
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||
timeformat: null, // format string to use
|
||||
twelveHourClock: false, // 12 or 24 time in time mode
|
||||
monthNames: null // list of names of months
|
||||
} |
||||
}; |
||||
|
||||
// round to nearby lower multiple of base
|
||||
|
||||
function floorInBase(n, base) { |
||||
return base * Math.floor(n / base); |
||||
} |
||||
|
||||
// Returns a string with the date d formatted according to fmt.
|
||||
// A subset of the Open Group's strftime format is supported.
|
||||
|
||||
function formatDate(d, fmt, monthNames, dayNames) { |
||||
|
||||
if (typeof d.strftime == "function") { |
||||
return d.strftime(fmt); |
||||
} |
||||
|
||||
var leftPad = function(n, pad) { |
||||
n = "" + n; |
||||
pad = "" + (pad == null ? "0" : pad); |
||||
return n.length == 1 ? pad + n : n; |
||||
}; |
||||
|
||||
var r = []; |
||||
var escape = false; |
||||
var hours = d.getHours(); |
||||
var isAM = hours < 12; |
||||
|
||||
if (monthNames == null) { |
||||
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; |
||||
} |
||||
|
||||
if (dayNames == null) { |
||||
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; |
||||
} |
||||
|
||||
var hours12; |
||||
|
||||
if (hours > 12) { |
||||
hours12 = hours - 12; |
||||
} else if (hours == 0) { |
||||
hours12 = 12; |
||||
} else { |
||||
hours12 = hours; |
||||
} |
||||
|
||||
for (var i = 0; i < fmt.length; ++i) { |
||||
|
||||
var c = fmt.charAt(i); |
||||
|
||||
if (escape) { |
||||
switch (c) { |
||||
case 'a': c = "" + dayNames[d.getDay()]; break; |
||||
case 'b': c = "" + monthNames[d.getMonth()]; break; |
||||
case 'd': c = leftPad(d.getDate(), ""); break; |
||||
case 'e': c = leftPad(d.getDate(), " "); break; |
||||
case 'h': // For back-compat with 0.7; remove in 1.0
|
||||
case 'H': c = leftPad(hours); break; |
||||
case 'I': c = leftPad(hours12); break; |
||||
case 'l': c = leftPad(hours12, " "); break; |
||||
case 'm': c = leftPad(d.getMonth() + 1, ""); break; |
||||
case 'M': c = leftPad(d.getMinutes()); break; |
||||
// quarters not in Open Group's strftime specification
|
||||
case 'q': |
||||
c = "" + (Math.floor(d.getMonth() / 3) + 1); break; |
||||
case 'S': c = leftPad(d.getSeconds()); break; |
||||
case 'y': c = leftPad(d.getFullYear() % 100); break; |
||||
case 'Y': c = "" + d.getFullYear(); break; |
||||
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; |
||||
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; |
||||
case 'w': c = "" + d.getDay(); break; |
||||
} |
||||
r.push(c); |
||||
escape = false; |
||||
} else { |
||||
if (c == "%") { |
||||
escape = true; |
||||
} else { |
||||
r.push(c); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return r.join(""); |
||||
} |
||||
|
||||
// To have a consistent view of time-based data independent of which time
|
||||
// zone the client happens to be in we need a date-like object independent
|
||||
// of time zones. This is done through a wrapper that only calls the UTC
|
||||
// versions of the accessor methods.
|
||||
|
||||
function makeUtcWrapper(d) { |
||||
|
||||
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { |
||||
sourceObj[sourceMethod] = function() { |
||||
return targetObj[targetMethod].apply(targetObj, arguments); |
||||
}; |
||||
}; |
||||
|
||||
var utc = { |
||||
date: d |
||||
}; |
||||
|
||||
// support strftime, if found
|
||||
|
||||
if (d.strftime != undefined) { |
||||
addProxyMethod(utc, "strftime", d, "strftime"); |
||||
} |
||||
|
||||
addProxyMethod(utc, "getTime", d, "getTime"); |
||||
addProxyMethod(utc, "setTime", d, "setTime"); |
||||
|
||||
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; |
||||
|
||||
for (var p = 0; p < props.length; p++) { |
||||
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); |
||||
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); |
||||
} |
||||
|
||||
return utc; |
||||
}; |
||||
|
||||
// select time zone strategy. This returns a date-like object tied to the
|
||||
// desired timezone
|
||||
|
||||
function dateGenerator(ts, opts) { |
||||
if (opts.timezone == "browser") { |
||||
return new Date(ts); |
||||
} else if (!opts.timezone || opts.timezone == "utc") { |
||||
return makeUtcWrapper(new Date(ts)); |
||||
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { |
||||
var d = new timezoneJS.Date(); |
||||
// timezone-js is fickle, so be sure to set the time zone before
|
||||
// setting the time.
|
||||
d.setTimezone(opts.timezone); |
||||
d.setTime(ts); |
||||
return d; |
||||
} else { |
||||
return makeUtcWrapper(new Date(ts)); |
||||
} |
||||
} |
||||
|
||||
// map of app. size of time units in milliseconds
|
||||
|
||||
var timeUnitSize = { |
||||
"second": 1000, |
||||
"minute": 60 * 1000, |
||||
"hour": 60 * 60 * 1000, |
||||
"day": 24 * 60 * 60 * 1000, |
||||
"month": 30 * 24 * 60 * 60 * 1000, |
||||
"quarter": 3 * 30 * 24 * 60 * 60 * 1000, |
||||
"year": 365.2425 * 24 * 60 * 60 * 1000 |
||||
}; |
||||
|
||||
// the allowed tick sizes, after 1 year we use
|
||||
// an integer algorithm
|
||||
|
||||
var baseSpec = [ |
||||
[1, "second"], [2, "second"], [5, "second"], [10, "second"], |
||||
[30, "second"], |
||||
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], |
||||
[30, "minute"], |
||||
[1, "hour"], [2, "hour"], [4, "hour"], |
||||
[8, "hour"], [12, "hour"], |
||||
[1, "day"], [2, "day"], [3, "day"], |
||||
[0.25, "month"], [0.5, "month"], [1, "month"], |
||||
[2, "month"] |
||||
]; |
||||
|
||||
// we don't know which variant(s) we'll need yet, but generating both is
|
||||
// cheap
|
||||
|
||||
var specMonths = baseSpec.concat([[3, "month"], [6, "month"], |
||||
[1, "year"]]); |
||||
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], |
||||
[1, "year"]]); |
||||
|
||||
function init(plot) { |
||||
plot.hooks.processOptions.push(function (plot, options) { |
||||
$.each(plot.getAxes(), function(axisName, axis) { |
||||
|
||||
var opts = axis.options; |
||||
|
||||
if (opts.mode == "time") { |
||||
axis.tickGenerator = function(axis) { |
||||
|
||||
var ticks = []; |
||||
var d = dateGenerator(axis.min, opts); |
||||
var minSize = 0; |
||||
|
||||
// make quarter use a possibility if quarters are
|
||||
// mentioned in either of these options
|
||||
|
||||
var spec = (opts.tickSize && opts.tickSize[1] === |
||||
"quarter") || |
||||
(opts.minTickSize && opts.minTickSize[1] === |
||||
"quarter") ? specQuarters : specMonths; |
||||
|
||||
if (opts.minTickSize != null) { |
||||
if (typeof opts.tickSize == "number") { |
||||
minSize = opts.tickSize; |
||||
} else { |
||||
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; |
||||
} |
||||
} |
||||
|
||||
for (var i = 0; i < spec.length - 1; ++i) { |
||||
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] |
||||
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 |
||||
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
var size = spec[i][0]; |
||||
var unit = spec[i][1]; |
||||
|
||||
// special-case the possibility of several years
|
||||
|
||||
if (unit == "year") { |
||||
|
||||
// if given a minTickSize in years, just use it,
|
||||
// ensuring that it's an integer
|
||||
|
||||
if (opts.minTickSize != null && opts.minTickSize[1] == "year") { |
||||
size = Math.floor(opts.minTickSize[0]); |
||||
} else { |
||||
|
||||
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); |
||||
var norm = (axis.delta / timeUnitSize.year) / magn; |
||||
|
||||
if (norm < 1.5) { |
||||
size = 1; |
||||
} else if (norm < 3) { |
||||
size = 2; |
||||
} else if (norm < 7.5) { |
||||
size = 5; |
||||
} else { |
||||
size = 10; |
||||
} |
||||
|
||||
size *= magn; |
||||
} |
||||
|
||||
// minimum size for years is 1
|
||||
|
||||
if (size < 1) { |
||||
size = 1; |
||||
} |
||||
} |
||||
|
||||
axis.tickSize = opts.tickSize || [size, unit]; |
||||
var tickSize = axis.tickSize[0]; |
||||
unit = axis.tickSize[1]; |
||||
|
||||
var step = tickSize * timeUnitSize[unit]; |
||||
|
||||
if (unit == "second") { |
||||
d.setSeconds(floorInBase(d.getSeconds(), tickSize)); |
||||
} else if (unit == "minute") { |
||||
d.setMinutes(floorInBase(d.getMinutes(), tickSize)); |
||||
} else if (unit == "hour") { |
||||
d.setHours(floorInBase(d.getHours(), tickSize)); |
||||
} else if (unit == "month") { |
||||
d.setMonth(floorInBase(d.getMonth(), tickSize)); |
||||
} else if (unit == "quarter") { |
||||
d.setMonth(3 * floorInBase(d.getMonth() / 3, |
||||
tickSize)); |
||||
} else if (unit == "year") { |
||||
d.setFullYear(floorInBase(d.getFullYear(), tickSize)); |
||||
} |
||||
|
||||
// reset smaller components
|
||||
|
||||
d.setMilliseconds(0); |
||||
|
||||
if (step >= timeUnitSize.minute) { |
||||
d.setSeconds(0); |
||||
} |
||||
if (step >= timeUnitSize.hour) { |
||||
d.setMinutes(0); |
||||
} |
||||
if (step >= timeUnitSize.day) { |
||||
d.setHours(0); |
||||
} |
||||
if (step >= timeUnitSize.day * 4) { |
||||
d.setDate(1); |
||||
} |
||||
if (step >= timeUnitSize.month * 2) { |
||||
d.setMonth(floorInBase(d.getMonth(), 3)); |
||||
} |
||||
if (step >= timeUnitSize.quarter * 2) { |
||||
d.setMonth(floorInBase(d.getMonth(), 6)); |
||||
} |
||||
if (step >= timeUnitSize.year) { |
||||
d.setMonth(0); |
||||
} |
||||
|
||||
var carry = 0; |
||||
var v = Number.NaN; |
||||
var prev; |
||||
|
||||
do { |
||||
|
||||
prev = v; |
||||
v = d.getTime(); |
||||
ticks.push(v); |
||||
|
||||
if (unit == "month" || unit == "quarter") { |
||||
if (tickSize < 1) { |
||||
|
||||
// a bit complicated - we'll divide the
|
||||
// month/quarter up but we need to take
|
||||
// care of fractions so we don't end up in
|
||||
// the middle of a day
|
||||
|
||||
d.setDate(1); |
||||
var start = d.getTime(); |
||||
d.setMonth(d.getMonth() + |
||||
(unit == "quarter" ? 3 : 1)); |
||||
var end = d.getTime(); |
||||
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); |
||||
carry = d.getHours(); |
||||
d.setHours(0); |
||||
} else { |
||||
d.setMonth(d.getMonth() + |
||||
tickSize * (unit == "quarter" ? 3 : 1)); |
||||
} |
||||
} else if (unit == "year") { |
||||
d.setFullYear(d.getFullYear() + tickSize); |
||||
} else { |
||||
d.setTime(v + step); |
||||
} |
||||
} while (v < axis.max && v != prev); |
||||
|
||||
return ticks; |
||||
}; |
||||
|
||||
axis.tickFormatter = function (v, axis) { |
||||
|
||||
var d = dateGenerator(v, axis.options); |
||||
|
||||
// first check global format
|
||||
|
||||
if (opts.timeformat != null) { |
||||
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); |
||||
} |
||||
|
||||
// possibly use quarters if quarters are mentioned in
|
||||
// any of these places
|
||||
|
||||
var useQuarters = (axis.options.tickSize && |
||||
axis.options.tickSize[1] == "quarter") || |
||||
(axis.options.minTickSize && |
||||
axis.options.minTickSize[1] == "quarter"); |
||||
|
||||
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; |
||||
var span = axis.max - axis.min; |
||||
var suffix = (opts.twelveHourClock) ? " %p" : ""; |
||||
var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; |
||||
var fmt; |
||||
|
||||
if (t < timeUnitSize.minute) { |
||||
fmt = hourCode + ":%M:%S" + suffix; |
||||
} else if (t < timeUnitSize.day) { |
||||
if (span < 2 * timeUnitSize.day) { |
||||
fmt = hourCode + ":%M" + suffix; |
||||
} else { |
||||
fmt = "%b %d " + hourCode + ":%M" + suffix; |
||||
} |
||||
} else if (t < timeUnitSize.month) { |
||||
fmt = "%b %d"; |
||||
} else if ((useQuarters && t < timeUnitSize.quarter) || |
||||
(!useQuarters && t < timeUnitSize.year)) { |
||||
if (span < timeUnitSize.year) { |
||||
fmt = "%b"; |
||||
} else { |
||||
fmt = "%b %Y"; |
||||
} |
||||
} else if (useQuarters && t < timeUnitSize.year) { |
||||
if (span < timeUnitSize.year) { |
||||
fmt = "Q%q"; |
||||
} else { |
||||
fmt = "Q%q %Y"; |
||||
} |
||||
} else { |
||||
fmt = "%Y"; |
||||
} |
||||
|
||||
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); |
||||
|
||||
return rt; |
||||
}; |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
$.plot.plugins.push({ |
||||
init: init, |
||||
options: options, |
||||
name: 'time', |
||||
version: '1.0' |
||||
}); |
||||
|
||||
// Time-axis support used to be in Flot core, which exposed the
|
||||
// formatDate function on the plot object. Various plugins depend
|
||||
// on the function, so we need to re-expose it here.
|
||||
|
||||
$.plot.formatDate = formatDate; |
||||
|
||||
})(jQuery); |
Loading…
Reference in new issue