|
/*! Javascript plotting library for jQuery, v. 0.7. |
|
* |
|
* Released under the MIT license by IOLA, December 2007. |
|
* |
|
*/ |
|
|
|
// first an inline dependency, jquery.colorhelpers.js, we inline it here |
|
// for convenience |
|
|
|
/* Plugin for jQuery for working with colors. |
|
* |
|
* Version 1.1. |
|
* |
|
* Inspiration from jQuery color animation plugin by John Resig. |
|
* |
|
* Released under the MIT license by Ole Laursen, October 2009. |
|
* |
|
* Examples: |
|
* |
|
* $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() |
|
* var c = $.color.extract($("#mydiv"), 'background-color'); |
|
* console.log(c.r, c.g, c.b, c.a); |
|
* $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" |
|
* |
|
* Note that .scale() and .add() return the same modified object |
|
* instead of making a new one. |
|
* |
|
* V. 1.1: Fix error handling so e.g. parsing an empty string does |
|
* produce a color rather than just crashing. |
|
*/ |
|
(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); |
|
|
|
// the actual Flot code |
|
(function($) { |
|
function Plot(placeholder, data_, options_, plugins) { |
|
// data is on the form: |
|
// [ series1, series2 ... ] |
|
// where series is either just the data as [ [x1, y1], [x2, y2], ... ] |
|
// or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } |
|
|
|
var series = [], |
|
options = { |
|
// the color theme used for graphs |
|
colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], |
|
legend: { |
|
show: true, |
|
noColumns: 1, // number of colums in legend table |
|
labelFormatter: null, // fn: string -> string |
|
labelBoxBorderColor: "#ccc", // border color for the little label boxes |
|
container: null, // container (as jQuery object) to put legend in, null means default on top of graph |
|
position: "ne", // position of default legend container within plot |
|
margin: 5, // distance from grid edge to default legend container within plot |
|
backgroundColor: null, // null means auto-detect |
|
backgroundOpacity: 0.85 // set to 0 to avoid background |
|
}, |
|
xaxis: { |
|
show: null, // null = auto-detect, true = always, false = never |
|
position: "bottom", // or "top" |
|
mode: null, // null or "time" |
|
color: null, // base color, labels, ticks |
|
tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" |
|
transform: null, // null or f: number -> number to transform axis |
|
inverseTransform: null, // if transform is set, this should be the inverse function |
|
min: null, // min. value to show, null means set automatically |
|
max: null, // max. value to show, null means set automatically |
|
autoscaleMargin: null, // margin in % to add if auto-setting min/max |
|
ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks |
|
tickFormatter: null, // fn: number -> string |
|
labelWidth: null, // size of tick labels in pixels |
|
labelHeight: null, |
|
reserveSpace: null, // whether to reserve space even if axis isn't shown |
|
tickLength: null, // size in pixels of ticks, or "full" for whole line |
|
alignTicksWithAxis: null, // axis number or null for no sync |
|
|
|
// mode specific options |
|
tickDecimals: null, // no. of decimals, null means auto |
|
tickSize: null, // number or [number, "unit"] |
|
minTickSize: null, // number or [number, "unit"] |
|
monthNames: null, // list of names of months |
|
timeformat: null, // format string to use |
|
twelveHourClock: false // 12 or 24 time in time mode |
|
}, |
|
yaxis: { |
|
autoscaleMargin: 0.02, |
|
position: "left" // or "right" |
|
}, |
|
xaxes: [], |
|
yaxes: [], |
|
series: { |
|
points: { |
|
show: false, |
|
radius: 3, |
|
lineWidth: 2, // in pixels |
|
fill: true, |
|
fillColor: "#ffffff", |
|
symbol: "circle" // or callback |
|
}, |
|
lines: { |
|
// we don't put in show: false so we can see |
|
// whether lines were actively disabled |
|
lineWidth: 2, // in pixels |
|
fill: false, |
|
fillColor: null, |
|
steps: false |
|
}, |
|
bars: { |
|
show: false, |
|
lineWidth: 2, // in pixels |
|
barWidth: 1, // in units of the x axis |
|
fill: true, |
|
fillColor: null, |
|
align: "left", // or "center" |
|
horizontal: false |
|
}, |
|
shadowSize: 3 |
|
}, |
|
grid: { |
|
show: true, |
|
aboveData: false, |
|
color: "#545454", // primary color used for outline and labels |
|
backgroundColor: null, // null for transparent, else color |
|
borderColor: null, // set if different from the grid color |
|
tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" |
|
labelMargin: 5, // in pixels |
|
axisMargin: 8, // in pixels |
|
borderWidth: 2, // in pixels |
|
minBorderMargin: null, // in pixels, null means taken from points radius |
|
markings: null, // array of ranges or fn: axes -> array of ranges |
|
markingsColor: "#f4f4f4", |
|
markingsLineWidth: 2, |
|
// interactive stuff |
|
clickable: false, |
|
hoverable: false, |
|
autoHighlight: true, // highlight in case mouse is near |
|
mouseActiveRadius: 10 // how far the mouse can be away to activate an item |
|
}, |
|
hooks: {} |
|
}, |
|
canvas = null, // the canvas for the plot itself |
|
overlay = null, // canvas for interactive stuff on top of plot |
|
eventHolder = null, // jQuery object that events should be bound to |
|
ctx = null, octx = null, |
|
xaxes = [], yaxes = [], |
|
plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, |
|
canvasWidth = 0, canvasHeight = 0, |
|
plotWidth = 0, plotHeight = 0, |
|
hooks = { |
|
processOptions: [], |
|
processRawData: [], |
|
processDatapoints: [], |
|
drawSeries: [], |
|
draw: [], |
|
bindEvents: [], |
|
drawOverlay: [], |
|
shutdown: [] |
|
}, |
|
plot = this; |
|
|
|
// public functions |
|
plot.setData = setData; |
|
plot.setupGrid = setupGrid; |
|
plot.draw = draw; |
|
plot.getPlaceholder = function() { return placeholder; }; |
|
plot.getCanvas = function() { return canvas; }; |
|
plot.getPlotOffset = function() { return plotOffset; }; |
|
plot.width = function () { return plotWidth; }; |
|
plot.height = function () { return plotHeight; }; |
|
plot.offset = function () { |
|
var o = eventHolder.offset(); |
|
o.left += plotOffset.left; |
|
o.top += plotOffset.top; |
|
return o; |
|
}; |
|
plot.getData = function () { return series; }; |
|
plot.getAxes = function () { |
|
var res = {}, i; |
|
$.each(xaxes.concat(yaxes), function (_, axis) { |
|
if (axis) |
|
res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; |
|
}); |
|
return res; |
|
}; |
|
plot.getXAxes = function () { return xaxes; }; |
|
plot.getYAxes = function () { return yaxes; }; |
|
plot.c2p = canvasToAxisCoords; |
|
plot.p2c = axisToCanvasCoords; |
|
plot.getOptions = function () { return options; }; |
|
plot.highlight = highlight; |
|
plot.unhighlight = unhighlight; |
|
plot.triggerRedrawOverlay = triggerRedrawOverlay; |
|
plot.pointOffset = function(point) { |
|
return { |
|
left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), |
|
top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) |
|
}; |
|
}; |
|
plot.shutdown = shutdown; |
|
plot.resize = function () { |
|
getCanvasDimensions(); |
|
resizeCanvas(canvas); |
|
resizeCanvas(overlay); |
|
}; |
|
|
|
// public attributes |
|
plot.hooks = hooks; |
|
|
|
// initialize |
|
initPlugins(plot); |
|
parseOptions(options_); |
|
setupCanvases(); |
|
setData(data_); |
|
setupGrid(); |
|
draw(); |
|
bindEvents(); |
|
|
|
|
|
function executeHooks(hook, args) { |
|
args = [plot].concat(args); |
|
for (var i = 0; i < hook.length; ++i) |
|
hook[i].apply(this, args); |
|
} |
|
|
|
function initPlugins() { |
|
for (var i = 0; i < plugins.length; ++i) { |
|
var p = plugins[i]; |
|
p.init(plot); |
|
if (p.options) |
|
$.extend(true, options, p.options); |
|
} |
|
} |
|
|
|
function parseOptions(opts) { |
|
var i; |
|
|
|
$.extend(true, options, opts); |
|
|
|
if (options.xaxis.color == null) |
|
options.xaxis.color = options.grid.color; |
|
if (options.yaxis.color == null) |
|
options.yaxis.color = options.grid.color; |
|
|
|
if (options.xaxis.tickColor == null) // backwards-compatibility |
|
options.xaxis.tickColor = options.grid.tickColor; |
|
if (options.yaxis.tickColor == null) // backwards-compatibility |
|
options.yaxis.tickColor = options.grid.tickColor; |
|
|
|
if (options.grid.borderColor == null) |
|
options.grid.borderColor = options.grid.color; |
|
if (options.grid.tickColor == null) |
|
options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); |
|
|
|
// fill in defaults in axes, copy at least always the |
|
// first as the rest of the code assumes it'll be there |
|
for (i = 0; i < Math.max(1, options.xaxes.length); ++i) |
|
options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); |
|
for (i = 0; i < Math.max(1, options.yaxes.length); ++i) |
|
options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); |
|
|
|
// backwards compatibility, to be removed in future |
|
if (options.xaxis.noTicks && options.xaxis.ticks == null) |
|
options.xaxis.ticks = options.xaxis.noTicks; |
|
if (options.yaxis.noTicks && options.yaxis.ticks == null) |
|
options.yaxis.ticks = options.yaxis.noTicks; |
|
if (options.x2axis) { |
|
options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); |
|
options.xaxes[1].position = "top"; |
|
} |
|
if (options.y2axis) { |
|
options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); |
|
options.yaxes[1].position = "right"; |
|
} |
|
if (options.grid.coloredAreas) |
|
options.grid.markings = options.grid.coloredAreas; |
|
if (options.grid.coloredAreasColor) |
|
options.grid.markingsColor = options.grid.coloredAreasColor; |
|
if (options.lines) |
|
$.extend(true, options.series.lines, options.lines); |
|
if (options.points) |
|
$.extend(true, options.series.points, options.points); |
|
if (options.bars) |
|
$.extend(true, options.series.bars, options.bars); |
|
if (options.shadowSize != null) |
|
options.series.shadowSize = options.shadowSize; |
|
|
|
// save options on axes for future reference |
|
for (i = 0; i < options.xaxes.length; ++i) |
|
getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; |
|
for (i = 0; i < options.yaxes.length; ++i) |
|
getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; |
|
|
|
// add hooks from options |
|
for (var n in hooks) |
|
if (options.hooks[n] && options.hooks[n].length) |
|
hooks[n] = hooks[n].concat(options.hooks[n]); |
|
|
|
executeHooks(hooks.processOptions, [options]); |
|
} |
|
|
|
function setData(d) { |
|
series = parseData(d); |
|
fillInSeriesOptions(); |
|
processData(); |
|
} |
|
|
|
function parseData(d) { |
|
var res = []; |
|
for (var i = 0; i < d.length; ++i) { |
|
var s = $.extend(true, {}, options.series); |
|
|
|
if (d[i].data != null) { |
|
s.data = d[i].data; // move the data instead of deep-copy |
|
delete d[i].data; |
|
|
|
$.extend(true, s, d[i]); |
|
|
|
d[i].data = s.data; |
|
} |
|
else |
|
s.data = d[i]; |
|
res.push(s); |
|
} |
|
|
|
return res; |
|
} |
|
|
|
function axisNumber(obj, coord) { |
|
var a = obj[coord + "axis"]; |
|
if (typeof a == "object") // if we got a real axis, extract number |
|
a = a.n; |
|
if (typeof a != "number") |
|
a = 1; // default to first axis |
|
return a; |
|
} |
|
|
|
function allAxes() { |
|
// return flat array without annoying null entries |
|
return $.grep(xaxes.concat(yaxes), function (a) { return a; }); |
|
} |
|
|
|
function canvasToAxisCoords(pos) { |
|