blacklight_range_limit 2.3.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/.gitignore +2 -2
  2. data/.travis.yml +7 -13
  3. data/Gemfile +8 -34
  4. data/LICENSE +15 -0
  5. data/README.rdoc +25 -7
  6. data/Rakefile +3 -4
  7. data/VERSION +1 -1
  8. data/app/assets/javascripts/blacklight_range_limit.js +5 -2
  9. data/app/assets/javascripts/blacklight_range_limit/range_limit_distro_facets.js +144 -68
  10. data/app/assets/javascripts/blacklight_range_limit/range_limit_slider.js +68 -36
  11. data/app/assets/stylesheets/blacklight_range_limit.css.scss +1 -46
  12. data/app/assets/stylesheets/blacklight_range_limit/blacklight_range_limit.css +12 -2
  13. data/app/helpers/range_limit_helper.rb +7 -1
  14. data/app/views/blacklight_range_limit/_range_limit_panel.html.erb +27 -11
  15. data/app/views/blacklight_range_limit/_range_segments.html.erb +13 -8
  16. data/blacklight_range_limit.gemspec +9 -12
  17. data/config/jetty.yml +7 -2
  18. data/lib/blacklight_range_limit/controller_override.rb +4 -13
  19. data/lib/blacklight_range_limit/route_sets.rb +21 -4
  20. data/lib/blacklight_range_limit/view_helper_override.rb +27 -11
  21. data/lib/generators/blacklight_range_limit/assets_generator.rb +9 -2
  22. data/spec/features/blacklight_range_limit_spec.rb +5 -5
  23. data/spec/spec_helper.rb +7 -1
  24. data/spec/test_app_templates/Gemfile.extra +14 -1
  25. data/spec/test_app_templates/lib/generators/test_app_generator.rb +4 -15
  26. data/spec/test_app_templates/lib/tasks/blacklight_test_app.rake +0 -6
  27. data/vendor/assets/javascripts/bootstrap-slider.js +388 -0
  28. data/vendor/assets/javascripts/flot/excanvas.min.js +1 -1
  29. data/vendor/assets/javascripts/flot/jquery.flot.js +1328 -790
  30. data/vendor/assets/javascripts/flot/jquery.flot.selection.js +76 -60
  31. data/vendor/assets/stylesheets/slider.css +138 -0
  32. metadata +74 -110
  33. checksums.yaml +0 -7
  34. data/.rspec +0 -1
  35. data/MIT-LICENSE +0 -20
  36. data/solr/sample_solr_documents.yml +0 -641
  37. data/vendor/assets/javascripts/jquery-ui-1.9.2.custom.js +0 -1654
@@ -1 +1 @@
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,"&amp;").replace(/"/g,"&quot;")}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;Z<j.length;Z++){this.initElement(j[Z])}},initElement:function(i){if(!i.getContext){i.getContext=T;r(i.ownerDocument);i.innerHTML="";i.attachEvent("onpropertychange",S);i.attachEvent("onresize",w);var Z=i.attributes;if(Z.width&&Z.width.specified){i.style.width=Z.width.nodeValue+"px"}else{i.width=i.clientWidth}if(Z.height&&Z.height.specified){i.style.height=Z.height.nodeValue+"px"}else{i.height=i.clientHeight}}return i}};function S(i){var Z=i.srcElement;switch(i.propertyName){case"width":Z.getContext().clearRect();Z.style.width=Z.attributes.width.nodeValue+"px";Z.firstChild.style.width=Z.clientWidth+"px";break;case"height":Z.getContext().clearRect();Z.style.height=Z.attributes.height.nodeValue+"px";Z.firstChild.style.height=Z.clientHeight+"px";break}}function w(i){var Z=i.srcElement;if(Z.firstChild){Z.firstChild.style.width=Z.clientWidth+"px";Z.firstChild.style.height=Z.clientHeight+"px"}}E.init();var I=[];for(var AC=0;AC<16;AC++){for(var AB=0;AB<16;AB++){I[AC*16+AB]=AC.toString(16)+AB.toString(16)}}function V(){return[[1,0,0],[0,1,0],[0,0,1]]}function d(m,j){var i=V();for(var Z=0;Z<3;Z++){for(var AF=0;AF<3;AF++){var p=0;for(var AE=0;AE<3;AE++){p+=m[Z][AE]*j[AE][AF]}i[Z][AF]=p}}return i}function Q(i,Z){Z.fillStyle=i.fillStyle;Z.lineCap=i.lineCap;Z.lineJoin=i.lineJoin;Z.lineWidth=i.lineWidth;Z.miterLimit=i.miterLimit;Z.shadowBlur=i.shadowBlur;Z.shadowColor=i.shadowColor;Z.shadowOffsetX=i.shadowOffsetX;Z.shadowOffsetY=i.shadowOffsetY;Z.strokeStyle=i.strokeStyle;Z.globalAlpha=i.globalAlpha;Z.font=i.font;Z.textAlign=i.textAlign;Z.textBaseline=i.textBaseline;Z.arcScaleX_=i.arcScaleX_;Z.arcScaleY_=i.arcScaleY_;Z.lineScale_=i.lineScale_}var B={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function g(i){var m=i.indexOf("(",3);var Z=i.indexOf(")",m+1);var j=i.substring(m+1,Z).split(",");if(j.length==4&&i.substr(3,1)=="a"){alpha=Number(j[3])}else{j[3]=1}return j}function C(Z){return parseFloat(Z)/100}function N(i,j,Z){return Math.min(Z,Math.max(j,i))}function c(AF){var j,i,Z;h=parseFloat(AF[0])/360%360;if(h<0){h++}s=N(C(AF[1]),0,1);l=N(C(AF[2]),0,1);if(s==0){j=i=Z=l}else{var m=l<0.5?l*(1+s):l+s-l*s;var AE=2*l-m;j=A(AE,m,h+1/3);i=A(AE,m,h);Z=A(AE,m,h-1/3)}return"#"+I[Math.floor(j*255)]+I[Math.floor(i*255)]+I[Math.floor(Z*255)]}function A(i,Z,j){if(j<0){j++}if(j>1){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(" <g_vml_:group",' coordsize="',D*Z,",",D*AE,'"',' coordorigin="0,0"',' style="width:',Z,"px;height:",AE,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var p=[];p.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",K(AW.x/D),",","Dy=",K(AW.y/D),"");var AS=AW;var AR=this.getCoords_(AH+AJ,AF);var AP=this.getCoords_(AH,AF+AV);var AL=this.getCoords_(AH+AJ,AF+AV);AS.x=z.max(AS.x,AR.x,AP.x,AL.x);AS.y=z.max(AS.y,AR.y,AP.y,AL.y);AU.push("padding:0 ",K(AS.x/D),"px ",K(AS.y/D),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",p.join(""),", sizingmethod='clip');")}else{AU.push("top:",K(AW.y/D),"px;left:",K(AW.x/D),"px;")}AU.push(' ">','<g_vml_:image src="',AO.src,'"',' style="width:',D*AJ,"px;"," height:",D*AV,'px"',' cropleft="',AM/AG,'"',' croptop="',AK/AT,'"',' cropright="',(AG-AM-AQ)/AG,'"',' cropbottom="',(AT-AK-AX)/AT,'"'," />","</g_vml_:group>");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;AH<this.currentPath_.length;AH+=AE){var AK=[];var AF=false;AK.push("<g_vml_:shape",' filled="',!!AM,'"',' style="position:absolute;width:',m,"px;height:",AN,'px;"',' coordorigin="0,0"',' coordsize="',D*m,",",D*AN,'"',' stroked="',!AM,'"',' path="');var AO=false;for(var AI=AH;AI<Math.min(AH+AE,this.currentPath_.length);AI++){if(AI%AE==0&&AI>0){AK.push(" m ",K(this.currentPath_[AI-1].x),",",K(this.currentPath_[AI-1].y))}var Z=this.currentPath_[AI];var AJ;switch(Z.type){case"moveTo":AJ=Z;AK.push(" m ",K(Z.x),",",K(Z.y));break;case"lineTo":AK.push(" l ",K(Z.x),",",K(Z.y));break;case"close":AK.push(" x ");Z=null;break;case"bezierCurveTo":AK.push(" c ",K(Z.cp1x),",",K(Z.cp1y),",",K(Z.cp2x),",",K(Z.cp2y),",",K(Z.x),",",K(Z.y));break;case"at":case"wa":AK.push(" ",Z.type," ",K(Z.x-this.arcScaleX_*Z.radius),",",K(Z.y-this.arcScaleY_*Z.radius)," ",K(Z.x+this.arcScaleX_*Z.radius),",",K(Z.y+this.arcScaleY_*Z.radius)," ",K(Z.xStart),",",K(Z.yStart)," ",K(Z.xEnd),",",K(Z.yEnd));break}if(Z){if(AG.x==null||Z.x<AG.x){AG.x=Z.x}if(AL.x==null||Z.x>AL.x){AL.x=Z.x}if(AG.y==null||Z.y<AG.y){AG.y=Z.y}if(AL.y==null||Z.y>AL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("</g_vml_:shape>");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("<g_vml_:stroke",' opacity="',p,'"',' joinstyle="',j.lineJoin,'"',' miterlimit="',j.miterLimit,'"',' endcap="',t(j.lineCap),'"',' weight="',Z,'px"',' color="',m,'" />')}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<AN;Ae++){var AM=AS[Ae];Ab.push(AM.offset*AK+AU+" "+AM.color)}AG.push('<g_vml_:fill type="',AH.type_,'"',' method="none" focus="100%"',' color="',AR,'"',' color2="',AQ,'"',' colors="',Ab.join(","),'"',' opacity="',AV,'"',' g_o_:opacity2="',AW,'"',' angle="',AL,'"',' focusposition="',Ac.x,",",Ac.y,'" />')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("<g_vml_:fill",' position="',AF/Z*AY*AY,",",AZ/m*AX*AX,'"',' type="tile"',' src="',AH.src_,'" />')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('<g_vml_:fill color="',AT,'" opacity="',Ad,'" />')}}}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('<g_vml_:line from="',-i,' 0" to="',AP,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!AG,'" stroked="',!!AG,'" style="position:absolute;width:1px;height:1px;">');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('<g_vml_:skew on="t" matrix="',AL,'" ',' offset="',AJ,'" origin="',i,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',AD(AK),'" style="v-text-align:',p,";font:",AD(j),'" /></g_vml_:line>');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='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';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})()};
1
+ if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j<m.length;j++){this.initElement(m[j])}},initElement:function(j){if(!j.getContext){j.getContext=y;R(j.ownerDocument);j.innerHTML="";j.attachEvent("onpropertychange",x);j.attachEvent("onresize",W);var i=j.attributes;if(i.width&&i.width.specified){j.style.width=i.width.nodeValue+"px"}else{j.width=j.clientWidth}if(i.height&&i.height.specified){j.style.height=i.height.nodeValue+"px"}else{j.height=j.clientHeight}}return j}};function x(j){var i=j.srcElement;switch(j.propertyName){case"width":i.getContext().clearRect();i.style.width=i.attributes.width.nodeValue+"px";i.firstChild.style.width=i.clientWidth+"px";break;case"height":i.getContext().clearRect();i.style.height=i.attributes.height.nodeValue+"px";i.firstChild.style.height=i.clientHeight+"px";break}}function W(j){var i=j.srcElement;if(i.firstChild){i.firstChild.style.width=i.clientWidth+"px";i.firstChild.style.height=i.clientHeight+"px"}}e.init();var k=[];for(var ae=0;ae<16;ae++){for(var ad=0;ad<16;ad++){k[ae*16+ad]=ae.toString(16)+ad.toString(16)}}function B(){return[[1,0,0],[0,1,0],[0,0,1]]}function J(p,m){var j=B();for(var i=0;i<3;i++){for(var ah=0;ah<3;ah++){var Z=0;for(var ag=0;ag<3;ag++){Z+=p[i][ag]*m[ag][ah]}j[i][ah]=Z}}return j}function v(j,i){i.fillStyle=j.fillStyle;i.lineCap=j.lineCap;i.lineJoin=j.lineJoin;i.lineWidth=j.lineWidth;i.miterLimit=j.miterLimit;i.shadowBlur=j.shadowBlur;i.shadowColor=j.shadowColor;i.shadowOffsetX=j.shadowOffsetX;i.shadowOffsetY=j.shadowOffsetY;i.strokeStyle=j.strokeStyle;i.globalAlpha=j.globalAlpha;i.font=j.font;i.textAlign=j.textAlign;i.textBaseline=j.textBaseline;i.arcScaleX_=j.arcScaleX_;i.arcScaleY_=j.arcScaleY_;i.lineScale_=j.lineScale_}var b={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function M(j){var p=j.indexOf("(",3);var i=j.indexOf(")",p+1);var m=j.substring(p+1,i).split(",");if(m.length!=4||j.charAt(3)!="a"){m[3]=1}return m}function c(i){return parseFloat(i)/100}function r(j,m,i){return Math.min(i,Math.max(m,j))}function I(ag){var i,ai,aj,ah,ak,Z;ah=parseFloat(ag[0])/360%360;if(ah<0){ah++}ak=r(c(ag[1]),0,1);Z=r(c(ag[2]),0,1);if(ak==0){i=ai=aj=Z}else{var j=Z<0.5?Z*(1+ak):Z+ak-Z*ak;var m=2*Z-j;i=a(m,j,ah+1/3);ai=a(m,j,ah);aj=a(m,j,ah-1/3)}return"#"+k[Math.floor(i*255)]+k[Math.floor(ai*255)]+k[Math.floor(aj*255)]}function a(j,i,m){if(m<0){m++}if(m>1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();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 m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" <g_vml_:group",' coordsize="',d*i,",",d*ag,'"',' coordorigin="0,0"',' style="width:',i,"px;height:",ag,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var Z=[];Z.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",n(az.x/d),",","Dy=",n(az.y/d),"");var av=az;var au=V(this,aj+al,ah);var ar=V(this,aj,ah+ay);var an=V(this,aj+al,ah+ay);av.x=ab.max(av.x,au.x,ar.x,an.x);av.y=ab.max(av.y,au.y,ar.y,an.y);ax.push("padding:0 ",n(av.x/d),"px ",n(av.y/d),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",Z.join(""),", sizingmethod='clip');")}else{ax.push("top:",n(az.y/d),"px;left:",n(az.x/d),"px;")}ax.push(' ">','<g_vml_:image src="',aq.src,'"',' style="width:',d*al,"px;"," height:",d*ay,'px"',' cropleft="',ao/ai,'"',' croptop="',am/aw,'"',' cropright="',(ai-ao-at)/ai,'"',' cropbottom="',(aw-am-aA)/aw,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;aj<this.currentPath_.length;aj+=ag){var am=[];var ah=false;am.push("<g_vml_:shape",' filled="',!!ao,'"',' style="position:absolute;width:',Z,"px;height:",ap,'px;"',' coordorigin="0,0"',' coordsize="',d*Z,",",d*ap,'"',' stroked="',!ao,'"',' path="');var aq=false;for(var ak=aj;ak<Math.min(aj+ag,this.currentPath_.length);ak++){if(ak%ag==0&&ak>0){am.push(" m ",n(this.currentPath_[ak-1].x),",",n(this.currentPath_[ak-1].y))}var m=this.currentPath_[ak];var al;switch(m.type){case"moveTo":al=m;am.push(" m ",n(m.x),",",n(m.y));break;case"lineTo":am.push(" l ",n(m.x),",",n(m.y));break;case"close":am.push(" x ");m=null;break;case"bezierCurveTo":am.push(" c ",n(m.cp1x),",",n(m.cp1y),",",n(m.cp2x),",",n(m.cp2y),",",n(m.x),",",n(m.y));break;case"at":case"wa":am.push(" ",m.type," ",n(m.x-this.arcScaleX_*m.radius),",",n(m.y-this.arcScaleY_*m.radius)," ",n(m.x+this.arcScaleX_*m.radius),",",n(m.y+this.arcScaleY_*m.radius)," ",n(m.xStart),",",n(m.yStart)," ",n(m.xEnd),",",n(m.yEnd));break}if(m){if(ai.x==null||m.x<ai.x){ai.x=m.x}if(an.x==null||m.x>an.x){an.x=m.x}if(ai.y==null||m.y<ai.y){ai.y=m.y}if(an.y==null||m.y>an.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("<g_vml_:stroke",' opacity="',Z,'"',' joinstyle="',m.lineJoin,'"',' miterlimit="',m.miterLimit,'"',' endcap="',S(m.lineCap),'"',' weight="',i,'px"',' color="',p,'" />')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH<ap;aH++){var ao=av[aH];aE.push(ao.offset*am+ax+" "+ao.color)}ai.push('<g_vml_:fill type="',aj.type_,'"',' method="none" focus="100%"',' color="',au,'"',' color2="',at,'"',' colors="',aE.join(","),'"',' opacity="',ay,'"',' g_o_:opacity2="',az,'"',' angle="',an,'"',' focusposition="',aF.x,",",aF.y,'" />')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("<g_vml_:fill",' position="',ah/j*aB*aB,",",aC/p*aA*aA,'"',' type="tile"',' src="',aj.src_,'" />')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('<g_vml_:fill color="',aw,'" opacity="',aG,'" />')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('<g_vml_:line from="',-j,' 0" to="',ar,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!ai,'" stroked="',!!ai,'" style="position:absolute;width:1px;height:1px;">');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('<g_vml_:skew on="t" matrix="',an,'" ',' offset="',al,'" origin="',j,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',af(am),'" style="v-text-align:',Z,";font:",af(p),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.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=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()};
@@ -1,16 +1,17 @@
1
- /*! Javascript plotting library for jQuery, v. 0.7.
2
- *
3
- * Released under the MIT license by IOLA, December 2007.
4
- *
5
- */
1
+ /* Javascript plotting library for jQuery, version 0.8.2.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ */
6
7
 
7
8
  // first an inline dependency, jquery.colorhelpers.js, we inline it here
8
9
  // for convenience
9
10
 
10
11
  /* Plugin for jQuery for working with colors.
11
- *
12
+ *
12
13
  * Version 1.1.
13
- *
14
+ *
14
15
  * Inspiration from jQuery color animation plugin by John Resig.
15
16
  *
16
17
  * Released under the MIT license by Ole Laursen, October 2009.
@@ -27,17 +28,473 @@
27
28
  *
28
29
  * V. 1.1: Fix error handling so e.g. parsing an empty string does
29
30
  * produce a color rather than just crashing.
30
- */
31
- (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
31
+ */
32
+ (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/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(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/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(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={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);
32
33
 
33
34
  // the actual Flot code
34
35
  (function($) {
36
+
37
+ // Cache the prototype hasOwnProperty for faster access
38
+
39
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
40
+
41
+ ///////////////////////////////////////////////////////////////////////////
42
+ // The Canvas object is a wrapper around an HTML5 <canvas> tag.
43
+ //
44
+ // @constructor
45
+ // @param {string} cls List of classes to apply to the canvas.
46
+ // @param {element} container Element onto which to append the canvas.
47
+ //
48
+ // Requiring a container is a little iffy, but unfortunately canvas
49
+ // operations don't work unless the canvas is attached to the DOM.
50
+
51
+ function Canvas(cls, container) {
52
+
53
+ var element = container.children("." + cls)[0];
54
+
55
+ if (element == null) {
56
+
57
+ element = document.createElement("canvas");
58
+ element.className = cls;
59
+
60
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
61
+ .appendTo(container);
62
+
63
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
64
+
65
+ if (!element.getContext) {
66
+ if (window.G_vmlCanvasManager) {
67
+ element = window.G_vmlCanvasManager.initElement(element);
68
+ } else {
69
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
70
+ }
71
+ }
72
+ }
73
+
74
+ this.element = element;
75
+
76
+ var context = this.context = element.getContext("2d");
77
+
78
+ // Determine the screen's ratio of physical to device-independent
79
+ // pixels. This is the ratio between the canvas width that the browser
80
+ // advertises and the number of pixels actually present in that space.
81
+
82
+ // The iPhone 4, for example, has a device-independent width of 320px,
83
+ // but its screen is actually 640px wide. It therefore has a pixel
84
+ // ratio of 2, while most normal devices have a ratio of 1.
85
+
86
+ var devicePixelRatio = window.devicePixelRatio || 1,
87
+ backingStoreRatio =
88
+ context.webkitBackingStorePixelRatio ||
89
+ context.mozBackingStorePixelRatio ||
90
+ context.msBackingStorePixelRatio ||
91
+ context.oBackingStorePixelRatio ||
92
+ context.backingStorePixelRatio || 1;
93
+
94
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
95
+
96
+ // Size the canvas to match the internal dimensions of its container
97
+
98
+ this.resize(container.width(), container.height());
99
+
100
+ // Collection of HTML div layers for text overlaid onto the canvas
101
+
102
+ this.textContainer = null;
103
+ this.text = {};
104
+
105
+ // Cache of text fragments and metrics, so we can avoid expensively
106
+ // re-calculating them when the plot is re-rendered in a loop.
107
+
108
+ this._textCache = {};
109
+ }
110
+
111
+ // Resizes the canvas to the given dimensions.
112
+ //
113
+ // @param {number} width New width of the canvas, in pixels.
114
+ // @param {number} width New height of the canvas, in pixels.
115
+
116
+ Canvas.prototype.resize = function(width, height) {
117
+
118
+ if (width <= 0 || height <= 0) {
119
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
120
+ }
121
+
122
+ var element = this.element,
123
+ context = this.context,
124
+ pixelRatio = this.pixelRatio;
125
+
126
+ // Resize the canvas, increasing its density based on the display's
127
+ // pixel ratio; basically giving it more pixels without increasing the
128
+ // size of its element, to take advantage of the fact that retina
129
+ // displays have that many more pixels in the same advertised space.
130
+
131
+ // Resizing should reset the state (excanvas seems to be buggy though)
132
+
133
+ if (this.width != width) {
134
+ element.width = width * pixelRatio;
135
+ element.style.width = width + "px";
136
+ this.width = width;
137
+ }
138
+
139
+ if (this.height != height) {
140
+ element.height = height * pixelRatio;
141
+ element.style.height = height + "px";
142
+ this.height = height;
143
+ }
144
+
145
+ // Save the context, so we can reset in case we get replotted. The
146
+ // restore ensure that we're really back at the initial state, and
147
+ // should be safe even if we haven't saved the initial state yet.
148
+
149
+ context.restore();
150
+ context.save();
151
+
152
+ // Scale the coordinate space to match the display density; so even though we
153
+ // may have twice as many pixels, we still want lines and other drawing to
154
+ // appear at the same size; the extra pixels will just make them crisper.
155
+
156
+ context.scale(pixelRatio, pixelRatio);
157
+ };
158
+
159
+ // Clears the entire canvas area, not including any overlaid HTML text
160
+
161
+ Canvas.prototype.clear = function() {
162
+ this.context.clearRect(0, 0, this.width, this.height);
163
+ };
164
+
165
+ // Finishes rendering the canvas, including managing the text overlay.
166
+
167
+ Canvas.prototype.render = function() {
168
+
169
+ var cache = this._textCache;
170
+
171
+ // For each text layer, add elements marked as active that haven't
172
+ // already been rendered, and remove those that are no longer active.
173
+
174
+ for (var layerKey in cache) {
175
+ if (hasOwnProperty.call(cache, layerKey)) {
176
+
177
+ var layer = this.getTextLayer(layerKey),
178
+ layerCache = cache[layerKey];
179
+
180
+ layer.hide();
181
+
182
+ for (var styleKey in layerCache) {
183
+ if (hasOwnProperty.call(layerCache, styleKey)) {
184
+ var styleCache = layerCache[styleKey];
185
+ for (var key in styleCache) {
186
+ if (hasOwnProperty.call(styleCache, key)) {
187
+
188
+ var positions = styleCache[key].positions;
189
+
190
+ for (var i = 0, position; position = positions[i]; i++) {
191
+ if (position.active) {
192
+ if (!position.rendered) {
193
+ layer.append(position.element);
194
+ position.rendered = true;
195
+ }
196
+ } else {
197
+ positions.splice(i--, 1);
198
+ if (position.rendered) {
199
+ position.element.detach();
200
+ }
201
+ }
202
+ }
203
+
204
+ if (positions.length == 0) {
205
+ delete styleCache[key];
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ layer.show();
213
+ }
214
+ }
215
+ };
216
+
217
+ // Creates (if necessary) and returns the text overlay container.
218
+ //
219
+ // @param {string} classes String of space-separated CSS classes used to
220
+ // uniquely identify the text layer.
221
+ // @return {object} The jQuery-wrapped text-layer div.
222
+
223
+ Canvas.prototype.getTextLayer = function(classes) {
224
+
225
+ var layer = this.text[classes];
226
+
227
+ // Create the text layer if it doesn't exist
228
+
229
+ if (layer == null) {
230
+
231
+ // Create the text layer container, if it doesn't exist
232
+
233
+ if (this.textContainer == null) {
234
+ this.textContainer = $("<div class='flot-text'></div>")
235
+ .css({
236
+ position: "absolute",
237
+ top: 0,
238
+ left: 0,
239
+ bottom: 0,
240
+ right: 0,
241
+ 'font-size': "smaller",
242
+ color: "#545454"
243
+ })
244
+ .insertAfter(this.element);
245
+ }
246
+
247
+ layer = this.text[classes] = $("<div></div>")
248
+ .addClass(classes)
249
+ .css({
250
+ position: "absolute",
251
+ top: 0,
252
+ left: 0,
253
+ bottom: 0,
254
+ right: 0
255
+ })
256
+ .appendTo(this.textContainer);
257
+ }
258
+
259
+ return layer;
260
+ };
261
+
262
+ // Creates (if necessary) and returns a text info object.
263
+ //
264
+ // The object looks like this:
265
+ //
266
+ // {
267
+ // width: Width of the text's wrapper div.
268
+ // height: Height of the text's wrapper div.
269
+ // element: The jQuery-wrapped HTML div containing the text.
270
+ // positions: Array of positions at which this text is drawn.
271
+ // }
272
+ //
273
+ // The positions array contains objects that look like this:
274
+ //
275
+ // {
276
+ // active: Flag indicating whether the text should be visible.
277
+ // rendered: Flag indicating whether the text is currently visible.
278
+ // element: The jQuery-wrapped HTML div containing the text.
279
+ // x: X coordinate at which to draw the text.
280
+ // y: Y coordinate at which to draw the text.
281
+ // }
282
+ //
283
+ // Each position after the first receives a clone of the original element.
284
+ //
285
+ // The idea is that that the width, height, and general 'identity' of the
286
+ // text is constant no matter where it is placed; the placements are a
287
+ // secondary property.
288
+ //
289
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
290
+ // either returns the cached element or creates a new entry.
291
+ //
292
+ // @param {string} layer A string of space-separated CSS classes uniquely
293
+ // identifying the layer containing this text.
294
+ // @param {string} text Text string to retrieve info for.
295
+ // @param {(string|object)=} font Either a string of space-separated CSS
296
+ // classes or a font-spec object, defining the text's font and style.
297
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
298
+ // Angle is currently unused, it will be implemented in the future.
299
+ // @param {number=} width Maximum width of the text before it wraps.
300
+ // @return {object} a text info object.
301
+
302
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
303
+
304
+ var textStyle, layerCache, styleCache, info;
305
+
306
+ // Cast the value to a string, in case we were given a number or such
307
+
308
+ text = "" + text;
309
+
310
+ // If the font is a font-spec object, generate a CSS font definition
311
+
312
+ if (typeof font === "object") {
313
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
314
+ } else {
315
+ textStyle = font;
316
+ }
317
+
318
+ // Retrieve (or create) the cache for the text's layer and styles
319
+
320
+ layerCache = this._textCache[layer];
321
+
322
+ if (layerCache == null) {
323
+ layerCache = this._textCache[layer] = {};
324
+ }
325
+
326
+ styleCache = layerCache[textStyle];
327
+
328
+ if (styleCache == null) {
329
+ styleCache = layerCache[textStyle] = {};
330
+ }
331
+
332
+ info = styleCache[text];
333
+
334
+ // If we can't find a matching element in our cache, create a new one
335
+
336
+ if (info == null) {
337
+
338
+ var element = $("<div></div>").html(text)
339
+ .css({
340
+ position: "absolute",
341
+ 'max-width': width,
342
+ top: -9999
343
+ })
344
+ .appendTo(this.getTextLayer(layer));
345
+
346
+ if (typeof font === "object") {
347
+ element.css({
348
+ font: textStyle,
349
+ color: font.color
350
+ });
351
+ } else if (typeof font === "string") {
352
+ element.addClass(font);
353
+ }
354
+
355
+ info = styleCache[text] = {
356
+ width: element.outerWidth(true),
357
+ height: element.outerHeight(true),
358
+ element: element,
359
+ positions: []
360
+ };
361
+
362
+ element.detach();
363
+ }
364
+
365
+ return info;
366
+ };
367
+
368
+ // Adds a text string to the canvas text overlay.
369
+ //
370
+ // The text isn't drawn immediately; it is marked as rendering, which will
371
+ // result in its addition to the canvas on the next render pass.
372
+ //
373
+ // @param {string} layer A string of space-separated CSS classes uniquely
374
+ // identifying the layer containing this text.
375
+ // @param {number} x X coordinate at which to draw the text.
376
+ // @param {number} y Y coordinate at which to draw the text.
377
+ // @param {string} text Text string to draw.
378
+ // @param {(string|object)=} font Either a string of space-separated CSS
379
+ // classes or a font-spec object, defining the text's font and style.
380
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
381
+ // Angle is currently unused, it will be implemented in the future.
382
+ // @param {number=} width Maximum width of the text before it wraps.
383
+ // @param {string=} halign Horizontal alignment of the text; either "left",
384
+ // "center" or "right".
385
+ // @param {string=} valign Vertical alignment of the text; either "top",
386
+ // "middle" or "bottom".
387
+
388
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
389
+
390
+ var info = this.getTextInfo(layer, text, font, angle, width),
391
+ positions = info.positions;
392
+
393
+ // Tweak the div's position to match the text's alignment
394
+
395
+ if (halign == "center") {
396
+ x -= info.width / 2;
397
+ } else if (halign == "right") {
398
+ x -= info.width;
399
+ }
400
+
401
+ if (valign == "middle") {
402
+ y -= info.height / 2;
403
+ } else if (valign == "bottom") {
404
+ y -= info.height;
405
+ }
406
+
407
+ // Determine whether this text already exists at this position.
408
+ // If so, mark it for inclusion in the next render pass.
409
+
410
+ for (var i = 0, position; position = positions[i]; i++) {
411
+ if (position.x == x && position.y == y) {
412
+ position.active = true;
413
+ return;
414
+ }
415
+ }
416
+
417
+ // If the text doesn't exist at this position, create a new entry
418
+
419
+ // For the very first position we'll re-use the original element,
420
+ // while for subsequent ones we'll clone it.
421
+
422
+ position = {
423
+ active: true,
424
+ rendered: false,
425
+ element: positions.length ? info.element.clone() : info.element,
426
+ x: x,
427
+ y: y
428
+ };
429
+
430
+ positions.push(position);
431
+
432
+ // Move the element to its final position within the container
433
+
434
+ position.element.css({
435
+ top: Math.round(y),
436
+ left: Math.round(x),
437
+ 'text-align': halign // In case the text wraps
438
+ });
439
+ };
440
+
441
+ // Removes one or more text strings from the canvas text overlay.
442
+ //
443
+ // If no parameters are given, all text within the layer is removed.
444
+ //
445
+ // Note that the text is not immediately removed; it is simply marked as
446
+ // inactive, which will result in its removal on the next render pass.
447
+ // This avoids the performance penalty for 'clear and redraw' behavior,
448
+ // where we potentially get rid of all text on a layer, but will likely
449
+ // add back most or all of it later, as when redrawing axes, for example.
450
+ //
451
+ // @param {string} layer A string of space-separated CSS classes uniquely
452
+ // identifying the layer containing this text.
453
+ // @param {number=} x X coordinate of the text.
454
+ // @param {number=} y Y coordinate of the text.
455
+ // @param {string=} text Text string to remove.
456
+ // @param {(string|object)=} font Either a string of space-separated CSS
457
+ // classes or a font-spec object, defining the text's font and style.
458
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
459
+ // Angle is currently unused, it will be implemented in the future.
460
+
461
+ Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
462
+ if (text == null) {
463
+ var layerCache = this._textCache[layer];
464
+ if (layerCache != null) {
465
+ for (var styleKey in layerCache) {
466
+ if (hasOwnProperty.call(layerCache, styleKey)) {
467
+ var styleCache = layerCache[styleKey];
468
+ for (var key in styleCache) {
469
+ if (hasOwnProperty.call(styleCache, key)) {
470
+ var positions = styleCache[key].positions;
471
+ for (var i = 0, position; position = positions[i]; i++) {
472
+ position.active = false;
473
+ }
474
+ }
475
+ }
476
+ }
477
+ }
478
+ }
479
+ } else {
480
+ var positions = this.getTextInfo(layer, text, font, angle).positions;
481
+ for (var i = 0, position; position = positions[i]; i++) {
482
+ if (position.x == x && position.y == y) {
483
+ position.active = false;
484
+ }
485
+ }
486
+ }
487
+ };
488
+
489
+ ///////////////////////////////////////////////////////////////////////////
490
+ // The top-level container for the entire plot.
491
+
35
492
  function Plot(placeholder, data_, options_, plugins) {
36
493
  // data is on the form:
37
494
  // [ series1, series2 ... ]
38
495
  // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
39
496
  // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
40
-
497
+
41
498
  var series = [],
42
499
  options = {
43
500
  // the color theme used for graphs
@@ -51,12 +508,14 @@
51
508
  position: "ne", // position of default legend container within plot
52
509
  margin: 5, // distance from grid edge to default legend container within plot
53
510
  backgroundColor: null, // null means auto-detect
54
- backgroundOpacity: 0.85 // set to 0 to avoid background
511
+ backgroundOpacity: 0.85, // set to 0 to avoid background
512
+ sorted: null // default to no legend sorting
55
513
  },
56
514
  xaxis: {
57
515
  show: null, // null = auto-detect, true = always, false = never
58
516
  position: "bottom", // or "top"
59
517
  mode: null, // null or "time"
518
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
60
519
  color: null, // base color, labels, ticks
61
520
  tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
62
521
  transform: null, // null or f: number -> number to transform axis
@@ -71,14 +530,9 @@
71
530
  reserveSpace: null, // whether to reserve space even if axis isn't shown
72
531
  tickLength: null, // size in pixels of ticks, or "full" for whole line
73
532
  alignTicksWithAxis: null, // axis number or null for no sync
74
-
75
- // mode specific options
76
533
  tickDecimals: null, // no. of decimals, null means auto
77
534
  tickSize: null, // number or [number, "unit"]
78
- minTickSize: null, // number or [number, "unit"]
79
- monthNames: null, // list of names of months
80
- timeformat: null, // format string to use
81
- twelveHourClock: false // 12 or 24 time in time mode
535
+ minTickSize: null // number or [number, "unit"]
82
536
  },
83
537
  yaxis: {
84
538
  autoscaleMargin: 0.02,
@@ -97,11 +551,13 @@
97
551
  },
98
552
  lines: {
99
553
  // we don't put in show: false so we can see
100
- // whether lines were actively disabled
554
+ // whether lines were actively disabled
101
555
  lineWidth: 2, // in pixels
102
556
  fill: false,
103
557
  fillColor: null,
104
558
  steps: false
559
+ // Omit 'zero', so we can later default its value to
560
+ // match that of the 'fill' option.
105
561
  },
106
562
  bars: {
107
563
  show: false,
@@ -109,10 +565,12 @@
109
565
  barWidth: 1, // in units of the x axis
110
566
  fill: true,
111
567
  fillColor: null,
112
- align: "left", // or "center"
113
- horizontal: false
568
+ align: "left", // "left", "right", or "center"
569
+ horizontal: false,
570
+ zero: true
114
571
  },
115
- shadowSize: 3
572
+ shadowSize: 3,
573
+ highlightColor: null
116
574
  },
117
575
  grid: {
118
576
  show: true,
@@ -121,6 +579,7 @@
121
579
  backgroundColor: null, // null for transparent, else color
122
580
  borderColor: null, // set if different from the grid color
123
581
  tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
582
+ margin: 0, // distance from the canvas edge to the grid
124
583
  labelMargin: 5, // in pixels
125
584
  axisMargin: 8, // in pixels
126
585
  borderWidth: 2, // in pixels
@@ -134,20 +593,24 @@
134
593
  autoHighlight: true, // highlight in case mouse is near
135
594
  mouseActiveRadius: 10 // how far the mouse can be away to activate an item
136
595
  },
596
+ interaction: {
597
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
598
+ },
137
599
  hooks: {}
138
600
  },
139
- canvas = null, // the canvas for the plot itself
601
+ surface = null, // the canvas for the plot itself
140
602
  overlay = null, // canvas for interactive stuff on top of plot
141
603
  eventHolder = null, // jQuery object that events should be bound to
142
604
  ctx = null, octx = null,
143
605
  xaxes = [], yaxes = [],
144
606
  plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
145
- canvasWidth = 0, canvasHeight = 0,
146
607
  plotWidth = 0, plotHeight = 0,
147
608
  hooks = {
148
609
  processOptions: [],
149
610
  processRawData: [],
150
611
  processDatapoints: [],
612
+ processOffset: [],
613
+ drawBackground: [],
151
614
  drawSeries: [],
152
615
  draw: [],
153
616
  bindEvents: [],
@@ -161,7 +624,7 @@
161
624
  plot.setupGrid = setupGrid;
162
625
  plot.draw = draw;
163
626
  plot.getPlaceholder = function() { return placeholder; };
164
- plot.getCanvas = function() { return canvas; };
627
+ plot.getCanvas = function() { return surface.element; };
165
628
  plot.getPlotOffset = function() { return plotOffset; };
166
629
  plot.width = function () { return plotWidth; };
167
630
  plot.height = function () { return plotHeight; };
@@ -190,20 +653,38 @@
190
653
  plot.triggerRedrawOverlay = triggerRedrawOverlay;
191
654
  plot.pointOffset = function(point) {
192
655
  return {
193
- left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
194
- top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
656
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
657
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
195
658
  };
196
659
  };
197
660
  plot.shutdown = shutdown;
661
+ plot.destroy = function () {
662
+ shutdown();
663
+ placeholder.removeData("plot").empty();
664
+
665
+ series = [];
666
+ options = null;
667
+ surface = null;
668
+ overlay = null;
669
+ eventHolder = null;
670
+ ctx = null;
671
+ octx = null;
672
+ xaxes = [];
673
+ yaxes = [];
674
+ hooks = null;
675
+ highlights = [];
676
+ plot = null;
677
+ };
198
678
  plot.resize = function () {
199
- getCanvasDimensions();
200
- resizeCanvas(canvas);
201
- resizeCanvas(overlay);
679
+ var width = placeholder.width(),
680
+ height = placeholder.height();
681
+ surface.resize(width, height);
682
+ overlay.resize(width, height);
202
683
  };
203
684
 
204
685
  // public attributes
205
686
  plot.hooks = hooks;
206
-
687
+
207
688
  // initialize
208
689
  initPlugins(plot);
209
690
  parseOptions(options_);
@@ -221,40 +702,109 @@
221
702
  }
222
703
 
223
704
  function initPlugins() {
705
+
706
+ // References to key classes, allowing plugins to modify them
707
+
708
+ var classes = {
709
+ Canvas: Canvas
710
+ };
711
+
224
712
  for (var i = 0; i < plugins.length; ++i) {
225
713
  var p = plugins[i];
226
- p.init(plot);
714
+ p.init(plot, classes);
227
715
  if (p.options)
228
716
  $.extend(true, options, p.options);
229
717
  }
230
718
  }
231
-
719
+
232
720
  function parseOptions(opts) {
233
- var i;
234
-
721
+
235
722
  $.extend(true, options, opts);
236
-
723
+
724
+ // $.extend merges arrays, rather than replacing them. When less
725
+ // colors are provided than the size of the default palette, we
726
+ // end up with those colors plus the remaining defaults, which is
727
+ // not expected behavior; avoid it by replacing them here.
728
+
729
+ if (opts && opts.colors) {
730
+ options.colors = opts.colors;
731
+ }
732
+
237
733
  if (options.xaxis.color == null)
238
- options.xaxis.color = options.grid.color;
734
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
239
735
  if (options.yaxis.color == null)
240
- options.yaxis.color = options.grid.color;
241
-
242
- if (options.xaxis.tickColor == null) // backwards-compatibility
243
- options.xaxis.tickColor = options.grid.tickColor;
244
- if (options.yaxis.tickColor == null) // backwards-compatibility
245
- options.yaxis.tickColor = options.grid.tickColor;
736
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
737
+
738
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
739
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
740
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
741
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
246
742
 
247
743
  if (options.grid.borderColor == null)
248
744
  options.grid.borderColor = options.grid.color;
249
745
  if (options.grid.tickColor == null)
250
746
  options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
251
-
252
- // fill in defaults in axes, copy at least always the
253
- // first as the rest of the code assumes it'll be there
254
- for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
255
- options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
256
- for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
257
- options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
747
+
748
+ // Fill in defaults for axis options, including any unspecified
749
+ // font-spec fields, if a font-spec was provided.
750
+
751
+ // If no x/y axis options were provided, create one of each anyway,
752
+ // since the rest of the code assumes that they exist.
753
+
754
+ var i, axisOptions, axisCount,
755
+ fontSize = placeholder.css("font-size"),
756
+ fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
757
+ fontDefaults = {
758
+ style: placeholder.css("font-style"),
759
+ size: Math.round(0.8 * fontSizeDefault),
760
+ variant: placeholder.css("font-variant"),
761
+ weight: placeholder.css("font-weight"),
762
+ family: placeholder.css("font-family")
763
+ };
764
+
765
+ axisCount = options.xaxes.length || 1;
766
+ for (i = 0; i < axisCount; ++i) {
767
+
768
+ axisOptions = options.xaxes[i];
769
+ if (axisOptions && !axisOptions.tickColor) {
770
+ axisOptions.tickColor = axisOptions.color;
771
+ }
772
+
773
+ axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
774
+ options.xaxes[i] = axisOptions;
775
+
776
+ if (axisOptions.font) {
777
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
778
+ if (!axisOptions.font.color) {
779
+ axisOptions.font.color = axisOptions.color;
780
+ }
781
+ if (!axisOptions.font.lineHeight) {
782
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
783
+ }
784
+ }
785
+ }
786
+
787
+ axisCount = options.yaxes.length || 1;
788
+ for (i = 0; i < axisCount; ++i) {
789
+
790
+ axisOptions = options.yaxes[i];
791
+ if (axisOptions && !axisOptions.tickColor) {
792
+ axisOptions.tickColor = axisOptions.color;
793
+ }
794
+
795
+ axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
796
+ options.yaxes[i] = axisOptions;
797
+
798
+ if (axisOptions.font) {
799
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
800
+ if (!axisOptions.font.color) {
801
+ axisOptions.font.color = axisOptions.color;
802
+ }
803
+ if (!axisOptions.font.lineHeight) {
804
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
805
+ }
806
+ }
807
+ }
258
808
 
259
809
  // backwards compatibility, to be removed in future
260
810
  if (options.xaxis.noTicks && options.xaxis.ticks == null)
@@ -281,6 +831,8 @@
281
831
  $.extend(true, options.series.bars, options.bars);
282
832
  if (options.shadowSize != null)
283
833
  options.series.shadowSize = options.shadowSize;
834
+ if (options.highlightColor != null)
835
+ options.series.highlightColor = options.highlightColor;
284
836
 
285
837
  // save options on axes for future reference
286
838
  for (i = 0; i < options.xaxes.length; ++i)
@@ -301,7 +853,7 @@
301
853
  fillInSeriesOptions();
302
854
  processData();
303
855
  }
304
-
856
+
305
857
  function parseData(d) {
306
858
  var res = [];
307
859
  for (var i = 0; i < d.length; ++i) {
@@ -322,7 +874,7 @@
322
874
 
323
875
  return res;
324
876
  }
325
-
877
+
326
878
  function axisNumber(obj, coord) {
327
879
  var a = obj[coord + "axis"];
328
880
  if (typeof a == "object") // if we got a real axis, extract number
@@ -336,9 +888,9 @@
336
888
  // return flat array without annoying null entries
337
889
  return $.grep(xaxes.concat(yaxes), function (a) { return a; });
338
890
  }
339
-
891
+
340
892
  function canvasToAxisCoords(pos) {
341
- // return an object with x/y corresponding to all used axes
893
+ // return an object with x/y corresponding to all used axes
342
894
  var res = {}, i, axis;
343
895
  for (i = 0; i < xaxes.length; ++i) {
344
896
  axis = xaxes[i];
@@ -351,7 +903,7 @@
351
903
  if (axis && axis.used)
352
904
  res["y" + axis.n] = axis.c2p(pos.top);
353
905
  }
354
-
906
+
355
907
  if (res.x1 !== undefined)
356
908
  res.x = res.x1;
357
909
  if (res.y1 !== undefined)
@@ -359,7 +911,7 @@
359
911
 
360
912
  return res;
361
913
  }
362
-
914
+
363
915
  function axisToCanvasCoords(pos) {
364
916
  // get canvas coords from the first pair of x/y found in pos
365
917
  var res = {}, i, axis, key;
@@ -377,7 +929,7 @@
377
929
  }
378
930
  }
379
931
  }
380
-
932
+
381
933
  for (i = 0; i < yaxes.length; ++i) {
382
934
  axis = yaxes[i];
383
935
  if (axis && axis.used) {
@@ -391,10 +943,10 @@
391
943
  }
392
944
  }
393
945
  }
394
-
946
+
395
947
  return res;
396
948
  }
397
-
949
+
398
950
  function getOrCreateAxis(axes, number) {
399
951
  if (!axes[number - 1])
400
952
  axes[number - 1] = {
@@ -402,64 +954,69 @@
402
954
  direction: axes == xaxes ? "x" : "y",
403
955
  options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
404
956
  };
405
-
957
+
406
958
  return axes[number - 1];
407
959
  }
408
960
 
409
961
  function fillInSeriesOptions() {
410
- var i;
411
-
412
- // collect what we already got of colors
413
- var neededColors = series.length,
414
- usedColors = [],
415
- assignedColors = [];
962
+
963
+ var neededColors = series.length, maxIndex = -1, i;
964
+
965
+ // Subtract the number of series that already have fixed colors or
966
+ // color indexes from the number that we still need to generate.
967
+
416
968
  for (i = 0; i < series.length; ++i) {
417
969
  var sc = series[i].color;
418
970
  if (sc != null) {
419
- --neededColors;
420
- if (typeof sc == "number")
421
- assignedColors.push(sc);
422
- else
423
- usedColors.push($.color.parse(series[i].color));
971
+ neededColors--;
972
+ if (typeof sc == "number" && sc > maxIndex) {
973
+ maxIndex = sc;
974
+ }
424
975
  }
425
976
  }
426
-
427
- // we might need to generate more colors if higher indices
428
- // are assigned
429
- for (i = 0; i < assignedColors.length; ++i) {
430
- neededColors = Math.max(neededColors, assignedColors[i] + 1);
977
+
978
+ // If any of the series have fixed color indexes, then we need to
979
+ // generate at least as many colors as the highest index.
980
+
981
+ if (neededColors <= maxIndex) {
982
+ neededColors = maxIndex + 1;
431
983
  }
432
984
 
433
- // produce colors as needed
434
- var colors = [], variation = 0;
435
- i = 0;
436
- while (colors.length < neededColors) {
437
- var c;
438
- if (options.colors.length == i) // check degenerate case
439
- c = $.color.make(100, 100, 100);
440
- else
441
- c = $.color.parse(options.colors[i]);
442
-
443
- // vary color if needed
444
- var sign = variation % 2 == 1 ? -1 : 1;
445
- c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
446
-
447
- // FIXME: if we're getting to close to something else,
448
- // we should probably skip this one
449
- colors.push(c);
450
-
451
- ++i;
452
- if (i >= options.colors.length) {
453
- i = 0;
454
- ++variation;
985
+ // Generate all the colors, using first the option colors and then
986
+ // variations on those colors once they're exhausted.
987
+
988
+ var c, colors = [], colorPool = options.colors,
989
+ colorPoolSize = colorPool.length, variation = 0;
990
+
991
+ for (i = 0; i < neededColors; i++) {
992
+
993
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
994
+
995
+ // Each time we exhaust the colors in the pool we adjust
996
+ // a scaling factor used to produce more variations on
997
+ // those colors. The factor alternates negative/positive
998
+ // to produce lighter/darker colors.
999
+
1000
+ // Reset the variation after every few cycles, or else
1001
+ // it will end up producing only white or black colors.
1002
+
1003
+ if (i % colorPoolSize == 0 && i) {
1004
+ if (variation >= 0) {
1005
+ if (variation < 0.5) {
1006
+ variation = -variation - 0.2;
1007
+ } else variation = 0;
1008
+ } else variation = -variation;
455
1009
  }
1010
+
1011
+ colors[i] = c.scale('rgb', 1 + variation);
456
1012
  }
457
1013
 
458
- // fill in the options
1014
+ // Finalize the series options, filling in their colors
1015
+
459
1016
  var colori = 0, s;
460
1017
  for (i = 0; i < series.length; ++i) {
461
1018
  s = series[i];
462
-
1019
+
463
1020
  // assign colors
464
1021
  if (s.color == null) {
465
1022
  s.color = colors[colori].toString();
@@ -480,18 +1037,26 @@
480
1037
  s.lines.show = true;
481
1038
  }
482
1039
 
1040
+ // If nothing was provided for lines.zero, default it to match
1041
+ // lines.fill, since areas by default should extend to zero.
1042
+
1043
+ if (s.lines.zero == null) {
1044
+ s.lines.zero = !!s.lines.fill;
1045
+ }
1046
+
483
1047
  // setup axes
484
1048
  s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
485
1049
  s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
486
1050
  }
487
1051
  }
488
-
1052
+
489
1053
  function processData() {
490
1054
  var topSentry = Number.POSITIVE_INFINITY,
491
1055
  bottomSentry = Number.NEGATIVE_INFINITY,
492
1056
  fakeInfinity = Number.MAX_VALUE,
493
1057
  i, j, k, m, length,
494
- s, points, ps, x, y, axis, val, f, p;
1058
+ s, points, ps, x, y, axis, val, f, p,
1059
+ data, format;
495
1060
 
496
1061
  function updateAxis(axis, min, max) {
497
1062
  if (min < axis.datamin && min != -fakeInfinity)
@@ -506,19 +1071,20 @@
506
1071
  axis.datamax = bottomSentry;
507
1072
  axis.used = false;
508
1073
  });
509
-
1074
+
510
1075
  for (i = 0; i < series.length; ++i) {
511
1076
  s = series[i];
512
1077
  s.datapoints = { points: [] };
513
-
1078
+
514
1079
  executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
515
1080
  }
516
-
1081
+
517
1082
  // first pass: clean and copy data
518
1083
  for (i = 0; i < series.length; ++i) {
519
1084
  s = series[i];
520
1085
 
521
- var data = s.data, format = s.datapoints.format;
1086
+ data = s.data;
1087
+ format = s.datapoints.format;
522
1088
 
523
1089
  if (!format) {
524
1090
  format = [];
@@ -527,13 +1093,14 @@
527
1093
  format.push({ y: true, number: true, required: true });
528
1094
 
529
1095
  if (s.bars.show || (s.lines.show && s.lines.fill)) {
530
- format.push({ y: true, number: true, required: false, defaultValue: 0 });
1096
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
1097
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
531
1098
  if (s.bars.horizontal) {
532
1099
  delete format[format.length - 1].y;
533
1100
  format[format.length - 1].x = true;
534
1101
  }
535
1102
  }
536
-
1103
+
537
1104
  s.datapoints.format = format;
538
1105
  }
539
1106
 
@@ -541,13 +1108,13 @@
541
1108
  continue; // already filled in
542
1109
 
543
1110
  s.datapoints.pointsize = format.length;
544
-
1111
+
545
1112
  ps = s.datapoints.pointsize;
546
1113
  points = s.datapoints.points;
547
1114
 
548
- insertSteps = s.lines.show && s.lines.steps;
1115
+ var insertSteps = s.lines.show && s.lines.steps;
549
1116
  s.xaxis.used = s.yaxis.used = true;
550
-
1117
+
551
1118
  for (j = k = 0; j < data.length; ++j, k += ps) {
552
1119
  p = data[j];
553
1120
 
@@ -571,26 +1138,30 @@
571
1138
  if (val == null) {
572
1139
  if (f.required)
573
1140
  nullify = true;
574
-
1141
+
575
1142
  if (f.defaultValue != null)
576
1143
  val = f.defaultValue;
577
1144
  }
578
1145
  }
579
-
1146
+
580
1147
  points[k + m] = val;
581
1148
  }
582
1149
  }
583
-
1150
+
584
1151
  if (nullify) {
585
1152
  for (m = 0; m < ps; ++m) {
586
1153
  val = points[k + m];
587
1154
  if (val != null) {
588
1155
  f = format[m];
589
1156
  // extract min/max info
590
- if (f.x)
591
- updateAxis(s.xaxis, val, val);
592
- if (f.y)
593
- updateAxis(s.yaxis, val, val);
1157
+ if (f.autoscale !== false) {
1158
+ if (f.x) {
1159
+ updateAxis(s.xaxis, val, val);
1160
+ }
1161
+ if (f.y) {
1162
+ updateAxis(s.yaxis, val, val);
1163
+ }
1164
+ }
594
1165
  }
595
1166
  points[k + m] = null;
596
1167
  }
@@ -620,19 +1191,20 @@
620
1191
  // give the hooks a chance to run
621
1192
  for (i = 0; i < series.length; ++i) {
622
1193
  s = series[i];
623
-
1194
+
624
1195
  executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
625
1196
  }
626
1197
 
627
1198
  // second pass: find datamax/datamin for auto-scaling
628
1199
  for (i = 0; i < series.length; ++i) {
629
1200
  s = series[i];
630
- points = s.datapoints.points,
1201
+ points = s.datapoints.points;
631
1202
  ps = s.datapoints.pointsize;
1203
+ format = s.datapoints.format;
632
1204
 
633
1205
  var xmin = topSentry, ymin = topSentry,
634
1206
  xmax = bottomSentry, ymax = bottomSentry;
635
-
1207
+
636
1208
  for (j = 0; j < points.length; j += ps) {
637
1209
  if (points[j] == null)
638
1210
  continue;
@@ -640,9 +1212,9 @@
640
1212
  for (m = 0; m < ps; ++m) {
641
1213
  val = points[j + m];
642
1214
  f = format[m];
643
- if (!f || val == fakeInfinity || val == -fakeInfinity)
1215
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
644
1216
  continue;
645
-
1217
+
646
1218
  if (f.x) {
647
1219
  if (val < xmin)
648
1220
  xmin = val;
@@ -657,10 +1229,22 @@
657
1229
  }
658
1230
  }
659
1231
  }
660
-
1232
+
661
1233
  if (s.bars.show) {
662
1234
  // make sure we got room for the bar on the dancing floor
663
- var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
1235
+ var delta;
1236
+
1237
+ switch (s.bars.align) {
1238
+ case "left":
1239
+ delta = 0;
1240
+ break;
1241
+ case "right":
1242
+ delta = -s.bars.barWidth;
1243
+ break;
1244
+ default:
1245
+ delta = -s.bars.barWidth / 2;
1246
+ }
1247
+
664
1248
  if (s.bars.horizontal) {
665
1249
  ymin += delta;
666
1250
  ymax += delta + s.bars.barWidth;
@@ -670,7 +1254,7 @@
670
1254
  xmax += delta + s.bars.barWidth;
671
1255
  }
672
1256
  }
673
-
1257
+
674
1258
  updateAxis(s.xaxis, xmin, xmax);
675
1259
  updateAxis(s.yaxis, ymin, ymax);
676
1260
  }
@@ -683,103 +1267,35 @@
683
1267
  });
684
1268
  }
685
1269
 
686
- function makeCanvas(skipPositioning, cls) {
687
- var c = document.createElement('canvas');
688
- c.className = cls;
689
- c.width = canvasWidth;
690
- c.height = canvasHeight;
691
-
692
- if (!skipPositioning)
693
- $(c).css({ position: 'absolute', left: 0, top: 0 });
694
-
695
- $(c).appendTo(placeholder);
696
-
697
- if (!c.getContext) // excanvas hack
698
- c = window.G_vmlCanvasManager.initElement(c);
699
-
700
- // used for resetting in case we get replotted
701
- c.getContext("2d").save();
702
-
703
- return c;
704
- }
705
-
706
- function getCanvasDimensions() {
707
- canvasWidth = placeholder.width();
708
- canvasHeight = placeholder.height();
709
-
710
- if (canvasWidth <= 0 || canvasHeight <= 0)
711
- throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
712
- }
1270
+ function setupCanvases() {
713
1271
 
714
- function resizeCanvas(c) {
715
- // resizing should reset the state (excanvas seems to be
716
- // buggy though)
717
- if (c.width != canvasWidth)
718
- c.width = canvasWidth;
1272
+ // Make sure the placeholder is clear of everything except canvases
1273
+ // from a previous plot in this container that we'll try to re-use.
719
1274
 
720
- if (c.height != canvasHeight)
721
- c.height = canvasHeight;
1275
+ placeholder.css("padding", 0) // padding messes up the positioning
1276
+ .children().filter(function(){
1277
+ return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
1278
+ }).remove();
722
1279
 
723
- // so try to get back to the initial state (even if it's
724
- // gone now, this should be safe according to the spec)
725
- var cctx = c.getContext("2d");
726
- cctx.restore();
1280
+ if (placeholder.css("position") == 'static')
1281
+ placeholder.css("position", "relative"); // for positioning labels and overlay
727
1282
 
728
- // and save again
729
- cctx.save();
730
- }
731
-
732
- function setupCanvases() {
733
- var reused,
734
- existingCanvas = placeholder.children("canvas.base"),
735
- existingOverlay = placeholder.children("canvas.overlay");
736
-
737
- if (existingCanvas.length == 0 || existingOverlay == 0) {
738
- // init everything
739
-
740
- placeholder.html(""); // make sure placeholder is clear
741
-
742
- placeholder.css({ padding: 0 }); // padding messes up the positioning
743
-
744
- if (placeholder.css("position") == 'static')
745
- placeholder.css("position", "relative"); // for positioning labels and overlay
746
-
747
- getCanvasDimensions();
748
-
749
- canvas = makeCanvas(true, "base");
750
- overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
751
-
752
- reused = false;
753
- }
754
- else {
755
- // reuse existing elements
1283
+ surface = new Canvas("flot-base", placeholder);
1284
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
756
1285
 
757
- canvas = existingCanvas.get(0);
758
- overlay = existingOverlay.get(0);
1286
+ ctx = surface.context;
1287
+ octx = overlay.context;
759
1288
 
760
- reused = true;
761
- }
1289
+ // define which element we're listening for events on
1290
+ eventHolder = $(overlay.element).unbind();
762
1291
 
763
- ctx = canvas.getContext("2d");
764
- octx = overlay.getContext("2d");
1292
+ // If we're re-using a plot object, shut down the old one
765
1293
 
766
- // we include the canvas in the event holder too, because IE 7
767
- // sometimes has trouble with the stacking order
768
- eventHolder = $([overlay, canvas]);
1294
+ var existing = placeholder.data("plot");
769
1295
 
770
- if (reused) {
771
- // run shutdown in the old plot object
772
- placeholder.data("plot").shutdown();
773
-
774
- // reset reused canvases
775
- plot.resize();
776
-
777
- // make sure overlay pixels are cleared (canvas is cleared when we redraw)
778
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
779
-
780
- // then whack any remaining obvious garbage left
781
- eventHolder.unbind();
782
- placeholder.children().not([canvas, overlay]).remove();
1296
+ if (existing) {
1297
+ existing.shutdown();
1298
+ overlay.clear();
783
1299
  }
784
1300
 
785
1301
  // save in case we get replotted
@@ -790,7 +1306,14 @@
790
1306
  // bind events
791
1307
  if (options.grid.hoverable) {
792
1308
  eventHolder.mousemove(onMouseMove);
793
- eventHolder.mouseleave(onMouseLeave);
1309
+
1310
+ // Use bind, rather than .mouseleave, because we officially
1311
+ // still support jQuery 1.2.6, which doesn't define a shortcut
1312
+ // for mouseenter or mouseleave. This was a bug/oversight that
1313
+ // was fixed somewhere around 1.3.x. We can return to using
1314
+ // .mouseleave when we drop support for 1.2.6.
1315
+
1316
+ eventHolder.bind("mouseleave", onMouseLeave);
794
1317
  }
795
1318
 
796
1319
  if (options.grid.clickable)
@@ -802,23 +1325,23 @@
802
1325
  function shutdown() {
803
1326
  if (redrawTimeout)
804
1327
  clearTimeout(redrawTimeout);
805
-
1328
+
806
1329
  eventHolder.unbind("mousemove", onMouseMove);
807
1330
  eventHolder.unbind("mouseleave", onMouseLeave);
808
1331
  eventHolder.unbind("click", onClick);
809
-
1332
+
810
1333
  executeHooks(hooks.shutdown, [eventHolder]);
811
1334
  }
812
1335
 
813
1336
  function setTransformationHelpers(axis) {
814
1337
  // set helper functions on the axis, assumes plot area
815
1338
  // has been computed already
816
-
1339
+
817
1340
  function identity(x) { return x; }
818
-
1341
+
819
1342
  var s, m, t = axis.options.transform || identity,
820
1343
  it = axis.options.inverseTransform;
821
-
1344
+
822
1345
  // precompute how much the axis is scaling a point
823
1346
  // in canvas space
824
1347
  if (axis.direction == "x") {
@@ -844,129 +1367,108 @@
844
1367
  }
845
1368
 
846
1369
  function measureTickLabels(axis) {
847
- var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
848
- l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
849
1370
 
850
- function makeDummyDiv(labels, width) {
851
- return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
852
- '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
853
- + labels.join("") + '</div></div>')
854
- .appendTo(placeholder);
855
- }
856
-
857
- if (axis.direction == "x") {
858
- // to avoid measuring the widths of the labels (it's slow), we
859
- // construct fixed-size boxes and put the labels inside
860
- // them, we don't need the exact figures and the
861
- // fixed-size box content is easy to center
862
- if (w == null)
863
- w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
864
-
865
- // measure x label heights
866
- if (h == null) {
867
- labels = [];
868
- for (i = 0; i < ticks.length; ++i) {
869
- l = ticks[i].label;
870
- if (l)
871
- labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
872
- }
873
-
874
- if (labels.length > 0) {
875
- // stick them all in the same div and measure
876
- // collective height
877
- labels.push('<div style="clear:left"></div>');
878
- dummyDiv = makeDummyDiv(labels, "width:10000px;");
879
- h = dummyDiv.height();
880
- dummyDiv.remove();
881
- }
882
- }
883
- }
884
- else if (w == null || h == null) {
885
- // calculate y label dimensions
886
- for (i = 0; i < ticks.length; ++i) {
887
- l = ticks[i].label;
888
- if (l)
889
- labels.push('<div class="tickLabel">' + l + '</div>');
890
- }
891
-
892
- if (labels.length > 0) {
893
- dummyDiv = makeDummyDiv(labels, "");
894
- if (w == null)
895
- w = dummyDiv.children().width();
896
- if (h == null)
897
- h = dummyDiv.find("div.tickLabel").height();
898
- dummyDiv.remove();
899
- }
900
- }
1371
+ var opts = axis.options,
1372
+ ticks = axis.ticks || [],
1373
+ labelWidth = opts.labelWidth || 0,
1374
+ labelHeight = opts.labelHeight || 0,
1375
+ maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
1376
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
1377
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
1378
+ font = opts.font || "flot-tick-label tickLabel";
1379
+
1380
+ for (var i = 0; i < ticks.length; ++i) {
901
1381
 
902
- if (w == null)
903
- w = 0;
904
- if (h == null)
905
- h = 0;
1382
+ var t = ticks[i];
1383
+
1384
+ if (!t.label)
1385
+ continue;
906
1386
 
907
- axis.labelWidth = w;
908
- axis.labelHeight = h;
1387
+ var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
1388
+
1389
+ labelWidth = Math.max(labelWidth, info.width);
1390
+ labelHeight = Math.max(labelHeight, info.height);
1391
+ }
1392
+
1393
+ axis.labelWidth = opts.labelWidth || labelWidth;
1394
+ axis.labelHeight = opts.labelHeight || labelHeight;
909
1395
  }
910
1396
 
911
1397
  function allocateAxisBoxFirstPhase(axis) {
912
1398
  // find the bounding box of the axis by looking at label
913
1399
  // widths/heights and ticks, make room by diminishing the
914
- // plotOffset
1400
+ // plotOffset; this first phase only looks at one
1401
+ // dimension per axis, the other dimension depends on the
1402
+ // other axes so will have to wait
915
1403
 
916
1404
  var lw = axis.labelWidth,
917
1405
  lh = axis.labelHeight,
918
1406
  pos = axis.options.position,
1407
+ isXAxis = axis.direction === "x",
919
1408
  tickLength = axis.options.tickLength,
920
- axismargin = options.grid.axisMargin,
1409
+ axisMargin = options.grid.axisMargin,
921
1410
  padding = options.grid.labelMargin,
922
- all = axis.direction == "x" ? xaxes : yaxes,
923
- index;
924
-
925
- // determine axis margin
926
- var samePosition = $.grep(all, function (a) {
927
- return a && a.options.position == pos && a.reserveSpace;
1411
+ innermost = true,
1412
+ outermost = true,
1413
+ first = true,
1414
+ found = false;
1415
+
1416
+ // Determine the axis's position in its direction and on its side
1417
+
1418
+ $.each(isXAxis ? xaxes : yaxes, function(i, a) {
1419
+ if (a && a.reserveSpace) {
1420
+ if (a === axis) {
1421
+ found = true;
1422
+ } else if (a.options.position === pos) {
1423
+ if (found) {
1424
+ outermost = false;
1425
+ } else {
1426
+ innermost = false;
1427
+ }
1428
+ }
1429
+ if (!found) {
1430
+ first = false;
1431
+ }
1432
+ }
928
1433
  });
929
- if ($.inArray(axis, samePosition) == samePosition.length - 1)
930
- axismargin = 0; // outermost
931
1434
 
932
- // determine tick length - if we're innermost, we can use "full"
933
- if (tickLength == null)
934
- tickLength = "full";
1435
+ // The outermost axis on each side has no margin
935
1436
 
936
- var sameDirection = $.grep(all, function (a) {
937
- return a && a.reserveSpace;
938
- });
1437
+ if (outermost) {
1438
+ axisMargin = 0;
1439
+ }
1440
+
1441
+ // The ticks for the first axis in each direction stretch across
1442
+
1443
+ if (tickLength == null) {
1444
+ tickLength = first ? "full" : 5;
1445
+ }
939
1446
 
940
- var innermost = $.inArray(axis, sameDirection) == 0;
941
- if (!innermost && tickLength == "full")
942
- tickLength = 5;
943
-
944
1447
  if (!isNaN(+tickLength))
945
1448
  padding += +tickLength;
946
1449
 
947
- // compute box
948
- if (axis.direction == "x") {
1450
+ if (isXAxis) {
949
1451
  lh += padding;
950
-
1452
+
951
1453
  if (pos == "bottom") {
952
- plotOffset.bottom += lh + axismargin;
953
- axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
1454
+ plotOffset.bottom += lh + axisMargin;
1455
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
954
1456
  }
955
1457
  else {
956
- axis.box = { top: plotOffset.top + axismargin, height: lh };
957
- plotOffset.top += lh + axismargin;
1458
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
1459
+ plotOffset.top += lh + axisMargin;
958
1460
  }
959
1461
  }
960
1462
  else {
961
1463
  lw += padding;
962
-
1464
+
963
1465
  if (pos == "left") {
964
- axis.box = { left: plotOffset.left + axismargin, width: lw };
965
- plotOffset.left += lw + axismargin;
1466
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
1467
+ plotOffset.left += lw + axisMargin;
966
1468
  }
967
1469
  else {
968
- plotOffset.right += lw + axismargin;
969
- axis.box = { left: canvasWidth - plotOffset.right, width: lw };
1470
+ plotOffset.right += lw + axisMargin;
1471
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
970
1472
  }
971
1473
  }
972
1474
 
@@ -978,85 +1480,144 @@
978
1480
  }
979
1481
 
980
1482
  function allocateAxisBoxSecondPhase(axis) {
981
- // set remaining bounding box coordinates
1483
+ // now that all axis boxes have been placed in one
1484
+ // dimension, we can set the remaining dimension coordinates
982
1485
  if (axis.direction == "x") {
983
- axis.box.left = plotOffset.left;
984
- axis.box.width = plotWidth;
1486
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
1487
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
985
1488
  }
986
1489
  else {
987
- axis.box.top = plotOffset.top;
988
- axis.box.height = plotHeight;
1490
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
1491
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
1492
+ }
1493
+ }
1494
+
1495
+ function adjustLayoutForThingsStickingOut() {
1496
+ // possibly adjust plot offset to ensure everything stays
1497
+ // inside the canvas and isn't clipped off
1498
+
1499
+ var minMargin = options.grid.minBorderMargin,
1500
+ axis, i;
1501
+
1502
+ // check stuff from the plot (FIXME: this should just read
1503
+ // a value from the series, otherwise it's impossible to
1504
+ // customize)
1505
+ if (minMargin == null) {
1506
+ minMargin = 0;
1507
+ for (i = 0; i < series.length; ++i)
1508
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
989
1509
  }
1510
+
1511
+ var margins = {
1512
+ left: minMargin,
1513
+ right: minMargin,
1514
+ top: minMargin,
1515
+ bottom: minMargin
1516
+ };
1517
+
1518
+ // check axis labels, note we don't check the actual
1519
+ // labels but instead use the overall width/height to not
1520
+ // jump as much around with replots
1521
+ $.each(allAxes(), function (_, axis) {
1522
+ if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
1523
+ var lastTick = axis.ticks[axis.ticks.length - 1];
1524
+ if (axis.direction === "x") {
1525
+ margins.left = Math.max(margins.left, axis.labelWidth / 2);
1526
+ if (lastTick.v <= axis.max) {
1527
+ margins.right = Math.max(margins.right, axis.labelWidth / 2);
1528
+ }
1529
+ } else {
1530
+ margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
1531
+ if (lastTick.v <= axis.max) {
1532
+ margins.top = Math.max(margins.top, axis.labelHeight / 2);
1533
+ }
1534
+ }
1535
+ }
1536
+ });
1537
+
1538
+ plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
1539
+ plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
1540
+ plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
1541
+ plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
990
1542
  }
991
-
1543
+
992
1544
  function setupGrid() {
993
- var i, axes = allAxes();
1545
+ var i, axes = allAxes(), showGrid = options.grid.show;
1546
+
1547
+ // Initialize the plot's offset from the edge of the canvas
994
1548
 
995
- // first calculate the plot and axis box dimensions
1549
+ for (var a in plotOffset) {
1550
+ var margin = options.grid.margin || 0;
1551
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
1552
+ }
996
1553
 
1554
+ executeHooks(hooks.processOffset, [plotOffset]);
1555
+
1556
+ // If the grid is visible, add its border width to the offset
1557
+
1558
+ for (var a in plotOffset) {
1559
+ if(typeof(options.grid.borderWidth) == "object") {
1560
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
1561
+ }
1562
+ else {
1563
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
1564
+ }
1565
+ }
1566
+
1567
+ // init axes
997
1568
  $.each(axes, function (_, axis) {
998
1569
  axis.show = axis.options.show;
999
1570
  if (axis.show == null)
1000
1571
  axis.show = axis.used; // by default an axis is visible if it's got data
1001
-
1572
+
1002
1573
  axis.reserveSpace = axis.show || axis.options.reserveSpace;
1003
1574
 
1004
1575
  setRange(axis);
1005
1576
  });
1006
1577
 
1007
- allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1578
+ if (showGrid) {
1579
+
1580
+ var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1008
1581
 
1009
- plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
1010
- if (options.grid.show) {
1011
1582
  $.each(allocatedAxes, function (_, axis) {
1012
1583
  // make the ticks
1013
1584
  setupTickGeneration(axis);
1014
1585
  setTicks(axis);
1015
1586
  snapRangeToTicks(axis, axis.ticks);
1016
-
1017
1587
  // find labelWidth/Height for axis
1018
1588
  measureTickLabels(axis);
1019
1589
  });
1020
1590
 
1021
- // with all dimensions in house, we can compute the
1022
- // axis boxes, start from the outside (reverse order)
1591
+ // with all dimensions calculated, we can compute the
1592
+ // axis bounding boxes, start from the outside
1593
+ // (reverse order)
1023
1594
  for (i = allocatedAxes.length - 1; i >= 0; --i)
1024
1595
  allocateAxisBoxFirstPhase(allocatedAxes[i]);
1025
1596
 
1026
1597
  // make sure we've got enough space for things that
1027
1598
  // might stick out
1028
- var minMargin = options.grid.minBorderMargin;
1029
- if (minMargin == null) {
1030
- minMargin = 0;
1031
- for (i = 0; i < series.length; ++i)
1032
- minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
1033
- }
1034
-
1035
- for (var a in plotOffset) {
1036
- plotOffset[a] += options.grid.borderWidth;
1037
- plotOffset[a] = Math.max(minMargin, plotOffset[a]);
1038
- }
1599
+ adjustLayoutForThingsStickingOut();
1600
+
1601
+ $.each(allocatedAxes, function (_, axis) {
1602
+ allocateAxisBoxSecondPhase(axis);
1603
+ });
1039
1604
  }
1040
-
1041
- plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
1042
- plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
1043
1605
 
1044
- // now we got the proper plotWidth/Height, we can compute the scaling
1606
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
1607
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
1608
+
1609
+ // now we got the proper plot dimensions, we can compute the scaling
1045
1610
  $.each(axes, function (_, axis) {
1046
1611
  setTransformationHelpers(axis);
1047
1612
  });
1048
1613
 
1049
- if (options.grid.show) {
1050
- $.each(allocatedAxes, function (_, axis) {
1051
- allocateAxisBoxSecondPhase(axis);
1052
- });
1053
-
1054
- insertAxisLabels();
1614
+ if (showGrid) {
1615
+ drawAxisLabels();
1055
1616
  }
1056
-
1617
+
1057
1618
  insertLegend();
1058
1619
  }
1059
-
1620
+
1060
1621
  function setRange(axis) {
1061
1622
  var opts = axis.options,
1062
1623
  min = +(opts.min != null ? opts.min : axis.datamin),
@@ -1098,7 +1659,7 @@
1098
1659
 
1099
1660
  function setupTickGeneration(axis) {
1100
1661
  var opts = axis.options;
1101
-
1662
+
1102
1663
  // estimate number of ticks
1103
1664
  var noTicks;
1104
1665
  if (typeof opts.ticks == "number" && opts.ticks > 0)
@@ -1106,209 +1667,65 @@
1106
1667
  else
1107
1668
  // heuristic based on the model a*sqrt(x) fitted to
1108
1669
  // some data points that seemed reasonable
1109
- noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
1670
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
1110
1671
 
1111
1672
  var delta = (axis.max - axis.min) / noTicks,
1112
- size, generator, unit, formatter, i, magn, norm;
1113
-
1114
- if (opts.mode == "time") {
1115
- // pretty handling of time
1116
-
1117
- // map of app. size of time units in milliseconds
1118
- var timeUnitSize = {
1119
- "second": 1000,
1120
- "minute": 60 * 1000,
1121
- "hour": 60 * 60 * 1000,
1122
- "day": 24 * 60 * 60 * 1000,
1123
- "month": 30 * 24 * 60 * 60 * 1000,
1124
- "year": 365.2425 * 24 * 60 * 60 * 1000
1125
- };
1673
+ dec = -Math.floor(Math.log(delta) / Math.LN10),
1674
+ maxDec = opts.tickDecimals;
1126
1675
 
1676
+ if (maxDec != null && dec > maxDec) {
1677
+ dec = maxDec;
1678
+ }
1127
1679
 
1128
- // the allowed tick sizes, after 1 year we use
1129
- // an integer algorithm
1130
- var spec = [
1131
- [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1132
- [30, "second"],
1133
- [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1134
- [30, "minute"],
1135
- [1, "hour"], [2, "hour"], [4, "hour"],
1136
- [8, "hour"], [12, "hour"],
1137
- [1, "day"], [2, "day"], [3, "day"],
1138
- [0.25, "month"], [0.5, "month"], [1, "month"],
1139
- [2, "month"], [3, "month"], [6, "month"],
1140
- [1, "year"]
1141
- ];
1142
-
1143
- var minSize = 0;
1144
- if (opts.minTickSize != null) {
1145
- if (typeof opts.tickSize == "number")
1146
- minSize = opts.tickSize;
1147
- else
1148
- minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
1680
+ var magn = Math.pow(10, -dec),
1681
+ norm = delta / magn, // norm is between 1.0 and 10.0
1682
+ size;
1683
+
1684
+ if (norm < 1.5) {
1685
+ size = 1;
1686
+ } else if (norm < 3) {
1687
+ size = 2;
1688
+ // special case for 2.5, requires an extra decimal
1689
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1690
+ size = 2.5;
1691
+ ++dec;
1149
1692
  }
1693
+ } else if (norm < 7.5) {
1694
+ size = 5;
1695
+ } else {
1696
+ size = 10;
1697
+ }
1150
1698
 
1151
- for (var i = 0; i < spec.length - 1; ++i)
1152
- if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
1153
- + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
1154
- && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
1155
- break;
1156
- size = spec[i][0];
1157
- unit = spec[i][1];
1158
-
1159
- // special-case the possibility of several years
1160
- if (unit == "year") {
1161
- magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
1162
- norm = (delta / timeUnitSize.year) / magn;
1163
- if (norm < 1.5)
1164
- size = 1;
1165
- else if (norm < 3)
1166
- size = 2;
1167
- else if (norm < 7.5)
1168
- size = 5;
1169
- else
1170
- size = 10;
1699
+ size *= magn;
1171
1700
 
1172
- size *= magn;
1173
- }
1701
+ if (opts.minTickSize != null && size < opts.minTickSize) {
1702
+ size = opts.minTickSize;
1703
+ }
1174
1704
 
1175
- axis.tickSize = opts.tickSize || [size, unit];
1176
-
1177
- generator = function(axis) {
1178
- var ticks = [],
1179
- tickSize = axis.tickSize[0], unit = axis.tickSize[1],
1180
- d = new Date(axis.min);
1181
-
1182
- var step = tickSize * timeUnitSize[unit];
1183
-
1184
- if (unit == "second")
1185
- d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
1186
- if (unit == "minute")
1187
- d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
1188
- if (unit == "hour")
1189
- d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
1190
- if (unit == "month")
1191
- d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
1192
- if (unit == "year")
1193
- d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
1194
-
1195
- // reset smaller components
1196
- d.setUTCMilliseconds(0);
1197
- if (step >= timeUnitSize.minute)
1198
- d.setUTCSeconds(0);
1199
- if (step >= timeUnitSize.hour)
1200
- d.setUTCMinutes(0);
1201
- if (step >= timeUnitSize.day)
1202
- d.setUTCHours(0);
1203
- if (step >= timeUnitSize.day * 4)
1204
- d.setUTCDate(1);
1205
- if (step >= timeUnitSize.year)
1206
- d.setUTCMonth(0);
1207
-
1208
-
1209
- var carry = 0, v = Number.NaN, prev;
1210
- do {
1211
- prev = v;
1212
- v = d.getTime();
1213
- ticks.push(v);
1214
- if (unit == "month") {
1215
- if (tickSize < 1) {
1216
- // a bit complicated - we'll divide the month
1217
- // up but we need to take care of fractions
1218
- // so we don't end up in the middle of a day
1219
- d.setUTCDate(1);
1220
- var start = d.getTime();
1221
- d.setUTCMonth(d.getUTCMonth() + 1);
1222
- var end = d.getTime();
1223
- d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
1224
- carry = d.getUTCHours();
1225
- d.setUTCHours(0);
1226
- }
1227
- else
1228
- d.setUTCMonth(d.getUTCMonth() + tickSize);
1229
- }
1230
- else if (unit == "year") {
1231
- d.setUTCFullYear(d.getUTCFullYear() + tickSize);
1232
- }
1233
- else
1234
- d.setTime(v + step);
1235
- } while (v < axis.max && v != prev);
1705
+ axis.delta = delta;
1706
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1707
+ axis.tickSize = opts.tickSize || size;
1236
1708
 
1237
- return ticks;
1238
- };
1709
+ // Time mode was moved to a plug-in in 0.8, but since so many people use this
1710
+ // we'll add an especially friendly make sure they remembered to include it.
1239
1711
 
1240
- formatter = function (v, axis) {
1241
- var d = new Date(v);
1242
-
1243
- // first check global format
1244
- if (opts.timeformat != null)
1245
- return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
1246
-
1247
- var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
1248
- var span = axis.max - axis.min;
1249
- var suffix = (opts.twelveHourClock) ? " %p" : "";
1250
-
1251
- if (t < timeUnitSize.minute)
1252
- fmt = "%h:%M:%S" + suffix;
1253
- else if (t < timeUnitSize.day) {
1254
- if (span < 2 * timeUnitSize.day)
1255
- fmt = "%h:%M" + suffix;
1256
- else
1257
- fmt = "%b %d %h:%M" + suffix;
1258
- }
1259
- else if (t < timeUnitSize.month)
1260
- fmt = "%b %d";
1261
- else if (t < timeUnitSize.year) {
1262
- if (span < timeUnitSize.year)
1263
- fmt = "%b";
1264
- else
1265
- fmt = "%b %y";
1266
- }
1267
- else
1268
- fmt = "%y";
1269
-
1270
- return $.plot.formatDate(d, fmt, opts.monthNames);
1271
- };
1712
+ if (opts.mode == "time" && !axis.tickGenerator) {
1713
+ throw new Error("Time mode requires the flot.time plugin.");
1272
1714
  }
1273
- else {
1274
- // pretty rounding of base-10 numbers
1275
- var maxDec = opts.tickDecimals;
1276
- var dec = -Math.floor(Math.log(delta) / Math.LN10);
1277
- if (maxDec != null && dec > maxDec)
1278
- dec = maxDec;
1279
-
1280
- magn = Math.pow(10, -dec);
1281
- norm = delta / magn; // norm is between 1.0 and 10.0
1282
-
1283
- if (norm < 1.5)
1284
- size = 1;
1285
- else if (norm < 3) {
1286
- size = 2;
1287
- // special case for 2.5, requires an extra decimal
1288
- if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1289
- size = 2.5;
1290
- ++dec;
1291
- }
1292
- }
1293
- else if (norm < 7.5)
1294
- size = 5;
1295
- else
1296
- size = 10;
1297
1715
 
1298
- size *= magn;
1299
-
1300
- if (opts.minTickSize != null && size < opts.minTickSize)
1301
- size = opts.minTickSize;
1716
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
1717
+ // like flot.time.js.
1718
+
1719
+ if (!axis.tickGenerator) {
1302
1720
 
1303
- axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1304
- axis.tickSize = opts.tickSize || size;
1721
+ axis.tickGenerator = function (axis) {
1305
1722
 
1306
- generator = function (axis) {
1307
- var ticks = [];
1723
+ var ticks = [],
1724
+ start = floorInBase(axis.min, axis.tickSize),
1725
+ i = 0,
1726
+ v = Number.NaN,
1727
+ prev;
1308
1728
 
1309
- // spew out all possible ticks
1310
- var start = floorInBase(axis.min, axis.tickSize),
1311
- i = 0, v = Number.NaN, prev;
1312
1729
  do {
1313
1730
  prev = v;
1314
1731
  v = start + i * axis.tickSize;
@@ -1318,24 +1735,42 @@
1318
1735
  return ticks;
1319
1736
  };
1320
1737
 
1321
- formatter = function (v, axis) {
1322
- return v.toFixed(axis.tickDecimals);
1738
+ axis.tickFormatter = function (value, axis) {
1739
+
1740
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
1741
+ var formatted = "" + Math.round(value * factor) / factor;
1742
+
1743
+ // If tickDecimals was specified, ensure that we have exactly that
1744
+ // much precision; otherwise default to the value's own precision.
1745
+
1746
+ if (axis.tickDecimals != null) {
1747
+ var decimal = formatted.indexOf(".");
1748
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
1749
+ if (precision < axis.tickDecimals) {
1750
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
1751
+ }
1752
+ }
1753
+
1754
+ return formatted;
1323
1755
  };
1324
1756
  }
1325
1757
 
1758
+ if ($.isFunction(opts.tickFormatter))
1759
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1760
+
1326
1761
  if (opts.alignTicksWithAxis != null) {
1327
1762
  var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1328
1763
  if (otherAxis && otherAxis.used && otherAxis != axis) {
1329
1764
  // consider snapping min/max to outermost nice ticks
1330
- var niceTicks = generator(axis);
1765
+ var niceTicks = axis.tickGenerator(axis);
1331
1766
  if (niceTicks.length > 0) {
1332
1767
  if (opts.min == null)
1333
1768
  axis.min = Math.min(axis.min, niceTicks[0]);
1334
1769
  if (opts.max == null && niceTicks.length > 1)
1335
1770
  axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1336
1771
  }
1337
-
1338
- generator = function (axis) {
1772
+
1773
+ axis.tickGenerator = function (axis) {
1339
1774
  // copy ticks, scaled to this axis
1340
1775
  var ticks = [], v, i;
1341
1776
  for (i = 0; i < otherAxis.ticks.length; ++i) {
@@ -1345,12 +1780,12 @@
1345
1780
  }
1346
1781
  return ticks;
1347
1782
  };
1348
-
1783
+
1349
1784
  // we might need an extra decimal since forced
1350
1785
  // ticks don't necessarily fit naturally
1351
- if (axis.mode != "time" && opts.tickDecimals == null) {
1352
- var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
1353
- ts = generator(axis);
1786
+ if (!axis.mode && opts.tickDecimals == null) {
1787
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
1788
+ ts = axis.tickGenerator(axis);
1354
1789
 
1355
1790
  // only proceed if the tick interval rounded
1356
1791
  // with an extra decimal doesn't give us a
@@ -1360,14 +1795,8 @@
1360
1795
  }
1361
1796
  }
1362
1797
  }
1363
-
1364
- axis.tickGenerator = generator;
1365
- if ($.isFunction(opts.tickFormatter))
1366
- axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1367
- else
1368
- axis.tickFormatter = formatter;
1369
1798
  }
1370
-
1799
+
1371
1800
  function setTicks(axis) {
1372
1801
  var oticks = axis.options.ticks, ticks = [];
1373
1802
  if (oticks == null || (typeof oticks == "number" && oticks > 0))
@@ -1375,7 +1804,7 @@
1375
1804
  else if (oticks) {
1376
1805
  if ($.isFunction(oticks))
1377
1806
  // generate the ticks
1378
- ticks = oticks({ min: axis.min, max: axis.max });
1807
+ ticks = oticks(axis);
1379
1808
  else
1380
1809
  ticks = oticks;
1381
1810
  }
@@ -1409,18 +1838,22 @@
1409
1838
  axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1410
1839
  }
1411
1840
  }
1412
-
1841
+
1413
1842
  function draw() {
1414
- ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1843
+
1844
+ surface.clear();
1845
+
1846
+ executeHooks(hooks.drawBackground, [ctx]);
1415
1847
 
1416
1848
  var grid = options.grid;
1417
1849
 
1418
1850
  // draw background, if any
1419
1851
  if (grid.show && grid.backgroundColor)
1420
1852
  drawBackground();
1421
-
1422
- if (grid.show && !grid.aboveData)
1853
+
1854
+ if (grid.show && !grid.aboveData) {
1423
1855
  drawGrid();
1856
+ }
1424
1857
 
1425
1858
  for (var i = 0; i < series.length; ++i) {
1426
1859
  executeHooks(hooks.drawSeries, [ctx, series[i]]);
@@ -1428,15 +1861,23 @@
1428
1861
  }
1429
1862
 
1430
1863
  executeHooks(hooks.draw, [ctx]);
1431
-
1432
- if (grid.show && grid.aboveData)
1864
+
1865
+ if (grid.show && grid.aboveData) {
1433
1866
  drawGrid();
1867
+ }
1868
+
1869
+ surface.render();
1870
+
1871
+ // A draw implies that either the axes or data have changed, so we
1872
+ // should probably update the overlay highlights as well.
1873
+
1874
+ triggerRedrawOverlay();
1434
1875
  }
1435
1876
 
1436
1877
  function extractRange(ranges, coord) {
1437
1878
  var axis, from, to, key, axes = allAxes();
1438
1879
 
1439
- for (i = 0; i < axes.length; ++i) {
1880
+ for (var i = 0; i < axes.length; ++i) {
1440
1881
  axis = axes[i];
1441
1882
  if (axis.direction == coord) {
1442
1883
  key = coord + axis.n + "axis";
@@ -1463,10 +1904,10 @@
1463
1904
  from = to;
1464
1905
  to = tmp;
1465
1906
  }
1466
-
1907
+
1467
1908
  return { from: from, to: to, axis: axis };
1468
1909
  }
1469
-
1910
+
1470
1911
  function drawBackground() {
1471
1912
  ctx.save();
1472
1913
  ctx.translate(plotOffset.left, plotOffset.top);
@@ -1477,8 +1918,8 @@
1477
1918
  }
1478
1919
 
1479
1920
  function drawGrid() {
1480
- var i;
1481
-
1921
+ var i, axes, bw, bc;
1922
+
1482
1923
  ctx.save();
1483
1924
  ctx.translate(plotOffset.left, plotOffset.top);
1484
1925
 
@@ -1486,14 +1927,14 @@
1486
1927
  var markings = options.grid.markings;
1487
1928
  if (markings) {
1488
1929
  if ($.isFunction(markings)) {
1489
- var axes = plot.getAxes();
1930
+ axes = plot.getAxes();
1490
1931
  // xmin etc. is backwards compatibility, to be
1491
1932
  // removed in the future
1492
1933
  axes.xmin = axes.xaxis.min;
1493
1934
  axes.xmax = axes.xaxis.max;
1494
1935
  axes.ymin = axes.yaxis.min;
1495
1936
  axes.ymax = axes.yaxis.max;
1496
-
1937
+
1497
1938
  markings = markings(axes);
1498
1939
  }
1499
1940
 
@@ -1530,7 +1971,7 @@
1530
1971
  xrange.to = xrange.axis.p2c(xrange.to);
1531
1972
  yrange.from = yrange.axis.p2c(yrange.from);
1532
1973
  yrange.to = yrange.axis.p2c(yrange.to);
1533
-
1974
+
1534
1975
  if (xrange.from == xrange.to || yrange.from == yrange.to) {
1535
1976
  // draw line
1536
1977
  ctx.beginPath();
@@ -1549,17 +1990,17 @@
1549
1990
  }
1550
1991
  }
1551
1992
  }
1552
-
1993
+
1553
1994
  // draw the ticks
1554
- var axes = allAxes(), bw = options.grid.borderWidth;
1995
+ axes = allAxes();
1996
+ bw = options.grid.borderWidth;
1555
1997
 
1556
1998
  for (var j = 0; j < axes.length; ++j) {
1557
1999
  var axis = axes[j], box = axis.box,
1558
2000
  t = axis.tickLength, x, y, xoff, yoff;
1559
2001
  if (!axis.show || axis.ticks.length == 0)
1560
- continue
1561
-
1562
- ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
2002
+ continue;
2003
+
1563
2004
  ctx.lineWidth = 1;
1564
2005
 
1565
2006
  // find the edges
@@ -1577,19 +2018,23 @@
1577
2018
  else
1578
2019
  x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
1579
2020
  }
1580
-
2021
+
1581
2022
  // draw tick bar
1582
2023
  if (!axis.innermost) {
2024
+ ctx.strokeStyle = axis.options.color;
1583
2025
  ctx.beginPath();
1584
2026
  xoff = yoff = 0;
1585
2027
  if (axis.direction == "x")
1586
- xoff = plotWidth;
2028
+ xoff = plotWidth + 1;
1587
2029
  else
1588
- yoff = plotHeight;
1589
-
2030
+ yoff = plotHeight + 1;
2031
+
1590
2032
  if (ctx.lineWidth == 1) {
1591
- x = Math.floor(x) + 0.5;
1592
- y = Math.floor(y) + 0.5;
2033
+ if (axis.direction == "x") {
2034
+ y = Math.floor(y) + 0.5;
2035
+ } else {
2036
+ x = Math.floor(x) + 0.5;
2037
+ }
1593
2038
  }
1594
2039
 
1595
2040
  ctx.moveTo(x, y);
@@ -1598,29 +2043,33 @@
1598
2043
  }
1599
2044
 
1600
2045
  // draw ticks
2046
+
2047
+ ctx.strokeStyle = axis.options.tickColor;
2048
+
1601
2049
  ctx.beginPath();
1602
2050
  for (i = 0; i < axis.ticks.length; ++i) {
1603
2051
  var v = axis.ticks[i].v;
1604
-
2052
+
1605
2053
  xoff = yoff = 0;
1606
2054
 
1607
- if (v < axis.min || v > axis.max
2055
+ if (isNaN(v) || v < axis.min || v > axis.max
1608
2056
  // skip those lying on the axes if we got a border
1609
- || (t == "full" && bw > 0
2057
+ || (t == "full"
2058
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
1610
2059
  && (v == axis.min || v == axis.max)))
1611
2060
  continue;
1612
2061
 
1613
2062
  if (axis.direction == "x") {
1614
2063
  x = axis.p2c(v);
1615
2064
  yoff = t == "full" ? -plotHeight : t;
1616
-
2065
+
1617
2066
  if (axis.position == "top")
1618
2067
  yoff = -yoff;
1619
2068
  }
1620
2069
  else {
1621
2070
  y = axis.p2c(v);
1622
2071
  xoff = t == "full" ? -plotWidth : t;
1623
-
2072
+
1624
2073
  if (axis.position == "left")
1625
2074
  xoff = -xoff;
1626
2075
  }
@@ -1635,74 +2084,117 @@
1635
2084
  ctx.moveTo(x, y);
1636
2085
  ctx.lineTo(x + xoff, y + yoff);
1637
2086
  }
1638
-
2087
+
1639
2088
  ctx.stroke();
1640
2089
  }
1641
-
1642
-
2090
+
2091
+
1643
2092
  // draw border
1644
2093
  if (bw) {
1645
- ctx.lineWidth = bw;
1646
- ctx.strokeStyle = options.grid.borderColor;
1647
- ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
2094
+ // If either borderWidth or borderColor is an object, then draw the border
2095
+ // line by line instead of as one rectangle
2096
+ bc = options.grid.borderColor;
2097
+ if(typeof bw == "object" || typeof bc == "object") {
2098
+ if (typeof bw !== "object") {
2099
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
2100
+ }
2101
+ if (typeof bc !== "object") {
2102
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
2103
+ }
2104
+
2105
+ if (bw.top > 0) {
2106
+ ctx.strokeStyle = bc.top;
2107
+ ctx.lineWidth = bw.top;
2108
+ ctx.beginPath();
2109
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
2110
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
2111
+ ctx.stroke();
2112
+ }
2113
+
2114
+ if (bw.right > 0) {
2115
+ ctx.strokeStyle = bc.right;
2116
+ ctx.lineWidth = bw.right;
2117
+ ctx.beginPath();
2118
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
2119
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
2120
+ ctx.stroke();
2121
+ }
2122
+
2123
+ if (bw.bottom > 0) {
2124
+ ctx.strokeStyle = bc.bottom;
2125
+ ctx.lineWidth = bw.bottom;
2126
+ ctx.beginPath();
2127
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
2128
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
2129
+ ctx.stroke();
2130
+ }
2131
+
2132
+ if (bw.left > 0) {
2133
+ ctx.strokeStyle = bc.left;
2134
+ ctx.lineWidth = bw.left;
2135
+ ctx.beginPath();
2136
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
2137
+ ctx.lineTo(0- bw.left/2, 0);
2138
+ ctx.stroke();
2139
+ }
2140
+ }
2141
+ else {
2142
+ ctx.lineWidth = bw;
2143
+ ctx.strokeStyle = options.grid.borderColor;
2144
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
2145
+ }
1648
2146
  }
1649
2147
 
1650
2148
  ctx.restore();
1651
2149
  }
1652
2150
 
1653
- function insertAxisLabels() {
1654
- placeholder.find(".tickLabels").remove();
1655
-
1656
- var html = ['<div class="tickLabels" style="font-size:smaller">'];
2151
+ function drawAxisLabels() {
2152
+
2153
+ $.each(allAxes(), function (_, axis) {
2154
+ var box = axis.box,
2155
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
2156
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
2157
+ font = axis.options.font || "flot-tick-label tickLabel",
2158
+ tick, x, y, halign, valign;
2159
+
2160
+ // Remove text before checking for axis.show and ticks.length;
2161
+ // otherwise plugins, like flot-tickrotor, that draw their own
2162
+ // tick labels will end up with both theirs and the defaults.
2163
+
2164
+ surface.removeText(layer);
2165
+
2166
+ if (!axis.show || axis.ticks.length == 0)
2167
+ return;
1657
2168
 
1658
- var axes = allAxes();
1659
- for (var j = 0; j < axes.length; ++j) {
1660
- var axis = axes[j], box = axis.box;
1661
- if (!axis.show)
1662
- continue;
1663
- //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
1664
- html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
1665
2169
  for (var i = 0; i < axis.ticks.length; ++i) {
1666
- var tick = axis.ticks[i];
2170
+
2171
+ tick = axis.ticks[i];
1667
2172
  if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1668
2173
  continue;
1669
2174
 
1670
- var pos = {}, align;
1671
-
1672
2175
  if (axis.direction == "x") {
1673
- align = "center";
1674
- pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2);
1675
- if (axis.position == "bottom")
1676
- pos.top = box.top + box.padding;
1677
- else
1678
- pos.bottom = canvasHeight - (box.top + box.height - box.padding);
1679
- }
1680
- else {
1681
- pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2);
1682
- if (axis.position == "left") {
1683
- pos.right = canvasWidth - (box.left + box.width - box.padding)
1684
- align = "right";
2176
+ halign = "center";
2177
+ x = plotOffset.left + axis.p2c(tick.v);
2178
+ if (axis.position == "bottom") {
2179
+ y = box.top + box.padding;
2180
+ } else {
2181
+ y = box.top + box.height - box.padding;
2182
+ valign = "bottom";
1685
2183
  }
1686
- else {
1687
- pos.left = box.left + box.padding;
1688
- align = "left";
2184
+ } else {
2185
+ valign = "middle";
2186
+ y = plotOffset.top + axis.p2c(tick.v);
2187
+ if (axis.position == "left") {
2188
+ x = box.left + box.width - box.padding;
2189
+ halign = "right";
2190
+ } else {
2191
+ x = box.left + box.padding;
1689
2192
  }
1690
2193
  }
1691
2194
 
1692
- pos.width = axis.labelWidth;
1693
-
1694
- var style = ["position:absolute", "text-align:" + align ];
1695
- for (var a in pos)
1696
- style.push(a + ":" + pos[a] + "px")
1697
-
1698
- html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
2195
+ surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
1699
2196
  }
1700
- html.push('</div>');
1701
- }
1702
-
1703
- html.push('</div>');
1704
-
1705
- placeholder.append(html.join(""));
2197
+ });
1706
2198
  }
1707
2199
 
1708
2200
  function drawSeries(series) {
@@ -1713,18 +2205,18 @@
1713
2205
  if (series.points.show)
1714
2206
  drawSeriesPoints(series);
1715
2207
  }
1716
-
2208
+
1717
2209
  function drawSeriesLines(series) {
1718
2210
  function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1719
2211
  var points = datapoints.points,
1720
2212
  ps = datapoints.pointsize,
1721
2213
  prevx = null, prevy = null;
1722
-
2214
+
1723
2215
  ctx.beginPath();
1724
2216
  for (var i = ps; i < points.length; i += ps) {
1725
2217
  var x1 = points[i - ps], y1 = points[i - ps + 1],
1726
2218
  x2 = points[i], y2 = points[i + 1];
1727
-
2219
+
1728
2220
  if (x1 == null || x2 == null)
1729
2221
  continue;
1730
2222
 
@@ -1787,7 +2279,7 @@
1787
2279
 
1788
2280
  if (x1 != prevx || y1 != prevy)
1789
2281
  ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1790
-
2282
+
1791
2283
  prevx = x2;
1792
2284
  prevy = y2;
1793
2285
  ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
@@ -1839,7 +2331,7 @@
1839
2331
  continue;
1840
2332
 
1841
2333
  // clip x values
1842
-
2334
+
1843
2335
  // clip with xmin
1844
2336
  if (x1 <= x2 && x1 < axisx.min) {
1845
2337
  if (x2 < axisx.min)
@@ -1874,7 +2366,7 @@
1874
2366
  ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1875
2367
  areaOpen = true;
1876
2368
  }
1877
-
2369
+
1878
2370
  // now first check the case where both is outside
1879
2371
  if (y1 >= axisy.max && y2 >= axisy.max) {
1880
2372
  ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
@@ -1886,7 +2378,7 @@
1886
2378
  ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1887
2379
  continue;
1888
2380
  }
1889
-
2381
+
1890
2382
  // else it's a bit more complicated, there might
1891
2383
  // be a flat maxed out rectangle first, then a
1892
2384
  // triangular cutout or reverse; to find these
@@ -1895,7 +2387,7 @@
1895
2387
 
1896
2388
  // clip the y values, without shortcutting, we
1897
2389
  // go through all cases in turn
1898
-
2390
+
1899
2391
  // clip with ymin
1900
2392
  if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1901
2393
  x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
@@ -1922,7 +2414,7 @@
1922
2414
  ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
1923
2415
  // it goes to (x1, y1), but we fill that below
1924
2416
  }
1925
-
2417
+
1926
2418
  // fill triangular section, this sometimes result
1927
2419
  // in redundant points if (x1, y1) hasn't changed
1928
2420
  // from previous line to, but we just ignore that
@@ -1976,7 +2468,7 @@
1976
2468
  var x = points[i], y = points[i + 1];
1977
2469
  if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1978
2470
  continue;
1979
-
2471
+
1980
2472
  ctx.beginPath();
1981
2473
  x = axisx.p2c(x);
1982
2474
  y = axisy.p2c(y) + offset;
@@ -1985,7 +2477,7 @@
1985
2477
  else
1986
2478
  symbol(ctx, x, y, radius, shadow);
1987
2479
  ctx.closePath();
1988
-
2480
+
1989
2481
  if (fillStyle) {
1990
2482
  ctx.fillStyle = fillStyle;
1991
2483
  ctx.fill();
@@ -1993,7 +2485,7 @@
1993
2485
  ctx.stroke();
1994
2486
  }
1995
2487
  }
1996
-
2488
+
1997
2489
  ctx.save();
1998
2490
  ctx.translate(plotOffset.left, plotOffset.top);
1999
2491
 
@@ -2001,6 +2493,15 @@
2001
2493
  sw = series.shadowSize,
2002
2494
  radius = series.points.radius,
2003
2495
  symbol = series.points.symbol;
2496
+
2497
+ // If the user sets the line width to 0, we change it to a very
2498
+ // small value. A line width of 0 seems to force the default of 1.
2499
+ // Doing the conditional here allows the shadow setting to still be
2500
+ // optional even with a lineWidth of 0.
2501
+
2502
+ if( lw == 0 )
2503
+ lw = 0.0001;
2504
+
2004
2505
  if (lw > 0 && sw > 0) {
2005
2506
  // draw shadow in two steps
2006
2507
  var w = sw / 2;
@@ -2022,7 +2523,7 @@
2022
2523
  ctx.restore();
2023
2524
  }
2024
2525
 
2025
- function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2526
+ function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2026
2527
  var left, right, bottom, top,
2027
2528
  drawLeft, drawRight, drawTop, drawBottom,
2028
2529
  tmp;
@@ -2064,12 +2565,12 @@
2064
2565
  drawTop = false;
2065
2566
  }
2066
2567
  }
2067
-
2568
+
2068
2569
  // clip
2069
2570
  if (right < axisx.min || left > axisx.max ||
2070
2571
  top < axisy.min || bottom > axisy.max)
2071
2572
  return;
2072
-
2573
+
2073
2574
  if (left < axisx.min) {
2074
2575
  left = axisx.min;
2075
2576
  drawLeft = false;
@@ -2084,7 +2585,7 @@
2084
2585
  bottom = axisy.min;
2085
2586
  drawBottom = false;
2086
2587
  }
2087
-
2588
+
2088
2589
  if (top > axisy.max) {
2089
2590
  top = axisy.max;
2090
2591
  drawTop = false;
@@ -2094,16 +2595,11 @@
2094
2595
  bottom = axisy.p2c(bottom);
2095
2596
  right = axisx.p2c(right);
2096
2597
  top = axisy.p2c(top);
2097
-
2598
+
2098
2599
  // fill the bar
2099
2600
  if (fillStyleCallback) {
2100
- c.beginPath();
2101
- c.moveTo(left, bottom);
2102
- c.lineTo(left, top);
2103
- c.lineTo(right, top);
2104
- c.lineTo(right, bottom);
2105
2601
  c.fillStyle = fillStyleCallback(bottom, top);
2106
- c.fill();
2602
+ c.fillRect(left, top, right - left, bottom - top)
2107
2603
  }
2108
2604
 
2109
2605
  // draw outline
@@ -2111,35 +2607,35 @@
2111
2607
  c.beginPath();
2112
2608
 
2113
2609
  // FIXME: inline moveTo is buggy with excanvas
2114
- c.moveTo(left, bottom + offset);
2610
+ c.moveTo(left, bottom);
2115
2611
  if (drawLeft)
2116
- c.lineTo(left, top + offset);
2612
+ c.lineTo(left, top);
2117
2613
  else
2118
- c.moveTo(left, top + offset);
2614
+ c.moveTo(left, top);
2119
2615
  if (drawTop)
2120
- c.lineTo(right, top + offset);
2616
+ c.lineTo(right, top);
2121
2617
  else
2122
- c.moveTo(right, top + offset);
2618
+ c.moveTo(right, top);
2123
2619
  if (drawRight)
2124
- c.lineTo(right, bottom + offset);
2620
+ c.lineTo(right, bottom);
2125
2621
  else
2126
- c.moveTo(right, bottom + offset);
2622
+ c.moveTo(right, bottom);
2127
2623
  if (drawBottom)
2128
- c.lineTo(left, bottom + offset);
2624
+ c.lineTo(left, bottom);
2129
2625
  else
2130
- c.moveTo(left, bottom + offset);
2626
+ c.moveTo(left, bottom);
2131
2627
  c.stroke();
2132
2628
  }
2133
2629
  }
2134
-
2630
+
2135
2631
  function drawSeriesBars(series) {
2136
- function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2632
+ function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
2137
2633
  var points = datapoints.points, ps = datapoints.pointsize;
2138
-
2634
+
2139
2635
  for (var i = 0; i < points.length; i += ps) {
2140
2636
  if (points[i] == null)
2141
2637
  continue;
2142
- drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2638
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2143
2639
  }
2144
2640
  }
2145
2641
 
@@ -2149,9 +2645,22 @@
2149
2645
  // FIXME: figure out a way to add shadows (for instance along the right edge)
2150
2646
  ctx.lineWidth = series.bars.lineWidth;
2151
2647
  ctx.strokeStyle = series.color;
2152
- var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2648
+
2649
+ var barLeft;
2650
+
2651
+ switch (series.bars.align) {
2652
+ case "left":
2653
+ barLeft = 0;
2654
+ break;
2655
+ case "right":
2656
+ barLeft = -series.bars.barWidth;
2657
+ break;
2658
+ default:
2659
+ barLeft = -series.bars.barWidth / 2;
2660
+ }
2661
+
2153
2662
  var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2154
- plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2663
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
2155
2664
  ctx.restore();
2156
2665
  }
2157
2666
 
@@ -2162,27 +2671,66 @@
2162
2671
 
2163
2672
  if (filloptions.fillColor)
2164
2673
  return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2165
-
2674
+
2166
2675
  var c = $.color.parse(seriesColor);
2167
2676
  c.a = typeof fill == "number" ? fill : 0.4;
2168
2677
  c.normalize();
2169
2678
  return c.toString();
2170
2679
  }
2171
-
2680
+
2172
2681
  function insertLegend() {
2173
- placeholder.find(".legend").remove();
2174
2682
 
2175
- if (!options.legend.show)
2683
+ if (options.legend.container != null) {
2684
+ $(options.legend.container).html("");
2685
+ } else {
2686
+ placeholder.find(".legend").remove();
2687
+ }
2688
+
2689
+ if (!options.legend.show) {
2176
2690
  return;
2177
-
2178
- var fragments = [], rowStarted = false,
2691
+ }
2692
+
2693
+ var fragments = [], entries = [], rowStarted = false,
2179
2694
  lf = options.legend.labelFormatter, s, label;
2695
+
2696
+ // Build a list of legend entries, with each having a label and a color
2697
+
2180
2698
  for (var i = 0; i < series.length; ++i) {
2181
2699
  s = series[i];
2182
- label = s.label;
2183
- if (!label)
2184
- continue;
2185
-
2700
+ if (s.label) {
2701
+ label = lf ? lf(s.label, s) : s.label;
2702
+ if (label) {
2703
+ entries.push({
2704
+ label: label,
2705
+ color: s.color
2706
+ });
2707
+ }
2708
+ }
2709
+ }
2710
+
2711
+ // Sort the legend using either the default or a custom comparator
2712
+
2713
+ if (options.legend.sorted) {
2714
+ if ($.isFunction(options.legend.sorted)) {
2715
+ entries.sort(options.legend.sorted);
2716
+ } else if (options.legend.sorted == "reverse") {
2717
+ entries.reverse();
2718
+ } else {
2719
+ var ascending = options.legend.sorted != "descending";
2720
+ entries.sort(function(a, b) {
2721
+ return a.label == b.label ? 0 : (
2722
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
2723
+ );
2724
+ });
2725
+ }
2726
+ }
2727
+
2728
+ // Generate markup for the list of entries, in their final order
2729
+
2730
+ for (var i = 0; i < entries.length; ++i) {
2731
+
2732
+ var entry = entries[i];
2733
+
2186
2734
  if (i % options.legend.noColumns == 0) {
2187
2735
  if (rowStarted)
2188
2736
  fragments.push('</tr>');
@@ -2190,16 +2738,15 @@
2190
2738
  rowStarted = true;
2191
2739
  }
2192
2740
 
2193
- if (lf)
2194
- label = lf(label, s);
2195
-
2196
2741
  fragments.push(
2197
- '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
2198
- '<td class="legendLabel">' + label + '</td>');
2742
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
2743
+ '<td class="legendLabel">' + entry.label + '</td>'
2744
+ );
2199
2745
  }
2746
+
2200
2747
  if (rowStarted)
2201
2748
  fragments.push('</tr>');
2202
-
2749
+
2203
2750
  if (fragments.length == 0)
2204
2751
  return;
2205
2752
 
@@ -2243,43 +2790,43 @@
2243
2790
 
2244
2791
 
2245
2792
  // interactive features
2246
-
2793
+
2247
2794
  var highlights = [],
2248
2795
  redrawTimeout = null;
2249
-
2796
+
2250
2797
  // returns the data item the mouse is over, or null if none is found
2251
2798
  function findNearbyItem(mouseX, mouseY, seriesFilter) {
2252
2799
  var maxDistance = options.grid.mouseActiveRadius,
2253
2800
  smallestDistance = maxDistance * maxDistance + 1,
2254
- item = null, foundPoint = false, i, j;
2801
+ item = null, foundPoint = false, i, j, ps;
2255
2802
 
2256
2803
  for (i = series.length - 1; i >= 0; --i) {
2257
2804
  if (!seriesFilter(series[i]))
2258
2805
  continue;
2259
-
2806
+
2260
2807
  var s = series[i],
2261
2808
  axisx = s.xaxis,
2262
2809
  axisy = s.yaxis,
2263
2810
  points = s.datapoints.points,
2264
- ps = s.datapoints.pointsize,
2265
2811
  mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2266
2812
  my = axisy.c2p(mouseY),
2267
2813
  maxx = maxDistance / axisx.scale,
2268
2814
  maxy = maxDistance / axisy.scale;
2269
2815
 
2816
+ ps = s.datapoints.pointsize;
2270
2817
  // with inverse transforms, we can't use the maxx/maxy
2271
2818
  // optimization, sadly
2272
2819
  if (axisx.options.inverseTransform)
2273
2820
  maxx = Number.MAX_VALUE;
2274
2821
  if (axisy.options.inverseTransform)
2275
2822
  maxy = Number.MAX_VALUE;
2276
-
2823
+
2277
2824
  if (s.lines.show || s.points.show) {
2278
2825
  for (j = 0; j < points.length; j += ps) {
2279
2826
  var x = points[j], y = points[j + 1];
2280
2827
  if (x == null)
2281
2828
  continue;
2282
-
2829
+
2283
2830
  // For points and lines, the cursor must be within a
2284
2831
  // certain distance to the data point
2285
2832
  if (x - mx > maxx || x - mx < -maxx ||
@@ -2300,19 +2847,32 @@
2300
2847
  }
2301
2848
  }
2302
2849
  }
2303
-
2850
+
2304
2851
  if (s.bars.show && !item) { // no other point can be nearby
2305
- var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2306
- barRight = barLeft + s.bars.barWidth;
2307
-
2852
+
2853
+ var barLeft, barRight;
2854
+
2855
+ switch (s.bars.align) {
2856
+ case "left":
2857
+ barLeft = 0;
2858
+ break;
2859
+ case "right":
2860
+ barLeft = -s.bars.barWidth;
2861
+ break;
2862
+ default:
2863
+ barLeft = -s.bars.barWidth / 2;
2864
+ }
2865
+
2866
+ barRight = barLeft + s.bars.barWidth;
2867
+
2308
2868
  for (j = 0; j < points.length; j += ps) {
2309
2869
  var x = points[j], y = points[j + 1], b = points[j + 2];
2310
2870
  if (x == null)
2311
2871
  continue;
2312
-
2872
+
2313
2873
  // for a bar graph, the cursor must be inside the bar
2314
- if (series[i].bars.horizontal ?
2315
- (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2874
+ if (series[i].bars.horizontal ?
2875
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2316
2876
  my >= y + barLeft && my <= y + barRight) :
2317
2877
  (mx >= x + barLeft && mx <= x + barRight &&
2318
2878
  my >= Math.min(b, y) && my <= Math.max(b, y)))
@@ -2325,13 +2885,13 @@
2325
2885
  i = item[0];
2326
2886
  j = item[1];
2327
2887
  ps = series[i].datapoints.pointsize;
2328
-
2888
+
2329
2889
  return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2330
2890
  dataIndex: j,
2331
2891
  series: series[i],
2332
2892
  seriesIndex: i };
2333
2893
  }
2334
-
2894
+
2335
2895
  return null;
2336
2896
  }
2337
2897
 
@@ -2367,8 +2927,8 @@
2367
2927
 
2368
2928
  if (item) {
2369
2929
  // fill in mouse pos for any listeners out there
2370
- item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
2371
- item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
2930
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
2931
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
2372
2932
  }
2373
2933
 
2374
2934
  if (options.grid.autoHighlight) {
@@ -2381,17 +2941,23 @@
2381
2941
  h.point[1] == item.datapoint[1]))
2382
2942
  unhighlight(h.series, h.point);
2383
2943
  }
2384
-
2944
+
2385
2945
  if (item)
2386
2946
  highlight(item.series, item.datapoint, eventname);
2387
2947
  }
2388
-
2948
+
2389
2949
  placeholder.trigger(eventname, [ pos, item ]);
2390
2950
  }
2391
2951
 
2392
2952
  function triggerRedrawOverlay() {
2953
+ var t = options.interaction.redrawOverlayInterval;
2954
+ if (t == -1) { // skip event queue
2955
+ drawOverlay();
2956
+ return;
2957
+ }
2958
+
2393
2959
  if (!redrawTimeout)
2394
- redrawTimeout = setTimeout(drawOverlay, 30);
2960
+ redrawTimeout = setTimeout(drawOverlay, t);
2395
2961
  }
2396
2962
 
2397
2963
  function drawOverlay() {
@@ -2399,9 +2965,9 @@
2399
2965
 
2400
2966
  // draw highlights
2401
2967
  octx.save();
2402
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
2968
+ overlay.clear();
2403
2969
  octx.translate(plotOffset.left, plotOffset.top);
2404
-
2970
+
2405
2971
  var i, hi;
2406
2972
  for (i = 0; i < highlights.length; ++i) {
2407
2973
  hi = highlights[i];
@@ -2412,10 +2978,10 @@
2412
2978
  drawPointHighlight(hi.series, hi.point);
2413
2979
  }
2414
2980
  octx.restore();
2415
-
2981
+
2416
2982
  executeHooks(hooks.drawOverlay, [octx]);
2417
2983
  }
2418
-
2984
+
2419
2985
  function highlight(s, point, auto) {
2420
2986
  if (typeof s == "number")
2421
2987
  s = series[s];
@@ -2434,18 +3000,21 @@
2434
3000
  else if (!auto)
2435
3001
  highlights[i].auto = false;
2436
3002
  }
2437
-
3003
+
2438
3004
  function unhighlight(s, point) {
2439
3005
  if (s == null && point == null) {
2440
3006
  highlights = [];
2441
3007
  triggerRedrawOverlay();
3008
+ return;
2442
3009
  }
2443
-
3010
+
2444
3011
  if (typeof s == "number")
2445
3012
  s = series[s];
2446
3013
 
2447
- if (typeof point == "number")
2448
- point = s.data[point];
3014
+ if (typeof point == "number") {
3015
+ var ps = s.datapoints.pointsize;
3016
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
3017
+ }
2449
3018
 
2450
3019
  var i = indexOfHighlight(s, point);
2451
3020
  if (i != -1) {
@@ -2454,7 +3023,7 @@
2454
3023
  triggerRedrawOverlay();
2455
3024
  }
2456
3025
  }
2457
-
3026
+
2458
3027
  function indexOfHighlight(s, p) {
2459
3028
  for (var i = 0; i < highlights.length; ++i) {
2460
3029
  var h = highlights[i];
@@ -2464,21 +3033,22 @@
2464
3033
  }
2465
3034
  return -1;
2466
3035
  }
2467
-
3036
+
2468
3037
  function drawPointHighlight(series, point) {
2469
3038
  var x = point[0], y = point[1],
2470
- axisx = series.xaxis, axisy = series.yaxis;
2471
-
3039
+ axisx = series.xaxis, axisy = series.yaxis,
3040
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
3041
+
2472
3042
  if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2473
3043
  return;
2474
-
3044
+
2475
3045
  var pointRadius = series.points.radius + series.points.lineWidth / 2;
2476
3046
  octx.lineWidth = pointRadius;
2477
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2478
- var radius = 1.5 * pointRadius,
2479
- x = axisx.p2c(x),
2480
- y = axisy.p2c(y);
2481
-
3047
+ octx.strokeStyle = highlightColor;
3048
+ var radius = 1.5 * pointRadius;
3049
+ x = axisx.p2c(x);
3050
+ y = axisy.p2c(y);
3051
+
2482
3052
  octx.beginPath();
2483
3053
  if (series.points.symbol == "circle")
2484
3054
  octx.arc(x, y, radius, 0, 2 * Math.PI, false);
@@ -2489,12 +3059,26 @@
2489
3059
  }
2490
3060
 
2491
3061
  function drawBarHighlight(series, point) {
3062
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
3063
+ fillStyle = highlightColor,
3064
+ barLeft;
3065
+
3066
+ switch (series.bars.align) {
3067
+ case "left":
3068
+ barLeft = 0;
3069
+ break;
3070
+ case "right":
3071
+ barLeft = -series.bars.barWidth;
3072
+ break;
3073
+ default:
3074
+ barLeft = -series.bars.barWidth / 2;
3075
+ }
3076
+
2492
3077
  octx.lineWidth = series.bars.lineWidth;
2493
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2494
- var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2495
- var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
3078
+ octx.strokeStyle = highlightColor;
3079
+
2496
3080
  drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2497
- 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
3081
+ function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
2498
3082
  }
2499
3083
 
2500
3084
  function getColorOrGradient(spec, bottom, top, defaultColor) {
@@ -2505,25 +3089,27 @@
2505
3089
  // supports a simple vertical gradient properly, so that's
2506
3090
  // what we support too
2507
3091
  var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2508
-
3092
+
2509
3093
  for (var i = 0, l = spec.colors.length; i < l; ++i) {
2510
3094
  var c = spec.colors[i];
2511
3095
  if (typeof c != "string") {
2512
3096
  var co = $.color.parse(defaultColor);
2513
3097
  if (c.brightness != null)
2514
- co = co.scale('rgb', c.brightness)
3098
+ co = co.scale('rgb', c.brightness);
2515
3099
  if (c.opacity != null)
2516
3100
  co.a *= c.opacity;
2517
3101
  c = co.toString();
2518
3102
  }
2519
3103
  gradient.addColorStop(i / (l - 1), c);
2520
3104
  }
2521
-
3105
+
2522
3106
  return gradient;
2523
3107
  }
2524
3108
  }
2525
3109
  }
2526
3110
 
3111
+ // Add the plot function to the top level of the jQuery object
3112
+
2527
3113
  $.plot = function(placeholder, data, options) {
2528
3114
  //var t0 = new Date();
2529
3115
  var plot = new Plot($(placeholder), data, options, $.plot.plugins);
@@ -2531,69 +3117,21 @@
2531
3117
  return plot;
2532
3118
  };
2533
3119
 
2534
- $.plot.version = "0.7";
2535
-
3120
+ $.plot.version = "0.8.2";
3121
+
2536
3122
  $.plot.plugins = [];
2537
3123
 
2538
- // returns a string with the date d formatted according to fmt
2539
- $.plot.formatDate = function(d, fmt, monthNames) {
2540
- var leftPad = function(n) {
2541
- n = "" + n;
2542
- return n.length == 1 ? "0" + n : n;
2543
- };
2544
-
2545
- var r = [];
2546
- var escape = false, padNext = false;
2547
- var hours = d.getUTCHours();
2548
- var isAM = hours < 12;
2549
- if (monthNames == null)
2550
- monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2551
-
2552
- if (fmt.search(/%p|%P/) != -1) {
2553
- if (hours > 12) {
2554
- hours = hours - 12;
2555
- } else if (hours == 0) {
2556
- hours = 12;
2557
- }
2558
- }
2559
- for (var i = 0; i < fmt.length; ++i) {
2560
- var c = fmt.charAt(i);
2561
-
2562
- if (escape) {
2563
- switch (c) {
2564
- case 'h': c = "" + hours; break;
2565
- case 'H': c = leftPad(hours); break;
2566
- case 'M': c = leftPad(d.getUTCMinutes()); break;
2567
- case 'S': c = leftPad(d.getUTCSeconds()); break;
2568
- case 'd': c = "" + d.getUTCDate(); break;
2569
- case 'm': c = "" + (d.getUTCMonth() + 1); break;
2570
- case 'y': c = "" + d.getUTCFullYear(); break;
2571
- case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2572
- case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2573
- case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2574
- case '0': c = ""; padNext = true; break;
2575
- }
2576
- if (c && padNext) {
2577
- c = leftPad(c);
2578
- padNext = false;
2579
- }
2580
- r.push(c);
2581
- if (!padNext)
2582
- escape = false;
2583
- }
2584
- else {
2585
- if (c == "%")
2586
- escape = true;
2587
- else
2588
- r.push(c);
2589
- }
2590
- }
2591
- return r.join("");
3124
+ // Also add the plot function as a chainable property
3125
+
3126
+ $.fn.plot = function(data, options) {
3127
+ return this.each(function() {
3128
+ $.plot(this, data, options);
3129
+ });
2592
3130
  };
2593
-
3131
+
2594
3132
  // round to nearby lower multiple of base
2595
3133
  function floorInBase(n, base) {
2596
3134
  return base * Math.floor(n / base);
2597
3135
  }
2598
-
3136
+
2599
3137
  })(jQuery);