From c50a323c25a0787ba2051b19721983776a229615 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Thu, 5 Feb 2015 00:40:47 +0100 Subject: Initial commit --- js/plugins/flot/excanvas.min.js | 1 + js/plugins/flot/flot-data.js | 1242 +++++++++++++ js/plugins/flot/jquery.flot.js | 2599 ++++++++++++++++++++++++++++ js/plugins/flot/jquery.flot.pie.js | 750 ++++++++ js/plugins/flot/jquery.flot.resize.js | 60 + js/plugins/flot/jquery.flot.tooltip.min.js | 12 + 6 files changed, 4664 insertions(+) create mode 100644 js/plugins/flot/excanvas.min.js create mode 100644 js/plugins/flot/flot-data.js create mode 100644 js/plugins/flot/jquery.flot.js create mode 100644 js/plugins/flot/jquery.flot.pie.js create mode 100644 js/plugins/flot/jquery.flot.resize.js create mode 100644 js/plugins/flot/jquery.flot.tooltip.min.js (limited to 'js/plugins/flot') diff --git a/js/plugins/flot/excanvas.min.js b/js/plugins/flot/excanvas.min.js new file mode 100644 index 0000000..12c74f7 --- /dev/null +++ b/js/plugins/flot/excanvas.min.js @@ -0,0 +1 @@ +if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&").replace(/"/g,""")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AHAL.x){AL.x=Z.x}if(AG.y==null||Z.yAL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('','','');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()}; \ No newline at end of file diff --git a/js/plugins/flot/flot-data.js b/js/plugins/flot/flot-data.js new file mode 100644 index 0000000..cc0a779 --- /dev/null +++ b/js/plugins/flot/flot-data.js @@ -0,0 +1,1242 @@ +//Flot Line Chart +$(document).ready(function() { + console.log("document ready"); + var offset = 0; + plot(); + + function plot() { + var sin = [], + cos = []; + for (var i = 0; i < 12; i += 0.2) { + sin.push([i, Math.sin(i + offset)]); + cos.push([i, Math.cos(i + offset)]); + } + + var options = { + series: { + lines: { + show: true + }, + points: { + show: true + } + }, + grid: { + hoverable: true //IMPORTANT! this is needed for tooltip to work + }, + yaxis: { + min: -1.2, + max: 1.2 + }, + tooltip: true, + tooltipOpts: { + content: "'%s' of %x.1 is %y.4", + shifts: { + x: -60, + y: 25 + } + } + }; + + var plotObj = $.plot($("#flot-line-chart"), [{ + data: sin, + label: "sin(x)" + }, { + data: cos, + label: "cos(x)" + }], + options); + } +}); + +//Flot Pie Chart +$(function() { + + var data = [{ + label: "Series 0", + data: 1 + }, { + label: "Series 1", + data: 3 + }, { + label: "Series 2", + data: 9 + }, { + label: "Series 3", + data: 20 + }]; + + var plotObj = $.plot($("#flot-pie-chart"), data, { + series: { + pie: { + show: true + } + }, + grid: { + hoverable: true + }, + tooltip: true, + tooltipOpts: { + content: "%p.0%, %s", // show percentages, rounding to 2 decimal places + shifts: { + x: 20, + y: 0 + }, + defaultTheme: false + } + }); + +}); + +//Flot Multiple Axes Line Chart +$(function() { + var oilprices = [ + [1167692400000, 61.05], + [1167778800000, 58.32], + [1167865200000, 57.35], + [1167951600000, 56.31], + [1168210800000, 55.55], + [1168297200000, 55.64], + [1168383600000, 54.02], + [1168470000000, 51.88], + [1168556400000, 52.99], + [1168815600000, 52.99], + [1168902000000, 51.21], + [1168988400000, 52.24], + [1169074800000, 50.48], + [1169161200000, 51.99], + [1169420400000, 51.13], + [1169506800000, 55.04], + [1169593200000, 55.37], + [1169679600000, 54.23], + [1169766000000, 55.42], + [1170025200000, 54.01], + [1170111600000, 56.97], + [1170198000000, 58.14], + [1170284400000, 58.14], + [1170370800000, 59.02], + [1170630000000, 58.74], + [1170716400000, 58.88], + [1170802800000, 57.71], + [1170889200000, 59.71], + [1170975600000, 59.89], + [1171234800000, 57.81], + [1171321200000, 59.06], + [1171407600000, 58.00], + [1171494000000, 57.99], + [1171580400000, 59.39], + [1171839600000, 59.39], + [1171926000000, 58.07], + [1172012400000, 60.07], + [1172098800000, 61.14], + [1172444400000, 61.39], + [1172530800000, 61.46], + [1172617200000, 61.79], + [1172703600000, 62.00], + [1172790000000, 60.07], + [1173135600000, 60.69], + [1173222000000, 61.82], + [1173308400000, 60.05], + [1173654000000, 58.91], + [1173740400000, 57.93], + [1173826800000, 58.16], + [1173913200000, 57.55], + [1173999600000, 57.11], + [1174258800000, 56.59], + [1174345200000, 59.61], + [1174518000000, 61.69], + [1174604400000, 62.28], + [1174860000000, 62.91], + [1174946400000, 62.93], + [1175032800000, 64.03], + [1175119200000, 66.03], + [1175205600000, 65.87], + [1175464800000, 64.64], + [1175637600000, 64.38], + [1175724000000, 64.28], + [1175810400000, 64.28], + [1176069600000, 61.51], + [1176156000000, 61.89], + [1176242400000, 62.01], + [1176328800000, 63.85], + [1176415200000, 63.63], + [1176674400000, 63.61], + [1176760800000, 63.10], + [1176847200000, 63.13], + [1176933600000, 61.83], + [1177020000000, 63.38], + [1177279200000, 64.58], + [1177452000000, 65.84], + [1177538400000, 65.06], + [1177624800000, 66.46], + [1177884000000, 64.40], + [1178056800000, 63.68], + [1178143200000, 63.19], + [1178229600000, 61.93], + [1178488800000, 61.47], + [1178575200000, 61.55], + [1178748000000, 61.81], + [1178834400000, 62.37], + [1179093600000, 62.46], + [1179180000000, 63.17], + [1179266400000, 62.55], + [1179352800000, 64.94], + [1179698400000, 66.27], + [1179784800000, 65.50], + [1179871200000, 65.77], + [1179957600000, 64.18], + [1180044000000, 65.20], + [1180389600000, 63.15], + [1180476000000, 63.49], + [1180562400000, 65.08], + [1180908000000, 66.30], + [1180994400000, 65.96], + [1181167200000, 66.93], + [1181253600000, 65.98], + [1181599200000, 65.35], + [1181685600000, 66.26], + [1181858400000, 68.00], + [1182117600000, 69.09], + [1182204000000, 69.10], + [1182290400000, 68.19], + [1182376800000, 68.19], + [1182463200000, 69.14], + [1182722400000, 68.19], + [1182808800000, 67.77], + [1182895200000, 68.97], + [1182981600000, 69.57], + [1183068000000, 70.68], + [1183327200000, 71.09], + [1183413600000, 70.92], + [1183586400000, 71.81], + [1183672800000, 72.81], + [1183932000000, 72.19], + [1184018400000, 72.56], + [1184191200000, 72.50], + [1184277600000, 74.15], + [1184623200000, 75.05], + [1184796000000, 75.92], + [1184882400000, 75.57], + [1185141600000, 74.89], + [1185228000000, 73.56], + [1185314400000, 75.57], + [1185400800000, 74.95], + [1185487200000, 76.83], + [1185832800000, 78.21], + [1185919200000, 76.53], + [1186005600000, 76.86], + [1186092000000, 76.00], + [1186437600000, 71.59], + [1186696800000, 71.47], + [1186956000000, 71.62], + [1187042400000, 71.00], + [1187301600000, 71.98], + [1187560800000, 71.12], + [1187647200000, 69.47], + [1187733600000, 69.26], + [1187820000000, 69.83], + [1187906400000, 71.09], + [1188165600000, 71.73], + [1188338400000, 73.36], + [1188511200000, 74.04], + [1188856800000, 76.30], + [1189116000000, 77.49], + [1189461600000, 78.23], + [1189548000000, 79.91], + [1189634400000, 80.09], + [1189720800000, 79.10], + [1189980000000, 80.57], + [1190066400000, 81.93], + [1190239200000, 83.32], + [1190325600000, 81.62], + [1190584800000, 80.95], + [1190671200000, 79.53], + [1190757600000, 80.30], + [1190844000000, 82.88], + [1190930400000, 81.66], + [1191189600000, 80.24], + [1191276000000, 80.05], + [1191362400000, 79.94], + [1191448800000, 81.44], + [1191535200000, 81.22], + [1191794400000, 79.02], + [1191880800000, 80.26], + [1191967200000, 80.30], + [1192053600000, 83.08], + [1192140000000, 83.69], + [1192399200000, 86.13], + [1192485600000, 87.61], + [1192572000000, 87.40], + [1192658400000, 89.47], + [1192744800000, 88.60], + [1193004000000, 87.56], + [1193090400000, 87.56], + [1193176800000, 87.10], + [1193263200000, 91.86], + [1193612400000, 93.53], + [1193698800000, 94.53], + [1193871600000, 95.93], + [1194217200000, 93.98], + [1194303600000, 96.37], + [1194476400000, 95.46], + [1194562800000, 96.32], + [1195081200000, 93.43], + [1195167600000, 95.10], + [1195426800000, 94.64], + [1195513200000, 95.10], + [1196031600000, 97.70], + [1196118000000, 94.42], + [1196204400000, 90.62], + [1196290800000, 91.01], + [1196377200000, 88.71], + [1196636400000, 88.32], + [1196809200000, 90.23], + [1196982000000, 88.28], + [1197241200000, 87.86], + [1197327600000, 90.02], + [1197414000000, 92.25], + [1197586800000, 90.63], + [1197846000000, 90.63], + [1197932400000, 90.49], + [1198018800000, 91.24], + [1198105200000, 91.06], + [1198191600000, 90.49], + [1198710000000, 96.62], + [1198796400000, 96.00], + [1199142000000, 99.62], + [1199314800000, 99.18], + [1199401200000, 95.09], + [1199660400000, 96.33], + [1199833200000, 95.67], + [1200351600000, 91.90], + [1200438000000, 90.84], + [1200524400000, 90.13], + [1200610800000, 90.57], + [1200956400000, 89.21], + [1201042800000, 86.99], + [1201129200000, 89.85], + [1201474800000, 90.99], + [1201561200000, 91.64], + [1201647600000, 92.33], + [1201734000000, 91.75], + [1202079600000, 90.02], + [1202166000000, 88.41], + [1202252400000, 87.14], + [1202338800000, 88.11], + [1202425200000, 91.77], + [1202770800000, 92.78], + [1202857200000, 93.27], + [1202943600000, 95.46], + [1203030000000, 95.46], + [1203289200000, 101.74], + [1203462000000, 98.81], + [1203894000000, 100.88], + [1204066800000, 99.64], + [1204153200000, 102.59], + [1204239600000, 101.84], + [1204498800000, 99.52], + [1204585200000, 99.52], + [1204671600000, 104.52], + [1204758000000, 105.47], + [1204844400000, 105.15], + [1205103600000, 108.75], + [1205276400000, 109.92], + [1205362800000, 110.33], + [1205449200000, 110.21], + [1205708400000, 105.68], + [1205967600000, 101.84], + [1206313200000, 100.86], + [1206399600000, 101.22], + [1206486000000, 105.90], + [1206572400000, 107.58], + [1206658800000, 105.62], + [1206914400000, 101.58], + [1207000800000, 100.98], + [1207173600000, 103.83], + [1207260000000, 106.23], + [1207605600000, 108.50], + [1207778400000, 110.11], + [1207864800000, 110.14], + [1208210400000, 113.79], + [1208296800000, 114.93], + [1208383200000, 114.86], + [1208728800000, 117.48], + [1208815200000, 118.30], + [1208988000000, 116.06], + [1209074400000, 118.52], + [1209333600000, 118.75], + [1209420000000, 113.46], + [1209592800000, 112.52], + [1210024800000, 121.84], + [1210111200000, 123.53], + [1210197600000, 123.69], + [1210543200000, 124.23], + [1210629600000, 125.80], + [1210716000000, 126.29], + [1211148000000, 127.05], + [1211320800000, 129.07], + [1211493600000, 132.19], + [1211839200000, 128.85], + [1212357600000, 127.76], + [1212703200000, 138.54], + [1212962400000, 136.80], + [1213135200000, 136.38], + [1213308000000, 134.86], + [1213653600000, 134.01], + [1213740000000, 136.68], + [1213912800000, 135.65], + [1214172000000, 134.62], + [1214258400000, 134.62], + [1214344800000, 134.62], + [1214431200000, 139.64], + [1214517600000, 140.21], + [1214776800000, 140.00], + [1214863200000, 140.97], + [1214949600000, 143.57], + [1215036000000, 145.29], + [1215381600000, 141.37], + [1215468000000, 136.04], + [1215727200000, 146.40], + [1215986400000, 145.18], + [1216072800000, 138.74], + [1216159200000, 134.60], + [1216245600000, 129.29], + [1216332000000, 130.65], + [1216677600000, 127.95], + [1216850400000, 127.95], + [1217282400000, 122.19], + [1217455200000, 124.08], + [1217541600000, 125.10], + [1217800800000, 121.41], + [1217887200000, 119.17], + [1217973600000, 118.58], + [1218060000000, 120.02], + [1218405600000, 114.45], + [1218492000000, 113.01], + [1218578400000, 116.00], + [1218751200000, 113.77], + [1219010400000, 112.87], + [1219096800000, 114.53], + [1219269600000, 114.98], + [1219356000000, 114.98], + [1219701600000, 116.27], + [1219788000000, 118.15], + [1219874400000, 115.59], + [1219960800000, 115.46], + [1220306400000, 109.71], + [1220392800000, 109.35], + [1220565600000, 106.23], + [1220824800000, 106.34] + ]; + var exchangerates = [ + [1167606000000, 0.7580], + [1167692400000, 0.7580], + [1167778800000, 0.75470], + [1167865200000, 0.75490], + [1167951600000, 0.76130], + [1168038000000, 0.76550], + [1168124400000, 0.76930], + [1168210800000, 0.76940], + [1168297200000, 0.76880], + [1168383600000, 0.76780], + [1168470000000, 0.77080], + [1168556400000, 0.77270], + [1168642800000, 0.77490], + [1168729200000, 0.77410], + [1168815600000, 0.77410], + [1168902000000, 0.77320], + [1168988400000, 0.77270], + [1169074800000, 0.77370], + [1169161200000, 0.77240], + [1169247600000, 0.77120], + [1169334000000, 0.7720], + [1169420400000, 0.77210], + [1169506800000, 0.77170], + [1169593200000, 0.77040], + [1169679600000, 0.7690], + [1169766000000, 0.77110], + [1169852400000, 0.7740], + [1169938800000, 0.77450], + [1170025200000, 0.77450], + [1170111600000, 0.7740], + [1170198000000, 0.77160], + [1170284400000, 0.77130], + [1170370800000, 0.76780], + [1170457200000, 0.76880], + [1170543600000, 0.77180], + [1170630000000, 0.77180], + [1170716400000, 0.77280], + [1170802800000, 0.77290], + [1170889200000, 0.76980], + [1170975600000, 0.76850], + [1171062000000, 0.76810], + [1171148400000, 0.7690], + [1171234800000, 0.7690], + [1171321200000, 0.76980], + [1171407600000, 0.76990], + [1171494000000, 0.76510], + [1171580400000, 0.76130], + [1171666800000, 0.76160], + [1171753200000, 0.76140], + [1171839600000, 0.76140], + [1171926000000, 0.76070], + [1172012400000, 0.76020], + [1172098800000, 0.76110], + [1172185200000, 0.76220], + [1172271600000, 0.76150], + [1172358000000, 0.75980], + [1172444400000, 0.75980], + [1172530800000, 0.75920], + [1172617200000, 0.75730], + [1172703600000, 0.75660], + [1172790000000, 0.75670], + [1172876400000, 0.75910], + [1172962800000, 0.75820], + [1173049200000, 0.75850], + [1173135600000, 0.76130], + [1173222000000, 0.76310], + [1173308400000, 0.76150], + [1173394800000, 0.760], + [1173481200000, 0.76130], + [1173567600000, 0.76270], + [1173654000000, 0.76270], + [1173740400000, 0.76080], + [1173826800000, 0.75830], + [1173913200000, 0.75750], + [1173999600000, 0.75620], + [1174086000000, 0.7520], + [1174172400000, 0.75120], + [1174258800000, 0.75120], + [1174345200000, 0.75170], + [1174431600000, 0.7520], + [1174518000000, 0.75110], + [1174604400000, 0.7480], + [1174690800000, 0.75090], + [1174777200000, 0.75310], + [1174860000000, 0.75310], + [1174946400000, 0.75270], + [1175032800000, 0.74980], + [1175119200000, 0.74930], + [1175205600000, 0.75040], + [1175292000000, 0.750], + [1175378400000, 0.74910], + [1175464800000, 0.74910], + [1175551200000, 0.74850], + [1175637600000, 0.74840], + [1175724000000, 0.74920], + [1175810400000, 0.74710], + [1175896800000, 0.74590], + [1175983200000, 0.74770], + [1176069600000, 0.74770], + [1176156000000, 0.74830], + [1176242400000, 0.74580], + [1176328800000, 0.74480], + [1176415200000, 0.7430], + [1176501600000, 0.73990], + [1176588000000, 0.73950], + [1176674400000, 0.73950], + [1176760800000, 0.73780], + [1176847200000, 0.73820], + [1176933600000, 0.73620], + [1177020000000, 0.73550], + [1177106400000, 0.73480], + [1177192800000, 0.73610], + [1177279200000, 0.73610], + [1177365600000, 0.73650], + [1177452000000, 0.73620], + [1177538400000, 0.73310], + [1177624800000, 0.73390], + [1177711200000, 0.73440], + [1177797600000, 0.73270], + [1177884000000, 0.73270], + [1177970400000, 0.73360], + [1178056800000, 0.73330], + [1178143200000, 0.73590], + [1178229600000, 0.73590], + [1178316000000, 0.73720], + [1178402400000, 0.7360], + [1178488800000, 0.7360], + [1178575200000, 0.7350], + [1178661600000, 0.73650], + [1178748000000, 0.73840], + [1178834400000, 0.73950], + [1178920800000, 0.74130], + [1179007200000, 0.73970], + [1179093600000, 0.73960], + [1179180000000, 0.73850], + [1179266400000, 0.73780], + [1179352800000, 0.73660], + [1179439200000, 0.740], + [1179525600000, 0.74110], + [1179612000000, 0.74060], + [1179698400000, 0.74050], + [1179784800000, 0.74140], + [1179871200000, 0.74310], + [1179957600000, 0.74310], + [1180044000000, 0.74380], + [1180130400000, 0.74430], + [1180216800000, 0.74430], + [1180303200000, 0.74430], + [1180389600000, 0.74340], + [1180476000000, 0.74290], + [1180562400000, 0.74420], + [1180648800000, 0.7440], + [1180735200000, 0.74390], + [1180821600000, 0.74370], + [1180908000000, 0.74370], + [1180994400000, 0.74290], + [1181080800000, 0.74030], + [1181167200000, 0.73990], + [1181253600000, 0.74180], + [1181340000000, 0.74680], + [1181426400000, 0.7480], + [1181512800000, 0.7480], + [1181599200000, 0.7490], + [1181685600000, 0.74940], + [1181772000000, 0.75220], + [1181858400000, 0.75150], + [1181944800000, 0.75020], + [1182031200000, 0.74720], + [1182117600000, 0.74720], + [1182204000000, 0.74620], + [1182290400000, 0.74550], + [1182376800000, 0.74490], + [1182463200000, 0.74670], + [1182549600000, 0.74580], + [1182636000000, 0.74270], + [1182722400000, 0.74270], + [1182808800000, 0.7430], + [1182895200000, 0.74290], + [1182981600000, 0.7440], + [1183068000000, 0.7430], + [1183154400000, 0.74220], + [1183240800000, 0.73880], + [1183327200000, 0.73880], + [1183413600000, 0.73690], + [1183500000000, 0.73450], + [1183586400000, 0.73450], + [1183672800000, 0.73450], + [1183759200000, 0.73520], + [1183845600000, 0.73410], + [1183932000000, 0.73410], + [1184018400000, 0.7340], + [1184104800000, 0.73240], + [1184191200000, 0.72720], + [1184277600000, 0.72640], + [1184364000000, 0.72550], + [1184450400000, 0.72580], + [1184536800000, 0.72580], + [1184623200000, 0.72560], + [1184709600000, 0.72570], + [1184796000000, 0.72470], + [1184882400000, 0.72430], + [1184968800000, 0.72440], + [1185055200000, 0.72350], + [1185141600000, 0.72350], + [1185228000000, 0.72350], + [1185314400000, 0.72350], + [1185400800000, 0.72620], + [1185487200000, 0.72880], + [1185573600000, 0.73010], + [1185660000000, 0.73370], + [1185746400000, 0.73370], + [1185832800000, 0.73240], + [1185919200000, 0.72970], + [1186005600000, 0.73170], + [1186092000000, 0.73150], + [1186178400000, 0.72880], + [1186264800000, 0.72630], + [1186351200000, 0.72630], + [1186437600000, 0.72420], + [1186524000000, 0.72530], + [1186610400000, 0.72640], + [1186696800000, 0.7270], + [1186783200000, 0.73120], + [1186869600000, 0.73050], + [1186956000000, 0.73050], + [1187042400000, 0.73180], + [1187128800000, 0.73580], + [1187215200000, 0.74090], + [1187301600000, 0.74540], + [1187388000000, 0.74370], + [1187474400000, 0.74240], + [1187560800000, 0.74240], + [1187647200000, 0.74150], + [1187733600000, 0.74190], + [1187820000000, 0.74140], + [1187906400000, 0.73770], + [1187992800000, 0.73550], + [1188079200000, 0.73150], + [1188165600000, 0.73150], + [1188252000000, 0.7320], + [1188338400000, 0.73320], + [1188424800000, 0.73460], + [1188511200000, 0.73280], + [1188597600000, 0.73230], + [1188684000000, 0.7340], + [1188770400000, 0.7340], + [1188856800000, 0.73360], + [1188943200000, 0.73510], + [1189029600000, 0.73460], + [1189116000000, 0.73210], + [1189202400000, 0.72940], + [1189288800000, 0.72660], + [1189375200000, 0.72660], + [1189461600000, 0.72540], + [1189548000000, 0.72420], + [1189634400000, 0.72130], + [1189720800000, 0.71970], + [1189807200000, 0.72090], + [1189893600000, 0.7210], + [1189980000000, 0.7210], + [1190066400000, 0.7210], + [1190152800000, 0.72090], + [1190239200000, 0.71590], + [1190325600000, 0.71330], + [1190412000000, 0.71050], + [1190498400000, 0.70990], + [1190584800000, 0.70990], + [1190671200000, 0.70930], + [1190757600000, 0.70930], + [1190844000000, 0.70760], + [1190930400000, 0.7070], + [1191016800000, 0.70490], + [1191103200000, 0.70120], + [1191189600000, 0.70110], + [1191276000000, 0.70190], + [1191362400000, 0.70460], + [1191448800000, 0.70630], + [1191535200000, 0.70890], + [1191621600000, 0.70770], + [1191708000000, 0.70770], + [1191794400000, 0.70770], + [1191880800000, 0.70910], + [1191967200000, 0.71180], + [1192053600000, 0.70790], + [1192140000000, 0.70530], + [1192226400000, 0.7050], + [1192312800000, 0.70550], + [1192399200000, 0.70550], + [1192485600000, 0.70450], + [1192572000000, 0.70510], + [1192658400000, 0.70510], + [1192744800000, 0.70170], + [1192831200000, 0.70], + [1192917600000, 0.69950], + [1193004000000, 0.69940], + [1193090400000, 0.70140], + [1193176800000, 0.70360], + [1193263200000, 0.70210], + [1193349600000, 0.70020], + [1193436000000, 0.69670], + [1193522400000, 0.6950], + [1193612400000, 0.6950], + [1193698800000, 0.69390], + [1193785200000, 0.6940], + [1193871600000, 0.69220], + [1193958000000, 0.69190], + [1194044400000, 0.69140], + [1194130800000, 0.68940], + [1194217200000, 0.68910], + [1194303600000, 0.69040], + [1194390000000, 0.6890], + [1194476400000, 0.68340], + [1194562800000, 0.68230], + [1194649200000, 0.68070], + [1194735600000, 0.68150], + [1194822000000, 0.68150], + [1194908400000, 0.68470], + [1194994800000, 0.68590], + [1195081200000, 0.68220], + [1195167600000, 0.68270], + [1195254000000, 0.68370], + [1195340400000, 0.68230], + [1195426800000, 0.68220], + [1195513200000, 0.68220], + [1195599600000, 0.67920], + [1195686000000, 0.67460], + [1195772400000, 0.67350], + [1195858800000, 0.67310], + [1195945200000, 0.67420], + [1196031600000, 0.67440], + [1196118000000, 0.67390], + [1196204400000, 0.67310], + [1196290800000, 0.67610], + [1196377200000, 0.67610], + [1196463600000, 0.67850], + [1196550000000, 0.68180], + [1196636400000, 0.68360], + [1196722800000, 0.68230], + [1196809200000, 0.68050], + [1196895600000, 0.67930], + [1196982000000, 0.68490], + [1197068400000, 0.68330], + [1197154800000, 0.68250], + [1197241200000, 0.68250], + [1197327600000, 0.68160], + [1197414000000, 0.67990], + [1197500400000, 0.68130], + [1197586800000, 0.68090], + [1197673200000, 0.68680], + [1197759600000, 0.69330], + [1197846000000, 0.69330], + [1197932400000, 0.69450], + [1198018800000, 0.69440], + [1198105200000, 0.69460], + [1198191600000, 0.69640], + [1198278000000, 0.69650], + [1198364400000, 0.69560], + [1198450800000, 0.69560], + [1198537200000, 0.6950], + [1198623600000, 0.69480], + [1198710000000, 0.69280], + [1198796400000, 0.68870], + [1198882800000, 0.68240], + [1198969200000, 0.67940], + [1199055600000, 0.67940], + [1199142000000, 0.68030], + [1199228400000, 0.68550], + [1199314800000, 0.68240], + [1199401200000, 0.67910], + [1199487600000, 0.67830], + [1199574000000, 0.67850], + [1199660400000, 0.67850], + [1199746800000, 0.67970], + [1199833200000, 0.680], + [1199919600000, 0.68030], + [1200006000000, 0.68050], + [1200092400000, 0.6760], + [1200178800000, 0.6770], + [1200265200000, 0.6770], + [1200351600000, 0.67360], + [1200438000000, 0.67260], + [1200524400000, 0.67640], + [1200610800000, 0.68210], + [1200697200000, 0.68310], + [1200783600000, 0.68420], + [1200870000000, 0.68420], + [1200956400000, 0.68870], + [1201042800000, 0.69030], + [1201129200000, 0.68480], + [1201215600000, 0.68240], + [1201302000000, 0.67880], + [1201388400000, 0.68140], + [1201474800000, 0.68140], + [1201561200000, 0.67970], + [1201647600000, 0.67690], + [1201734000000, 0.67650], + [1201820400000, 0.67330], + [1201906800000, 0.67290], + [1201993200000, 0.67580], + [1202079600000, 0.67580], + [1202166000000, 0.6750], + [1202252400000, 0.6780], + [1202338800000, 0.68330], + [1202425200000, 0.68560], + [1202511600000, 0.69030], + [1202598000000, 0.68960], + [1202684400000, 0.68960], + [1202770800000, 0.68820], + [1202857200000, 0.68790], + [1202943600000, 0.68620], + [1203030000000, 0.68520], + [1203116400000, 0.68230], + [1203202800000, 0.68130], + [1203289200000, 0.68130], + [1203375600000, 0.68220], + [1203462000000, 0.68020], + [1203548400000, 0.68020], + [1203634800000, 0.67840], + [1203721200000, 0.67480], + [1203807600000, 0.67470], + [1203894000000, 0.67470], + [1203980400000, 0.67480], + [1204066800000, 0.67330], + [1204153200000, 0.6650], + [1204239600000, 0.66110], + [1204326000000, 0.65830], + [1204412400000, 0.6590], + [1204498800000, 0.6590], + [1204585200000, 0.65810], + [1204671600000, 0.65780], + [1204758000000, 0.65740], + [1204844400000, 0.65320], + [1204930800000, 0.65020], + [1205017200000, 0.65140], + [1205103600000, 0.65140], + [1205190000000, 0.65070], + [1205276400000, 0.6510], + [1205362800000, 0.64890], + [1205449200000, 0.64240], + [1205535600000, 0.64060], + [1205622000000, 0.63820], + [1205708400000, 0.63820], + [1205794800000, 0.63410], + [1205881200000, 0.63440], + [1205967600000, 0.63780], + [1206054000000, 0.64390], + [1206140400000, 0.64780], + [1206226800000, 0.64810], + [1206313200000, 0.64810], + [1206399600000, 0.64940], + [1206486000000, 0.64380], + [1206572400000, 0.63770], + [1206658800000, 0.63290], + [1206745200000, 0.63360], + [1206831600000, 0.63330], + [1206914400000, 0.63330], + [1207000800000, 0.6330], + [1207087200000, 0.63710], + [1207173600000, 0.64030], + [1207260000000, 0.63960], + [1207346400000, 0.63640], + [1207432800000, 0.63560], + [1207519200000, 0.63560], + [1207605600000, 0.63680], + [1207692000000, 0.63570], + [1207778400000, 0.63540], + [1207864800000, 0.6320], + [1207951200000, 0.63320], + [1208037600000, 0.63280], + [1208124000000, 0.63310], + [1208210400000, 0.63420], + [1208296800000, 0.63210], + [1208383200000, 0.63020], + [1208469600000, 0.62780], + [1208556000000, 0.63080], + [1208642400000, 0.63240], + [1208728800000, 0.63240], + [1208815200000, 0.63070], + [1208901600000, 0.62770], + [1208988000000, 0.62690], + [1209074400000, 0.63350], + [1209160800000, 0.63920], + [1209247200000, 0.640], + [1209333600000, 0.64010], + [1209420000000, 0.63960], + [1209506400000, 0.64070], + [1209592800000, 0.64230], + [1209679200000, 0.64290], + [1209765600000, 0.64720], + [1209852000000, 0.64850], + [1209938400000, 0.64860], + [1210024800000, 0.64670], + [1210111200000, 0.64440], + [1210197600000, 0.64670], + [1210284000000, 0.65090], + [1210370400000, 0.64780], + [1210456800000, 0.64610], + [1210543200000, 0.64610], + [1210629600000, 0.64680], + [1210716000000, 0.64490], + [1210802400000, 0.6470], + [1210888800000, 0.64610], + [1210975200000, 0.64520], + [1211061600000, 0.64220], + [1211148000000, 0.64220], + [1211234400000, 0.64250], + [1211320800000, 0.64140], + [1211407200000, 0.63660], + [1211493600000, 0.63460], + [1211580000000, 0.6350], + [1211666400000, 0.63460], + [1211752800000, 0.63460], + [1211839200000, 0.63430], + [1211925600000, 0.63460], + [1212012000000, 0.63790], + [1212098400000, 0.64160], + [1212184800000, 0.64420], + [1212271200000, 0.64310], + [1212357600000, 0.64310], + [1212444000000, 0.64350], + [1212530400000, 0.6440], + [1212616800000, 0.64730], + [1212703200000, 0.64690], + [1212789600000, 0.63860], + [1212876000000, 0.63560], + [1212962400000, 0.6340], + [1213048800000, 0.63460], + [1213135200000, 0.6430], + [1213221600000, 0.64520], + [1213308000000, 0.64670], + [1213394400000, 0.65060], + [1213480800000, 0.65040], + [1213567200000, 0.65030], + [1213653600000, 0.64810], + [1213740000000, 0.64510], + [1213826400000, 0.6450], + [1213912800000, 0.64410], + [1213999200000, 0.64140], + [1214085600000, 0.64090], + [1214172000000, 0.64090], + [1214258400000, 0.64280], + [1214344800000, 0.64310], + [1214431200000, 0.64180], + [1214517600000, 0.63710], + [1214604000000, 0.63490], + [1214690400000, 0.63330], + [1214776800000, 0.63340], + [1214863200000, 0.63380], + [1214949600000, 0.63420], + [1215036000000, 0.6320], + [1215122400000, 0.63180], + [1215208800000, 0.6370], + [1215295200000, 0.63680], + [1215381600000, 0.63680], + [1215468000000, 0.63830], + [1215554400000, 0.63710], + [1215640800000, 0.63710], + [1215727200000, 0.63550], + [1215813600000, 0.6320], + [1215900000000, 0.62770], + [1215986400000, 0.62760], + [1216072800000, 0.62910], + [1216159200000, 0.62740], + [1216245600000, 0.62930], + [1216332000000, 0.63110], + [1216418400000, 0.6310], + [1216504800000, 0.63120], + [1216591200000, 0.63120], + [1216677600000, 0.63040], + [1216764000000, 0.62940], + [1216850400000, 0.63480], + [1216936800000, 0.63780], + [1217023200000, 0.63680], + [1217109600000, 0.63680], + [1217196000000, 0.63680], + [1217282400000, 0.6360], + [1217368800000, 0.6370], + [1217455200000, 0.64180], + [1217541600000, 0.64110], + [1217628000000, 0.64350], + [1217714400000, 0.64270], + [1217800800000, 0.64270], + [1217887200000, 0.64190], + [1217973600000, 0.64460], + [1218060000000, 0.64680], + [1218146400000, 0.64870], + [1218232800000, 0.65940], + [1218319200000, 0.66660], + [1218405600000, 0.66660], + [1218492000000, 0.66780], + [1218578400000, 0.67120], + [1218664800000, 0.67050], + [1218751200000, 0.67180], + [1218837600000, 0.67840], + [1218924000000, 0.68110], + [1219010400000, 0.68110], + [1219096800000, 0.67940], + [1219183200000, 0.68040], + [1219269600000, 0.67810], + [1219356000000, 0.67560], + [1219442400000, 0.67350], + [1219528800000, 0.67630], + [1219615200000, 0.67620], + [1219701600000, 0.67770], + [1219788000000, 0.68150], + [1219874400000, 0.68020], + [1219960800000, 0.6780], + [1220047200000, 0.67960], + [1220133600000, 0.68170], + [1220220000000, 0.68170], + [1220306400000, 0.68320], + [1220392800000, 0.68770], + [1220479200000, 0.69120], + [1220565600000, 0.69140], + [1220652000000, 0.70090], + [1220738400000, 0.70120], + [1220824800000, 0.7010], + [1220911200000, 0.70050] + ]; + + function euroFormatter(v, axis) { + return v.toFixed(axis.tickDecimals) + "€"; + } + + function doPlot(position) { + $.plot($("#flot-line-chart-multi"), [{ + data: oilprices, + label: "Oil price ($)" + }, { + data: exchangerates, + label: "USD/EUR exchange rate", + yaxis: 2 + }], { + xaxes: [{ + mode: 'time' + }], + yaxes: [{ + min: 0 + }, { + // align if we are to the right + alignTicksWithAxis: position == "right" ? 1 : null, + position: position, + tickFormatter: euroFormatter + }], + legend: { + position: 'sw' + }, + grid: { + hoverable: true //IMPORTANT! this is needed for tooltip to work + }, + tooltip: true, + tooltipOpts: { + content: "%s for %x was %y", + xDateFormat: "%y-%0m-%0d", + + onHover: function(flotItem, $tooltipEl) { + // console.log(flotItem, $tooltipEl); + } + } + + }); + } + + doPlot("right"); + + $("button").click(function() { + doPlot($(this).text()); + }); +}); + +//Flot Moving Line Chart + +$(function() { + + var container = $("#flot-line-chart-moving"); + + // Determine how many data points to keep based on the placeholder's initial size; + // this gives us a nice high-res plot while avoiding more than one point per pixel. + + var maximum = container.outerWidth() / 2 || 300; + + // + + var data = []; + + function getRandomData() { + + if (data.length) { + data = data.slice(1); + } + + while (data.length < maximum) { + var previous = data.length ? data[data.length - 1] : 50; + var y = previous + Math.random() * 10 - 5; + data.push(y < 0 ? 0 : y > 100 ? 100 : y); + } + + // zip the generated y values with the x values + + var res = []; + for (var i = 0; i < data.length; ++i) { + res.push([i, data[i]]) + } + + return res; + } + + // + + series = [{ + data: getRandomData(), + lines: { + fill: true + } + }]; + + // + + var plot = $.plot(container, series, { + grid: { + borderWidth: 1, + minBorderMargin: 20, + labelMargin: 10, + backgroundColor: { + colors: ["#fff", "#e4f4f4"] + }, + margin: { + top: 8, + bottom: 20, + left: 20 + }, + markings: function(axes) { + var markings = []; + var xaxis = axes.xaxis; + for (var x = Math.floor(xaxis.min); x < xaxis.max; x += xaxis.tickSize * 2) { + markings.push({ + xaxis: { + from: x, + to: x + xaxis.tickSize + }, + color: "rgba(232, 232, 255, 0.2)" + }); + } + return markings; + } + }, + xaxis: { + tickFormatter: function() { + return ""; + } + }, + yaxis: { + min: 0, + max: 110 + }, + legend: { + show: true + } + }); + + // Update the random dataset at 25FPS for a smoothly-animating chart + + setInterval(function updateRandom() { + series[0].data = getRandomData(); + plot.setData(series); + plot.draw(); + }, 40); + +}); + +//Flot Bar Chart + +$(function() { + + var barOptions = { + series: { + bars: { + show: true, + barWidth: 43200000 + } + }, + xaxis: { + mode: "time", + timeformat: "%m/%d", + minTickSize: [1, "day"] + }, + grid: { + hoverable: true + }, + legend: { + show: false + }, + tooltip: true, + tooltipOpts: { + content: "x: %x, y: %y" + } + }; + var barData = { + label: "bar", + data: [ + [1354521600000, 1000], + [1355040000000, 2000], + [1355223600000, 3000], + [1355306400000, 4000], + [1355487300000, 5000], + [1355571900000, 6000] + ] + }; + $.plot($("#flot-bar-chart"), [barData], barOptions); + +}); diff --git a/js/plugins/flot/jquery.flot.js b/js/plugins/flot/jquery.flot.js new file mode 100644 index 0000000..78c5718 --- /dev/null +++ b/js/plugins/flot/jquery.flot.js @@ -0,0 +1,2599 @@ +/*! 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=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 KI?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) { + // return an object with x/y corresponding to all used axes + var res = {}, i, axis; + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) + res["x" + axis.n] = axis.c2p(pos.left); + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) + res["y" + axis.n] = axis.c2p(pos.top); + } + + if (res.x1 !== undefined) + res.x = res.x1; + if (res.y1 !== undefined) + res.y = res.y1; + + return res; + } + + function axisToCanvasCoords(pos) { + // get canvas coords from the first pair of x/y found in pos + var res = {}, i, axis, key; + + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) { + key = "x" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "x"; + + if (pos[key] != null) { + res.left = axis.p2c(pos[key]); + break; + } + } + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) { + key = "y" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "y"; + + if (pos[key] != null) { + res.top = axis.p2c(pos[key]); + break; + } + } + } + + return res; + } + + function getOrCreateAxis(axes, number) { + if (!axes[number - 1]) + axes[number - 1] = { + n: number, // save the number for future reference + direction: axes == xaxes ? "x" : "y", + options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) + }; + + return axes[number - 1]; + } + + function fillInSeriesOptions() { + var i; + + // collect what we already got of colors + var neededColors = series.length, + usedColors = [], + assignedColors = []; + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + --neededColors; + if (typeof sc == "number") + assignedColors.push(sc); + else + usedColors.push($.color.parse(series[i].color)); + } + } + + // we might need to generate more colors if higher indices + // are assigned + for (i = 0; i < assignedColors.length; ++i) { + neededColors = Math.max(neededColors, assignedColors[i] + 1); + } + + // produce colors as needed + var colors = [], variation = 0; + i = 0; + while (colors.length < neededColors) { + var c; + if (options.colors.length == i) // check degenerate case + c = $.color.make(100, 100, 100); + else + c = $.color.parse(options.colors[i]); + + // vary color if needed + var sign = variation % 2 == 1 ? -1 : 1; + c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) + + // FIXME: if we're getting to close to something else, + // we should probably skip this one + colors.push(c); + + ++i; + if (i >= options.colors.length) { + i = 0; + ++variation; + } + } + + // fill in the options + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v] && s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // setup axes + s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); + s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + fakeInfinity = Number.MAX_VALUE, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p; + + function updateAxis(axis, min, max) { + if (min < axis.datamin && min != -fakeInfinity) + axis.datamin = min; + if (max > axis.datamax && max != fakeInfinity) + axis.datamax = max; + } + + $.each(allAxes(), function (_, axis) { + // init axis + axis.datamin = topSentry; + axis.datamax = bottomSentry; + axis.used = false; + }); + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + var data = s.data, format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show || (s.lines.show && s.lines.fill)) { + format.push({ y: true, number: true, required: false, defaultValue: 0 }); + if (s.bars.horizontal) { + delete format[format.length - 1].y; + format[format.length - 1].x = true; + } + } + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + else if (val == Infinity) + val = fakeInfinity; + else if (val == -Infinity) + val = -fakeInfinity; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.x) + updateAxis(s.xaxis, val, val); + if (f.y) + updateAxis(s.yaxis, val, val); + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points, + ps = s.datapoints.pointsize; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f || val == fakeInfinity || val == -fakeInfinity) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + $.each(allAxes(), function (_, axis) { + if (axis.datamin == topSentry) + axis.datamin = null; + if (axis.datamax == bottomSentry) + axis.datamax = null; + }); + } + + function makeCanvas(skipPositioning, cls) { + var c = document.createElement('canvas'); + c.className = cls; + c.width = canvasWidth; + c.height = canvasHeight; + + if (!skipPositioning) + $(c).css({ position: 'absolute', left: 0, top: 0 }); + + $(c).appendTo(placeholder); + + if (!c.getContext) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); + + // used for resetting in case we get replotted + c.getContext("2d").save(); + + return c; + } + + function getCanvasDimensions() { + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + + if (canvasWidth <= 0 || canvasHeight <= 0) + throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; + } + + function resizeCanvas(c) { + // resizing should reset the state (excanvas seems to be + // buggy though) + if (c.width != canvasWidth) + c.width = canvasWidth; + + if (c.height != canvasHeight) + c.height = canvasHeight; + + // so try to get back to the initial state (even if it's + // gone now, this should be safe according to the spec) + var cctx = c.getContext("2d"); + cctx.restore(); + + // and save again + cctx.save(); + } + + function setupCanvases() { + var reused, + existingCanvas = placeholder.children("canvas.base"), + existingOverlay = placeholder.children("canvas.overlay"); + + if (existingCanvas.length == 0 || existingOverlay == 0) { + // init everything + + placeholder.html(""); // make sure placeholder is clear + + placeholder.css({ padding: 0 }); // padding messes up the positioning + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + getCanvasDimensions(); + + canvas = makeCanvas(true, "base"); + overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features + + reused = false; + } + else { + // reuse existing elements + + canvas = existingCanvas.get(0); + overlay = existingOverlay.get(0); + + reused = true; + } + + ctx = canvas.getContext("2d"); + octx = overlay.getContext("2d"); + + // we include the canvas in the event holder too, because IE 7 + // sometimes has trouble with the stacking order + eventHolder = $([overlay, canvas]); + + if (reused) { + // run shutdown in the old plot object + placeholder.data("plot").shutdown(); + + // reset reused canvases + plot.resize(); + + // make sure overlay pixels are cleared (canvas is cleared when we redraw) + octx.clearRect(0, 0, canvasWidth, canvasHeight); + + // then whack any remaining obvious garbage left + eventHolder.unbind(); + placeholder.children().not([canvas, overlay]).remove(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + eventHolder.mouseleave(onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + + function setTransformationHelpers(axis) { + // set helper functions on the axis, assumes plot area + // has been computed already + + function identity(x) { return x; } + + var s, m, t = axis.options.transform || identity, + it = axis.options.inverseTransform; + + // precompute how much the axis is scaling a point + // in canvas space + if (axis.direction == "x") { + s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); + m = Math.min(t(axis.max), t(axis.min)); + } + else { + s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); + s = -s; + m = Math.max(t(axis.max), t(axis.min)); + } + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + + function measureTickLabels(axis) { + var opts = axis.options, i, ticks = axis.ticks || [], labels = [], + l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; + + function makeDummyDiv(labels, width) { + return $('
' + + '
' + + labels.join("") + '
') + .appendTo(placeholder); + } + + if (axis.direction == "x") { + // to avoid measuring the widths of the labels (it's slow), we + // construct fixed-size boxes and put the labels inside + // them, we don't need the exact figures and the + // fixed-size box content is easy to center + if (w == null) + w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1)); + + // measure x label heights + if (h == null) { + labels = []; + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + // stick them all in the same div and measure + // collective height + labels.push('
'); + dummyDiv = makeDummyDiv(labels, "width:10000px;"); + h = dummyDiv.height(); + dummyDiv.remove(); + } + } + } + else if (w == null || h == null) { + // calculate y label dimensions + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + dummyDiv = makeDummyDiv(labels, ""); + if (w == null) + w = dummyDiv.children().width(); + if (h == null) + h = dummyDiv.find("div.tickLabel").height(); + dummyDiv.remove(); + } + } + + if (w == null) + w = 0; + if (h == null) + h = 0; + + axis.labelWidth = w; + axis.labelHeight = h; + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + tickLength = axis.options.tickLength, + axismargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + all = axis.direction == "x" ? xaxes : yaxes, + index; + + // determine axis margin + var samePosition = $.grep(all, function (a) { + return a && a.options.position == pos && a.reserveSpace; + }); + if ($.inArray(axis, samePosition) == samePosition.length - 1) + axismargin = 0; // outermost + + // determine tick length - if we're innermost, we can use "full" + if (tickLength == null) + tickLength = "full"; + + var sameDirection = $.grep(all, function (a) { + return a && a.reserveSpace; + }); + + var innermost = $.inArray(axis, sameDirection) == 0; + if (!innermost && tickLength == "full") + tickLength = 5; + + if (!isNaN(+tickLength)) + padding += +tickLength; + + // compute box + if (axis.direction == "x") { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axismargin; + axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; + } + else { + axis.box = { top: plotOffset.top + axismargin, height: lh }; + plotOffset.top += lh + axismargin; + } + } + else { + lw += padding; + + if (pos == "left") { + axis.box = { left: plotOffset.left + axismargin, width: lw }; + plotOffset.left += lw + axismargin; + } + else { + plotOffset.right += lw + axismargin; + axis.box = { left: canvasWidth - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // set remaining bounding box coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left; + axis.box.width = plotWidth; + } + else { + axis.box.top = plotOffset.top; + axis.box.height = plotHeight; + } + } + + function setupGrid() { + var i, axes = allAxes(); + + // first calculate the plot and axis box dimensions + + $.each(axes, function (_, axis) { + axis.show = axis.options.show; + if (axis.show == null) + axis.show = axis.used; // by default an axis is visible if it's got data + + axis.reserveSpace = axis.show || axis.options.reserveSpace; + + setRange(axis); + }); + + allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + + plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; + if (options.grid.show) { + $.each(allocatedAxes, function (_, axis) { + // make the ticks + setupTickGeneration(axis); + setTicks(axis); + snapRangeToTicks(axis, axis.ticks); + + // find labelWidth/Height for axis + measureTickLabels(axis); + }); + + // with all dimensions in house, we can compute the + // axis boxes, start from the outside (reverse order) + for (i = allocatedAxes.length - 1; i >= 0; --i) + allocateAxisBoxFirstPhase(allocatedAxes[i]); + + // make sure we've got enough space for things that + // might stick out + var minMargin = options.grid.minBorderMargin; + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2); + } + + for (var a in plotOffset) { + plotOffset[a] += options.grid.borderWidth; + plotOffset[a] = Math.max(minMargin, plotOffset[a]); + } + } + + plotWidth = canvasWidth - plotOffset.left - plotOffset.right; + plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + + // now we got the proper plotWidth/Height, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + if (options.grid.show) { + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + + insertAxisLabels(); + } + + insertLegend(); + } + + function setRange(axis) { + var opts = axis.options, + min = +(opts.min != null ? opts.min : axis.datamin), + max = +(opts.max != null ? opts.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (opts.min == null) + min -= widen; + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (opts.max == null || opts.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = opts.autoscaleMargin; + if (margin != null) { + if (opts.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (opts.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function setupTickGeneration(axis) { + var opts = axis.options; + + // estimate number of ticks + var noTicks; + if (typeof opts.ticks == "number" && opts.ticks > 0) + noTicks = opts.ticks; + else + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); + + var delta = (axis.max - axis.min) / noTicks, + size, generator, unit, formatter, i, magn, norm; + + if (opts.mode == "time") { + // pretty handling of time + + // 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, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + var spec = [ + [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"], [3, "month"], [6, "month"], + [1, "year"] + ]; + + var minSize = 0; + 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 (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; + size = spec[i][0]; + unit = spec[i][1]; + + // special-case the possibility of several years + if (unit == "year") { + magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); + norm = (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; + } + + axis.tickSize = opts.tickSize || [size, unit]; + + generator = function(axis) { + var ticks = [], + tickSize = axis.tickSize[0], unit = axis.tickSize[1], + d = new Date(axis.min); + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") + d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); + if (unit == "minute") + d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); + if (unit == "hour") + d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); + if (unit == "month") + d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); + if (unit == "year") + d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); + + // reset smaller components + d.setUTCMilliseconds(0); + if (step >= timeUnitSize.minute) + d.setUTCSeconds(0); + if (step >= timeUnitSize.hour) + d.setUTCMinutes(0); + if (step >= timeUnitSize.day) + d.setUTCHours(0); + if (step >= timeUnitSize.day * 4) + d.setUTCDate(1); + if (step >= timeUnitSize.year) + d.setUTCMonth(0); + + + var carry = 0, v = Number.NaN, prev; + do { + prev = v; + v = d.getTime(); + ticks.push(v); + if (unit == "month") { + if (tickSize < 1) { + // a bit complicated - we'll divide the month + // up but we need to take care of fractions + // so we don't end up in the middle of a day + d.setUTCDate(1); + var start = d.getTime(); + d.setUTCMonth(d.getUTCMonth() + 1); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getUTCHours(); + d.setUTCHours(0); + } + else + d.setUTCMonth(d.getUTCMonth() + tickSize); + } + else if (unit == "year") { + d.setUTCFullYear(d.getUTCFullYear() + tickSize); + } + else + d.setTime(v + step); + } while (v < axis.max && v != prev); + + return ticks; + }; + + formatter = function (v, axis) { + var d = new Date(v); + + // first check global format + if (opts.timeformat != null) + return $.plot.formatDate(d, opts.timeformat, opts.monthNames); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + + if (t < timeUnitSize.minute) + fmt = "%h:%M:%S" + suffix; + else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) + fmt = "%h:%M" + suffix; + else + fmt = "%b %d %h:%M" + suffix; + } + else if (t < timeUnitSize.month) + fmt = "%b %d"; + else if (t < timeUnitSize.year) { + if (span < timeUnitSize.year) + fmt = "%b"; + else + fmt = "%b %y"; + } + else + fmt = "%y"; + + return $.plot.formatDate(d, fmt, opts.monthNames); + }; + } + else { + // pretty rounding of base-10 numbers + var maxDec = opts.tickDecimals; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + if (maxDec != null && dec > maxDec) + dec = maxDec; + + magn = Math.pow(10, -dec); + norm = delta / magn; // norm is between 1.0 and 10.0 + + if (norm < 1.5) + size = 1; + else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) + size = opts.minTickSize; + + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + generator = function (axis) { + var ticks = []; + + // spew out all possible ticks + var start = floorInBase(axis.min, axis.tickSize), + i = 0, v = Number.NaN, prev; + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push(v); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + formatter = function (v, axis) { + return v.toFixed(axis.tickDecimals); + }; + } + + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = generator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + generator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (axis.mode != "time" && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), + ts = generator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + + axis.tickGenerator = generator; + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + else + axis.tickFormatter = formatter; + } + + function setTicks(axis) { + var oticks = axis.options.ticks, ticks = []; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) + // generate the ticks + ticks = oticks({ min: axis.min, max: axis.max }); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + axis.ticks = []; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = +t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = +t; + if (label == null) + label = axis.tickFormatter(v, axis); + if (!isNaN(v)) + axis.ticks.push({ v: v, label: label }); + } + } + + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin && ticks.length > 0) { + // snap to ticks + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); + } + } + + function draw() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + var grid = options.grid; + + // draw background, if any + if (grid.show && grid.backgroundColor) + drawBackground(); + + if (grid.show && !grid.aboveData) + drawGrid(); + + for (var i = 0; i < series.length; ++i) { + executeHooks(hooks.drawSeries, [ctx, series[i]]); + drawSeries(series[i]); + } + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) + drawGrid(); + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (i = 0; i < axes.length; ++i) { + axis = axes[i]; + 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" ? xaxes[0] : yaxes[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 drawBackground() { + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + ctx.restore(); + } + + function drawGrid() { + var i; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + var axes = plot.getAxes(); + // xmin etc. is backwards compatibility, to be + // removed in the future + axes.xmin = axes.xaxis.min; + axes.xmax = axes.xaxis.max; + axes.ymin = axes.yaxis.min; + axes.ymax = axes.yaxis.max; + + markings = markings(axes); + } + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + var axes = allAxes(), bw = options.grid.borderWidth; + + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box, + t = axis.tickLength, x, y, xoff, yoff; + if (!axis.show || axis.ticks.length == 0) + continue + + ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); + ctx.lineWidth = 1; + + // find the edges + if (axis.direction == "x") { + x = 0; + if (t == "full") + y = (axis.position == "top" ? 0 : plotHeight); + else + y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); + } + else { + y = 0; + if (t == "full") + x = (axis.position == "left" ? 0 : plotWidth); + else + x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); + } + + // draw tick bar + if (!axis.innermost) { + ctx.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth; + else + yoff = plotHeight; + + if (ctx.lineWidth == 1) { + x = Math.floor(x) + 0.5; + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" && bw > 0 + && (v == axis.min || v == axis.max))) + continue; + + if (axis.direction == "x") { + x = axis.p2c(v); + yoff = t == "full" ? -plotHeight : t; + + if (axis.position == "top") + yoff = -yoff; + } + else { + y = axis.p2c(v); + xoff = t == "full" ? -plotWidth : t; + + if (axis.position == "left") + xoff = -xoff; + } + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") + x = Math.floor(x) + 0.5; + else + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + } + + ctx.stroke(); + } + + + // draw border + if (bw) { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + + ctx.restore(); + } + + function insertAxisLabels() { + placeholder.find(".tickLabels").remove(); + + var html = ['
']; + + var axes = allAxes(); + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box; + if (!axis.show) + continue; + //debug: html.push('
') + html.push('
'); + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + var pos = {}, align; + + if (axis.direction == "x") { + align = "center"; + pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2); + if (axis.position == "bottom") + pos.top = box.top + box.padding; + else + pos.bottom = canvasHeight - (box.top + box.height - box.padding); + } + else { + pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2); + if (axis.position == "left") { + pos.right = canvasWidth - (box.left + box.width - box.padding) + align = "right"; + } + else { + pos.left = box.left + box.padding; + align = "left"; + } + } + + pos.width = axis.labelWidth; + + var style = ["position:absolute", "text-align:" + align ]; + for (var a in pos) + style.push(a + ":" + pos[a] + "px") + + html.push('
' + tick.label + '
'); + } + html.push('
'); + } + + html.push('
'); + + placeholder.append(html.join("")); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + 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); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 2; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // 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 (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.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; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + x = axisx.p2c(x); + y = axisy.p2c(y) + offset; + if (symbol == "circle") + ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); + else + symbol(ctx, x, y, radius, shadow); + ctx.closePath(); + + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.points.lineWidth, + sw = series.shadowSize, + radius = series.points.radius, + symbol = series.points.symbol; + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, true, + series.xaxis, series.yaxis, symbol); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, true, + series.xaxis, series.yaxis, symbol); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, false, + series.xaxis, series.yaxis, symbol); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + // in horizontal mode, we start the bar from the left + // instead of from the bottom so it appears to be + // horizontal rather than vertical + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.beginPath(); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); + c.fill(); + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom + offset); + if (drawLeft) + c.lineTo(left, top + offset); + else + c.moveTo(left, top + offset); + if (drawTop) + c.lineTo(right, top + offset); + else + c.moveTo(right, top + offset); + if (drawRight) + c.lineTo(right, bottom + offset); + else + c.moveTo(right, bottom + offset); + if (drawBottom) + c.lineTo(left, bottom + offset); + else + c.moveTo(left, bottom + offset); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + for (var i = 0; i < series.length; ++i) { + s = series[i]; + label = s.label; + if (!label) + continue; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push(''); + rowStarted = true; + } + + if (lf) + label = lf(label, s); + + fragments.push( + '
' + + '' + label + ''); + } + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '' + fragments.join("") + '
'; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j; + + for (i = series.length - 1; i >= 0; --i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + ps = s.datapoints.pointsize, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + // with inverse transforms, we can't use the maxx/maxy + // optimization, sadly + if (axisx.options.inverseTransform) + maxx = Number.MAX_VALUE; + if (axisy.options.inverseTransform) + maxy = Number.MAX_VALUE; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist < smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onMouseLeave(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top, + pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + + pos.pageX = event.pageX; + pos.pageY = event.pageY; + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); + } + + if (options.grid.autoHighlight) { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && + !(item && h.series == item.series && + h.point[0] == item.datapoint[0] && + h.point[1] == item.datapoint[1])) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, 30); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + octx.clearRect(0, 0, canvasWidth, canvasHeight); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis; + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var radius = 1.5 * pointRadius, + x = axisx.p2c(x), + y = axisy.p2c(y); + + octx.beginPath(); + if (series.points.symbol == "circle") + octx.arc(x, y, radius, 0, 2 * Math.PI, false); + else + series.points.symbol(octx, x, y, radius, false); + octx.closePath(); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + var co = $.color.parse(defaultColor); + if (c.brightness != null) + co = co.scale('rgb', c.brightness) + if (c.opacity != null) + co.a *= c.opacity; + c = co.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + $.plot = function(placeholder, data, options) { + //var t0 = new Date(); + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); + return plot; + }; + + $.plot.version = "0.7"; + + $.plot.plugins = []; + + // returns a string with the date d formatted according to fmt + $.plot.formatDate = function(d, fmt, monthNames) { + var leftPad = function(n) { + n = "" + n; + return n.length == 1 ? "0" + n : n; + }; + + var r = []; + var escape = false, padNext = false; + var hours = d.getUTCHours(); + var isAM = hours < 12; + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + if (fmt.search(/%p|%P/) != -1) { + if (hours > 12) { + hours = hours - 12; + } else if (hours == 0) { + hours = 12; + } + } + for (var i = 0; i < fmt.length; ++i) { + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'h': c = "" + hours; break; + case 'H': c = leftPad(hours); break; + case 'M': c = leftPad(d.getUTCMinutes()); break; + case 'S': c = leftPad(d.getUTCSeconds()); break; + case 'd': c = "" + d.getUTCDate(); break; + case 'm': c = "" + (d.getUTCMonth() + 1); break; + case 'y': c = "" + d.getUTCFullYear(); break; + case 'b': c = "" + monthNames[d.getUTCMonth()]; break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case '0': c = ""; padNext = true; break; + } + if (c && padNext) { + c = leftPad(c); + padNext = false; + } + r.push(c); + if (!padNext) + escape = false; + } + else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + return r.join(""); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/js/plugins/flot/jquery.flot.pie.js b/js/plugins/flot/jquery.flot.pie.js new file mode 100644 index 0000000..70941dd --- /dev/null +++ b/js/plugins/flot/jquery.flot.pie.js @@ -0,0 +1,750 @@ +/* +Flot plugin for rendering pie charts. The plugin assumes the data is +coming is as a single data value for each series, and each of those +values is a positive value or zero (negative numbers don't make +any sense and will cause strange effects). The data values do +NOT need to be passed in as percentage values because it +internally calculates the total and percentages. + +* Created by Brian Medendorp, June 2009 +* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars + +* Changes: + 2009-10-22: lineJoin set to round + 2009-10-23: IE full circle fix, donut + 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera + 2009-11-17: Added IE hover capability submitted by Anthony Aragues + 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well) + + +Available options are: +series: { + pie: { + show: true/false + radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' + innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect + startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result + tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) + offset: { + top: integer value to move the pie up or down + left: integer value to move the pie left or right, or 'auto' + }, + stroke: { + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') + width: integer pixel width of the stroke + }, + label: { + show: true/false, or 'auto' + formatter: a user-defined function that modifies the text/style of the label text + radius: 0-1 for percentage of fullsize, or a specified pixel length + background: { + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') + opacity: 0-1 + }, + threshold: 0-1 for the percentage value at which to hide labels (if they're too small) + }, + combine: { + threshold: 0-1 for the percentage value at which to combine slices (if they're too small) + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined + label: any text value of what the combined slice should be labeled + } + highlight: { + opacity: 0-1 + } + } +} + +More detail and specific examples can be found in the included HTML file. + +*/ + +(function ($) +{ + function init(plot) // this is the "body" of the plugin + { + var canvas = null; + var target = null; + var maxRadius = null; + var centerLeft = null; + var centerTop = null; + var total = 0; + var redraw = true; + var redrawAttempts = 10; + var shrink = 0.95; + var legendWidth = 0; + var processed = false; + var raw = false; + + // interactive variables + var highlights = []; + + // add hook to determine if pie plugin in enabled, and then perform necessary operations + plot.hooks.processOptions.push(checkPieEnabled); + plot.hooks.bindEvents.push(bindEvents); + + // check to see if the pie plugin is enabled + function checkPieEnabled(plot, options) + { + if (options.series.pie.show) + { + //disable grid + options.grid.show = false; + + // set labels.show + if (options.series.pie.label.show=='auto') + if (options.legend.show) + options.series.pie.label.show = false; + else + options.series.pie.label.show = true; + + // set radius + if (options.series.pie.radius=='auto') + if (options.series.pie.label.show) + options.series.pie.radius = 3/4; + else + options.series.pie.radius = 1; + + // ensure sane tilt + if (options.series.pie.tilt>1) + options.series.pie.tilt=1; + if (options.series.pie.tilt<0) + options.series.pie.tilt=0; + + // add processData hook to do transformations on the data + plot.hooks.processDatapoints.push(processDatapoints); + plot.hooks.drawOverlay.push(drawOverlay); + + // add draw hook + plot.hooks.draw.push(draw); + } + } + + // bind hoverable events + function bindEvents(plot, eventHolder) + { + var options = plot.getOptions(); + + if (options.series.pie.show && options.grid.hoverable) + eventHolder.unbind('mousemove').mousemove(onMouseMove); + + if (options.series.pie.show && options.grid.clickable) + eventHolder.unbind('click').click(onClick); + } + + + // debugging function that prints out an object + function alertObject(obj) + { + var msg = ''; + function traverse(obj, depth) + { + if (!depth) + depth = 0; + for (var i = 0; i < obj.length; ++i) + { + for (var j=0; jcanvas.width-maxRadius) + centerLeft = canvas.width-maxRadius; + } + + function fixData(data) + { + for (var i = 0; i < data.length; ++i) + { + if (typeof(data[i].data)=='number') + data[i].data = [[1,data[i].data]]; + else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') + { + if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') + data[i].label = data[i].data.label; // fix weirdness coming from flot + data[i].data = [[1,0]]; + + } + } + return data; + } + + function combine(data) + { + data = fixData(data); + calcTotal(data); + var combined = 0; + var numCombined = 0; + var color = options.series.pie.combine.color; + + var newdata = []; + for (var i = 0; i < data.length; ++i) + { + // make sure its a number + data[i].data[0][1] = parseFloat(data[i].data[0][1]); + if (!data[i].data[0][1]) + data[i].data[0][1] = 0; + + if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) + { + combined += data[i].data[0][1]; + numCombined++; + if (!color) + color = data[i].color; + } + else + { + newdata.push({ + data: [[1,data[i].data[0][1]]], + color: data[i].color, + label: data[i].label, + angle: (data[i].data[0][1]*(Math.PI*2))/total, + percent: (data[i].data[0][1]/total*100) + }); + } + } + if (numCombined>0) + newdata.push({ + data: [[1,combined]], + color: color, + label: options.series.pie.combine.label, + angle: (combined*(Math.PI*2))/total, + percent: (combined/total*100) + }); + return newdata; + } + + function draw(plot, newCtx) + { + if (!target) return; // if no series were passed + ctx = newCtx; + + setupPie(); + var slices = plot.getData(); + + var attempts = 0; + while (redraw && attempts0) + maxRadius *= shrink; + attempts += 1; + clear(); + if (options.series.pie.tilt<=0.8) + drawShadow(); + drawPie(); + } + if (attempts >= redrawAttempts) { + clear(); + target.prepend('
Could not draw pie with labels contained inside canvas
'); + } + + if ( plot.setSeries && plot.insertLegend ) + { + plot.setSeries(slices); + plot.insertLegend(); + } + + // we're actually done at this point, just defining internal functions at this point + + function clear() + { + ctx.clearRect(0,0,canvas.width,canvas.height); + target.children().filter('.pieLabel, .pieLabelBackground').remove(); + } + + function drawShadow() + { + var shadowLeft = 5; + var shadowTop = 15; + var edge = 10; + var alpha = 0.02; + + // set radius + if (options.series.pie.radius>1) + var radius = options.series.pie.radius; + else + var radius = maxRadius * options.series.pie.radius; + + if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) + return; // shadow would be outside canvas, so don't draw it + + ctx.save(); + ctx.translate(shadowLeft,shadowTop); + ctx.globalAlpha = alpha; + ctx.fillStyle = '#000'; + + // center and rotate to starting position + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + + //radius -= edge; + for (var i=1; i<=edge; i++) + { + ctx.beginPath(); + ctx.arc(0,0,radius,0,Math.PI*2,false); + ctx.fill(); + radius -= i; + } + + ctx.restore(); + } + + function drawPie() + { + startAngle = Math.PI*options.series.pie.startAngle; + + // set radius + if (options.series.pie.radius>1) + var radius = options.series.pie.radius; + else + var radius = maxRadius * options.series.pie.radius; + + // center and rotate to starting position + ctx.save(); + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera + + // draw slices + ctx.save(); + var currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) + { + slices[i].startAngle = currentAngle; + drawSlice(slices[i].angle, slices[i].color, true); + } + ctx.restore(); + + // draw slice outlines + ctx.save(); + ctx.lineWidth = options.series.pie.stroke.width; + currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) + drawSlice(slices[i].angle, options.series.pie.stroke.color, false); + ctx.restore(); + + // draw donut hole + drawDonutHole(ctx); + + // draw labels + if (options.series.pie.label.show) + drawLabels(); + + // restore to original state + ctx.restore(); + + function drawSlice(angle, color, fill) + { + if (angle<=0) + return; + + if (fill) + ctx.fillStyle = color; + else + { + ctx.strokeStyle = color; + ctx.lineJoin = 'round'; + } + + ctx.beginPath(); + if (Math.abs(angle - Math.PI*2) > 0.000000001) + ctx.moveTo(0,0); // Center of the pie + else if ($.browser.msie) + angle -= 0.0001; + //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera + ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); + ctx.closePath(); + //ctx.rotate(angle); // This doesn't work properly in Opera + currentAngle += angle; + + if (fill) + ctx.fill(); + else + ctx.stroke(); + } + + function drawLabels() + { + var currentAngle = startAngle; + + // set radius + if (options.series.pie.label.radius>1) + var radius = options.series.pie.label.radius; + else + var radius = maxRadius * options.series.pie.label.radius; + + for (var i = 0; i < slices.length; ++i) + { + if (slices[i].percent >= options.series.pie.label.threshold*100) + drawLabel(slices[i], currentAngle, i); + currentAngle += slices[i].angle; + } + + function drawLabel(slice, startAngle, index) + { + if (slice.data[0][1]==0) + return; + + // format label text + var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; + if (lf) + text = lf(slice.label, slice); + else + text = slice.label; + if (plf) + text = plf(text, slice); + + var halfAngle = ((startAngle+slice.angle) + startAngle)/2; + var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); + var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; + + var html = '' + text + ""; + target.append(html); + var label = target.children('#pieLabel'+index); + var labelTop = (y - label.height()/2); + var labelLeft = (x - label.width()/2); + label.css('top', labelTop); + label.css('left', labelLeft); + + // check to make sure that the label is not outside the canvas + if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) + redraw = true; + + if (options.series.pie.label.background.opacity != 0) { + // put in the transparent background separately to avoid blended labels and label boxes + var c = options.series.pie.label.background.color; + if (c == null) { + c = slice.color; + } + var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; + $('
').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); + } + } // end individual label function + } // end drawLabels function + } // end drawPie function + } // end draw function + + // Placed here because it needs to be accessed from multiple locations + function drawDonutHole(layer) + { + // draw donut hole + if(options.series.pie.innerRadius > 0) + { + // subtract the center + layer.save(); + innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; + layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color + layer.beginPath(); + layer.fillStyle = options.series.pie.stroke.color; + layer.arc(0,0,innerRadius,0,Math.PI*2,false); + layer.fill(); + layer.closePath(); + layer.restore(); + + // add inner stroke + layer.save(); + layer.beginPath(); + layer.strokeStyle = options.series.pie.stroke.color; + layer.arc(0,0,innerRadius,0,Math.PI*2,false); + layer.stroke(); + layer.closePath(); + layer.restore(); + // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. + } + } + + //-- Additional Interactive related functions -- + + function isPointInPoly(poly, pt) + { + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) + && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) + && (c = !c); + return c; + } + + function findNearbySlice(mouseX, mouseY) + { + var slices = plot.getData(), + options = plot.getOptions(), + radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + for (var i = 0; i < slices.length; ++i) + { + var s = slices[i]; + + if(s.pie.show) + { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0,0); // Center of the pie + //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. + ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); + ctx.closePath(); + x = mouseX-centerLeft; + y = mouseY-centerTop; + if(ctx.isPointInPath) + { + if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) + { + //alert('found slice!'); + ctx.restore(); + return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; + } + } + else + { + // excanvas for IE doesn;t support isPointInPath, this is a workaround. + p1X = (radius * Math.cos(s.startAngle)); + p1Y = (radius * Math.sin(s.startAngle)); + p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); + p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); + p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); + p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); + p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); + p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); + p5X = (radius * Math.cos(s.startAngle+s.angle)); + p5Y = (radius * Math.sin(s.startAngle+s.angle)); + arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; + arrPoint = [x,y]; + // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? + if(isPointInPoly(arrPoly, arrPoint)) + { + ctx.restore(); + return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; + } + } + ctx.restore(); + } + } + + return null; + } + + function onMouseMove(e) + { + triggerClickHoverEvent('plothover', e); + } + + function onClick(e) + { + triggerClickHoverEvent('plotclick', e); + } + + // trigger click or hover event (they send the same parameters so we share their code) + function triggerClickHoverEvent(eventname, e) + { + var offset = plot.offset(), + canvasX = parseInt(e.pageX - offset.left), + canvasY = parseInt(e.pageY - offset.top), + item = findNearbySlice(canvasX, canvasY); + + if (options.grid.autoHighlight) + { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) + { + var h = highlights[i]; + if (h.auto == eventname && !(item && h.series == item.series)) + unhighlight(h.series); + } + } + + // highlight the slice + if (item) + highlight(item.series, eventname); + + // trigger any hover bind events + var pos = { pageX: e.pageX, pageY: e.pageY }; + target.trigger(eventname, [ pos, item ]); + } + + function highlight(s, auto) + { + if (typeof s == "number") + s = series[s]; + + var i = indexOfHighlight(s); + if (i == -1) + { + highlights.push({ series: s, auto: auto }); + plot.triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s) + { + if (s == null) + { + highlights = []; + plot.triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + var i = indexOfHighlight(s); + if (i != -1) + { + highlights.splice(i, 1); + plot.triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s) + { + for (var i = 0; i < highlights.length; ++i) + { + var h = highlights[i]; + if (h.series == s) + return i; + } + return -1; + } + + function drawOverlay(plot, octx) + { + //alert(options.series.pie.radius); + var options = plot.getOptions(); + //alert(options.series.pie.radius); + + var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + octx.save(); + octx.translate(centerLeft, centerTop); + octx.scale(1, options.series.pie.tilt); + + for (i = 0; i < highlights.length; ++i) + drawHighlight(highlights[i].series); + + drawDonutHole(octx); + + octx.restore(); + + function drawHighlight(series) + { + if (series.angle < 0) return; + + //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); + octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor + + octx.beginPath(); + if (Math.abs(series.angle - Math.PI*2) > 0.000000001) + octx.moveTo(0,0); // Center of the pie + octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); + octx.closePath(); + octx.fill(); + } + + } + + } // end init (plugin body) + + // define pie specific options and their default values + var options = { + series: { + pie: { + show: false, + radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) + innerRadius:0, /* for donut */ + startAngle: 3/2, + tilt: 1, + offset: { + top: 0, + left: 'auto' + }, + stroke: { + color: '#FFF', + width: 1 + }, + label: { + show: 'auto', + formatter: function(label, slice){ + return '
'+label+'
'+Math.round(slice.percent)+'%
'; + }, // formatter function + radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) + background: { + color: null, + opacity: 0 + }, + threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) + }, + combine: { + threshold: -1, // percentage at which to combine little slices into one larger slice + color: null, // color to give the new slice (auto-generated if null) + label: 'Other' // label to give the new slice + }, + highlight: { + //color: '#FFF', // will add this functionality once parseColor is available + opacity: 0.5 + } + } + } + }; + + $.plot.plugins.push({ + init: init, + options: options, + name: "pie", + version: "1.0" + }); +})(jQuery); diff --git a/js/plugins/flot/jquery.flot.resize.js b/js/plugins/flot/jquery.flot.resize.js new file mode 100644 index 0000000..6ee972e --- /dev/null +++ b/js/plugins/flot/jquery.flot.resize.js @@ -0,0 +1,60 @@ +/* Flot plugin for automatically redrawing plots as the placeholder resizes. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +It works by listening for changes on the placeholder div (through the jQuery +resize event plugin) - if the size changes, it will redraw the plot. + +There are no options. If you need to disable the plugin for some plots, you +can just fix the size of their placeholders. + +*/ + +/* Inline dependency: + * jQuery resize event - v1.1 - 3/14/2010 + * http://benalman.com/projects/jquery-resize-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); + +(function ($) { + var options = { }; // no options + + function init(plot) { + function onResize() { + var placeholder = plot.getPlaceholder(); + + // somebody might have hidden us and we can't plot + // when we don't have the dimensions + if (placeholder.width() == 0 || placeholder.height() == 0) + return; + + plot.resize(); + plot.setupGrid(); + plot.draw(); + } + + function bindEvents(plot, eventHolder) { + plot.getPlaceholder().resize(onResize); + } + + function shutdown(plot, eventHolder) { + plot.getPlaceholder().unbind("resize", onResize); + } + + plot.hooks.bindEvents.push(bindEvents); + plot.hooks.shutdown.push(shutdown); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'resize', + version: '1.0' + }); +})(jQuery); \ No newline at end of file diff --git a/js/plugins/flot/jquery.flot.tooltip.min.js b/js/plugins/flot/jquery.flot.tooltip.min.js new file mode 100644 index 0000000..bc93bf8 --- /dev/null +++ b/js/plugins/flot/jquery.flot.tooltip.min.js @@ -0,0 +1,12 @@ +/* + * jquery.flot.tooltip + * + * description: easy-to-use tooltips for Flot charts + * version: 0.6.2 + * author: Krzysztof Urbas @krzysu [myviews.pl] + * website: https://github.com/krzysu/flot.tooltip + * + * build on 2013-09-30 + * released under MIT License, 2012 +*/ +(function(t){var o={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},i=function(t){this.tipPosition={x:0,y:0},this.init(t)};i.prototype.init=function(o){function i(t){var o={};o.x=t.pageX,o.y=t.pageY,s.updateTooltipPosition(o)}function e(t,o,i){var e=s.getDomElement();if(i){var n;n=s.stringFormat(s.tooltipOptions.content,i),e.html(n),s.updateTooltipPosition({x:o.pageX,y:o.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(i,e)}else e.hide().html("")}var s=this;o.hooks.bindEvents.push(function(o,n){s.plotOptions=o.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(o.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",i))}),o.hooks.shutdown.push(function(o,s){t(o.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",i)})},i.prototype.getDomElement=function(){var o;return t("#flotTip").length>0?o=t("#flotTip"):(o=t("
").attr("id","flotTip"),o.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&o.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),o},i.prototype.updateTooltipPosition=function(o){var i=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;o.x-t(window).scrollLeft()>t(window).innerWidth()-i&&(o.x-=i),o.y-t(window).scrollTop()>t(window).innerHeight()-e&&(o.y-=e),this.tipPosition.x=o.x,this.tipPosition.y=o.y},i.prototype.stringFormat=function(t,o){var i=/%p\.{0,1}(\d{0,})/,e=/%s/,s=/%x\.{0,1}(?:\d{0,})/,n=/%y\.{0,1}(?:\d{0,})/;return"function"==typeof t&&(t=t(o.series.label,o.series.data[o.dataIndex][0],o.series.data[o.dataIndex][1],o)),o.series.percent!==void 0&&(t=this.adjustValPrecision(i,t,o.series.percent)),o.series.label!==void 0&&(t=t.replace(e,o.series.label)),this.isTimeMode("xaxis",o)&&this.isXDateFormat(o)&&(t=t.replace(s,this.timestampToDate(o.series.data[o.dataIndex][0],this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",o)&&this.isYDateFormat(o)&&(t=t.replace(n,this.timestampToDate(o.series.data[o.dataIndex][1],this.tooltipOptions.yDateFormat))),"number"==typeof o.series.data[o.dataIndex][0]&&(t=this.adjustValPrecision(s,t,o.series.data[o.dataIndex][0])),"number"==typeof o.series.data[o.dataIndex][1]&&(t=this.adjustValPrecision(n,t,o.series.data[o.dataIndex][1])),o.series.xaxis.tickFormatter!==void 0&&(t=t.replace(s,o.series.xaxis.tickFormatter(o.series.data[o.dataIndex][0],o.series.xaxis))),o.series.yaxis.tickFormatter!==void 0&&(t=t.replace(n,o.series.yaxis.tickFormatter(o.series.data[o.dataIndex][1],o.series.yaxis))),t},i.prototype.isTimeMode=function(t,o){return o.series[t].options.mode!==void 0&&"time"===o.series[t].options.mode},i.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},i.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},i.prototype.timestampToDate=function(o,i){var e=new Date(o);return t.plot.formatDate(e,i)},i.prototype.adjustValPrecision=function(t,o,i){var e,s=o.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,i=i.toFixed(e),o=o.replace(t,i)),o};var e=function(t){new i(t)};t.plot.plugins.push({init:e,options:o,name:"tooltip",version:"0.6.1"})})(jQuery); \ No newline at end of file -- cgit v1.2.3