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