flotr-0.2.1~r301/0000755000175000017500000000000011640001743012434 5ustar segresegreflotr-0.2.1~r301/flotr-min.js0000644000175000017500000025041211640000654014705 0ustar segresegrevar Flotr={version:"0.2.0-alpha",revision:("$Revision: 301 $".match(/(\d+)/)||[null,null])[1],author:["Bas Wenneker","Fabien Ménager"],website:"http://www.solutoire.com",isIphone:/iphone/i.test(navigator.userAgent),isIE9:document.documentMode==9,graphTypes:{},plugins:{},addType:function(a,b){Flotr.graphTypes[a]=b;Flotr.defaultOptions[a]=b.options||{};Flotr.defaultOptions.defaultType=Flotr.defaultOptions.defaultType||a},addPlugin:function(a,b){Flotr.plugins[a]=b;Flotr.defaultOptions[a]=b.options||{}},draw:function(b,c,a,d){d=d||Flotr.Graph;return new d(b,c,a)},getSeries:function(a){return a.collect(function(c){c=(c.data)?Object.clone(c):{data:c};for(var b=c.data.length-1;b>-1;--b){c.data[b][1]=(c.data[b][1]===null?null:parseFloat(c.data[b][1]))}return c})},merge:function(e,c){var d,b,a=c||{};for(d in e){b=e[d];a[d]=(b&&typeof(b)==="object"&&!(b.constructor===Array||b.constructor===RegExp)&&!Object.isElement(b))?Flotr.merge(b,c[d]):a[d]=b}return a},clone:function(b){var c,a,d={};for(c in b){a=b[c];d[c]=(a&&typeof(a)==="object"&&!(a.constructor===Array||a.constructor===RegExp)&&!Object.isElement(a))?Flotr.clone(a):a}return d},getTickSize:function(e,d,a,b){var h=(a-d)/e,g=Flotr.getMagnitude(h),f=10,c=h/g;if(c<1.5){f=1}else{if(c<2.25){f=2}else{if(c<3){f=((b==0)?2:2.5)}else{if(c<7.5){f=5}}}}return f*g},defaultTickFormatter:function(a){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(e,a,d){var c=["Y","Z","E","P","T","G","M","k",""],f=["y","z","a","f","p","n","µ","m",""],b=c.length;d=d||1000;a=Math.pow(10,a||2);if(e==0){return 0}if(e>1){while(b--&&(e>=d)){e/=d}}else{c=f;b=c.length;while(b--&&(e<1)){e*=d}}return(Math.round(e*a)/a)+c[b]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+0.5},toRad:function(a){return -a*(Math.PI/180)},floorInBase:function(b,a){return a*Math.floor(b/a)},drawText:function(b,d,a,e,c){if(!b.fillText||Flotr.isIphone){b.drawText(d,a,e,c);return}c=Object.extend({size:Flotr.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},c);b.save();b.translate(a,e);b.rotate(c.angle);b.fillStyle=c.color;b.font=(c.weight>1?"bold ":"")+(c.size*1.3)+"px sans-serif";b.textAlign=c.textAlign;b.textBaseline=c.textBaseline;b.fillText(d,0,0);b.restore()},measureText:function(a,d,c){if(!a.fillText||Flotr.isIphone){return{width:a.measure(d,c)}}c=Object.extend({size:Flotr.defaultOptions.fontSize,weight:1,angle:0},c);a.save();a.rotate(c.angle);a.font=(c.weight>1?"bold ":"")+(c.size*1.3)+"px sans-serif";var b=a.measureText(d);a.restore();return b},getBestTextAlign:function(b,a){a=a||{textAlign:"center",textBaseline:"middle"};b+=Flotr.getTextAngleFromAlign(a);if(Math.abs(Math.cos(b))>0.01){a.textAlign=(Math.cos(b)>0?"right":"left")}if(Math.abs(Math.sin(b))>0.01){a.textBaseline=(Math.sin(b)>0?"top":"bottom")}return a},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return Flotr.alignTable[a.textAlign+" "+a.textBaseline]||0}};Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:true,fontSize:7.5,resolution:1,legend:{show:true,noColumns:1,labelFormatter:function(a){return a},labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{ticks:null,minorTicks:null,showLabels:true,showMinorLabels:false,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:false,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:true},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:true,showMinorLabels:false,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:false,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:true},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:0.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:true,minorVerticalLines:null,horizontalLines:true,minorHorizontalLines:null,outlineWidth:2,circular:false},selection:{mode:null,color:"#B6D9FF",fps:20},crosshair:{mode:null,color:"#FF0000",hideCursor:true},mouse:{track:false,trackAll:false,position:"se",relative:false,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:true,radius:3,fillColor:null,fillOpacity:0.4}};Flotr.Graph=Class.create({initialize:function(c,d,a){try{this.el=$(c);if(!this.el){throw"The target container doesn't exist"}if(!this.el.clientWidth){throw"The target container must be visible"}this.registerPlugins();this.el.fire("flotr:beforeinit",[this]);this.el.graph=this;this.data=d;this.lastMousePos={pageX:null,pageY:null};this.selection={first:{x:-1,y:-1},second:{x:-1,y:-1}};this.plotOffset={left:0,right:0,top:0,bottom:0};this.prevSelection=null;this.selectionInterval=null;this.ignoreClick=false;this.prevHit=null;this.series=Flotr.getSeries(d);this.setOptions(a);var b,g;for(b in Flotr.graphTypes){this[b]=Object.clone(Flotr.graphTypes[b]);for(g in this[b]){if(Object.isFunction(this[b][g])){this[b][g]=this[b][g].bind(this)}}}this.constructCanvas();this.el.fire("flotr:afterconstruct",[this]);this.initEvents();this.findDataRanges();this.calculateTicks(this.axes.x);this.calculateTicks(this.axes.x2);this.calculateTicks(this.axes.y);this.calculateTicks(this.axes.y2);this.calculateSpacing();this.setupAxes();this.draw(function(){this.insertLegend();this.el.fire("flotr:afterinit",[this])}.bind(this))}catch(f){try{console.error(f)}catch(f){}}},setOptions:function(b){var v=Flotr.clone(Flotr.defaultOptions);v.x2axis=Object.extend(Object.clone(v.xaxis),v.x2axis);v.y2axis=Object.extend(Object.clone(v.yaxis),v.y2axis);this.options=Flotr.merge(b||{},v);this.axes={x:{options:this.options.xaxis,n:1},x2:{options:this.options.x2axis,n:2},y:{options:this.options.yaxis,n:1},y2:{options:this.options.y2axis,n:2}};if(this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"){this.options.grid.minorVerticalLines=true}if(this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"){this.options.grid.minorHorizontalLines=true}var h=[],d=[],m=this.series.length,q=this.series.length,e=this.options.colors,a=[],g=0,o,l,k,u;for(l=q-1;l>-1;--l){o=this.series[l].color;if(o){--q;if(Object.isNumber(o)){h.push(o)}else{a.push(Flotr.Color.parse(o))}}}for(l=h.length-1;l>-1;--l){q=Math.max(q,h[l]+1)}for(l=0;d.length=e.length){l=0;++g}}for(l=0,k=0;l'+g+"").select(".flotr-dummy-div")[0],f=a.getDimensions();a.remove();return f}},loadDataGrid:function(){if(this.seriesData){return this.seriesData}var a=this.series,b=[];for(i=0;i0){var d,b,e,l,g,c,m,f;for(d=0;d0&&!n[d].hide){for(e=c.length-1;e>-1;--e){l=c[e][0];if((l<=0)&&(m.options.scaling==="logarithmic")){continue}if(lm.datamax){m.datamax=l;m.used=true}for(b=1;bf.datamax){f.datamax=g;f.used=true}}}}}}this.findAxesValues();this.calculateRange(k.x,"x");if(k.x2.used){this.calculateRange(k.x2,"x")}this.calculateRange(k.y,"y");if(k.y2.used){this.calculateRange(k.y2,"y")}},extendRange:function(c,b){var e=(b==="y")?"extendYRange":"extendXRange";for(var a in Flotr.graphTypes){if(this.options[a]&&this.options[a].show){if(this[a][e]){this[a][e](c)}}else{var g=false;for(i=0;i10){c.minorTickFreq=0}else{if(a-j>5){c.minorTickFreq=2}else{c.minorTickFreq=5}}}}else{d.tickSize=Flotr.getTickSize(c.noTicks,f,h,c.tickDecimals)}d.min=f;d.max=h;this.extendRange(d,g);if(c.min==null&&c.autoscale){d.min-=d.tickSize*e;if(d.min<0&&d.datamin>=0){d.min=0}d.min=d.tickSize*Math.floor(d.min/d.tickSize)}if(c.max==null&&c.autoscale){d.max+=d.tickSize*e;if(d.max>0&&d.datamax<=0&&d.datamax!=d.datamin){d.max=0}d.max=d.tickSize*Math.ceil(d.max/d.tickSize)}if(d.min==d.max){d.max=d.min+1}},findAxesValues:function(){var b,a,c;for(b=this.series.length-1;b>-1;--b){c=this.series[b];this.findXAxesValues(c);if(c.bars.show&&c.bars.horizontal&&c.bars.stacked){this.findYAxesValues(c)}}},findXAxesValues:function(b){var a;b.xaxis.values=b.xaxis.values||{};for(a=b.data.length-1;a>-1;--a){b.xaxis.values[b.data[a][0]+""]={}}},findYAxesValues:function(b){var a;b.yaxis.values=b.yaxis.values||{};for(a=b.data.length-1;a>-1;--a){b.yaxis.values[b.data[a][1]+""]={}}},calculateTicks:function(e){var u=e.options,A,m;e.ticks=[];e.minorTicks=[];if(u.ticks){var E=u.ticks,b=u.minorTicks||[],n,l;if(Object.isFunction(E)){E=E({min:e.min,max:e.max})}if(Object.isFunction(b)){b=b({min:e.min,max:e.max})}for(A=0;A1)?n[1]:u.tickFormatter(m)}else{m=n;l=u.tickFormatter(m)}e.ticks[A]={v:m,label:l}}for(A=0;A1)?n[1]:u.tickFormatter(m)}else{m=n;l=u.tickFormatter(m)}e.minorTicks[A]={v:m,label:l}}}else{if(u.mode=="time"){var G=Flotr.Date.timeUnits,k=Flotr.Date.spec,D=(e.max-e.min)/e.options.noTicks,r,q;for(A=0;A=e.tickSize){break}}r=k[A][0];q=k[A][1];if(q=="year"){r=Flotr.getTickSize(e.options.noTicks*G.year,e.min,e.max,0)}e.tickSize=r;e.tickUnit=q;e.ticks=Flotr.Date.generator(e)}else{if(u.scaling==="logarithmic"){var z=Math.log(e.max);if(u.base!=Math.E){z/=Math.log(u.base)}z=Math.ceil(z);var w=Math.log(e.min);if(u.base!=Math.E){w/=Math.log(u.base)}w=Math.ceil(w);for(A=w;Aa.length){a=j.ticks[h].label}}}j.maxLabel=this.getTextDimensions(a,{size:t.fontSize,angle:Flotr.toRad(j.options.labelsAngle)},"font-size:smaller;","flotr-grid-label");j.titleSize=this.getTextDimensions(j.options.title,{size:t.fontSize*1.2,angle:Flotr.toRad(j.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},this);m=this.getTextDimensions(t.title,{size:t.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title");this.titleHeight=m.height;m=this.getTextDimensions(t.subtitle,{size:t.fontSize},"font-size:smaller;","flotr-subtitle");this.subtitleHeight=m.height;if(t.show){g=Math.max(g,t.points.radius+t.points.lineWidth/2)}for(f=0;f=l.max)||(m==l.min||m==l.max)&&b.grid.outlineWidth!=0){continue}n.moveTo(Math.floor(l.d2p(m))+n.lineWidth/2,0);n.lineTo(Math.floor(l.d2p(m))+n.lineWidth/2,this.plotHeight)}}if(b.grid.minorVerticalLines){l=this.axes.x;for(var f=0;f=l.max)||(m==l.min||m==l.max)&&b.grid.outlineWidth!=0){continue}n.moveTo(Math.floor(l.d2p(m))+n.lineWidth/2,0);n.lineTo(Math.floor(l.d2p(m))+n.lineWidth/2,this.plotHeight)}}if(b.grid.horizontalLines){l=this.axes.y;for(var e=0;e=l.max)||(m==l.min||m==l.max)&&b.grid.outlineWidth!=0){continue}n.moveTo(0,Math.floor(l.d2p(m))+n.lineWidth/2);n.lineTo(this.plotWidth,Math.floor(l.d2p(m))+n.lineWidth/2)}}if(b.grid.minorHorizontalLines){l=this.axes.y;for(var e=0;e=l.max)||(m==l.min||m==l.max)&&b.grid.outlineWidth!=0){continue}n.moveTo(0,Math.floor(l.d2p(m))+n.lineWidth/2);n.lineTo(this.plotWidth,Math.floor(l.d2p(m))+n.lineWidth/2)}}n.stroke()}n.restore();if(b.grid.verticalLines||b.grid.minorVerticalLines||b.grid.horizontalLines||b.grid.minorHorizontalLines){this.el.fire("flotr:aftergrid",[this.axes.x,this.axes.y,b,this])}},drawOutline:function(){var j,a=this.options,k=this.ctx;if(a.grid.outlineWidth==0){return}k.save();if(a.grid.circular){k.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2);var g=Math.min(this.plotHeight,this.plotWidth)*a.radar.radiusRatio/2,b=this.axes.x.ticks.length,f=2*(Math.PI/b),d=-Math.PI/2;k.beginPath();k.lineWidth=a.grid.outlineWidth;k.strokeStyle=a.grid.color;k.lineJoin="round";for(var e=0;e<=b;++e){k[e==0?"moveTo":"lineTo"](Math.cos(e*f+d)*g,Math.sin(e*f+d)*g)}k.stroke()}else{k.translate(this.plotOffset.left,this.plotOffset.top);var c=a.grid.outlineWidth,h=0.5-c+((c+1)%2/2);k.lineWidth=c;k.strokeStyle=a.grid.color;k.lineJoin="miter";k.strokeRect(h,h,this.plotWidth,this.plotHeight)}k.restore()},drawLabels:function(){var c=0,d,m,o,k,q,g,l,e=this.options,n=this.ctx,v=this.axes;for(o=0;othis.plotWidth){continue}r.angle=Flotr.toRad(d.options.labelsAngle);r.textAlign="center";r.textBaseline="top";r=Flotr.getBestTextAlign(r.angle,r);Flotr.drawText(n,q.label,this.plotOffset.left+g,this.plotOffset.top+this.plotHeight+e.grid.labelMargin,r)}d=v.x2;r.color=d.options.color||e.grid.color;for(o=0;othis.plotWidth){continue}r.angle=Flotr.toRad(d.options.labelsAngle);r.textAlign="center";r.textBaseline="bottom";r=Flotr.getBestTextAlign(r.angle,r);Flotr.drawText(n,q.label,this.plotOffset.left+g,this.plotOffset.top+e.grid.labelMargin,r)}d=v.y;r.color=d.options.color||e.grid.color;for(o=0;othis.plotHeight){continue}r.angle=Flotr.toRad(d.options.labelsAngle);r.textAlign="right";r.textBaseline="middle";r=Flotr.getBestTextAlign(r.angle,r);Flotr.drawText(n,q.label,this.plotOffset.left-e.grid.labelMargin,this.plotOffset.top+l,r)}d=v.y2;r.color=d.options.color||e.grid.color;for(o=0;othis.plotHeight){continue}r.angle=Flotr.toRad(d.options.labelsAngle);r.textAlign="left";r.textBaseline="middle";r=Flotr.getBestTextAlign(r.angle,r);Flotr.drawText(n,q.label,this.plotOffset.left+this.plotWidth+e.grid.labelMargin,this.plotOffset.top+l,r);n.save();n.strokeStyle=r.color;n.beginPath();n.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+d.d2p(q.v));n.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+d.d2p(q.v));n.stroke();n.restore()}}else{if(v.x.options.showLabels||v.x2.options.showLabels||v.y.options.showLabels||v.y2.options.showLabels){k=['
'];d=v.x;if(d.options.showLabels){for(o=0;othis.canvasWidth)){continue}k.push('
',q.label,"
")}}d=v.x2;if(d.options.showLabels&&d.used){for(o=0;othis.canvasWidth)){continue}k.push('
',q.label,"
")}}d=v.y;if(d.options.showLabels){for(o=0;othis.canvasHeight)){continue}k.push('
',q.label,"
")}}d=v.y2;if(d.options.showLabels&&d.used){n.save();n.strokeStyle=d.options.color||e.grid.color;n.beginPath();for(o=0;othis.canvasHeight)){continue}k.push('
',q.label,"
");n.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+d.d2p(q.v));n.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+d.d2p(q.v))}n.stroke();n.restore()}k.push("
");this.el.insert(k.join(""))}}},drawTitles:function(){var e,d=this.options,g=d.grid.labelMargin,c=this.ctx,b=this.axes;if(!d.HtmlText&&this.textEnabled){var f={size:d.fontSize,color:d.grid.color,textAlign:"center"};if(d.subtitle){Flotr.drawText(c,d.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,f)}f.weight=1.5;f.size*=1.5;if(d.title){Flotr.drawText(c,d.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,f)}f.weight=1.8;f.size*=0.8;if(b.x.options.title&&b.x.used){f.textAlign=b.x.options.titleAlign||"center";f.textBaseline="top";f.angle=Flotr.toRad(b.x.options.titleAngle);f=Flotr.getBestTextAlign(f.angle,f);Flotr.drawText(c,b.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+b.x.maxLabel.height+this.plotHeight+2*g,f)}if(b.x2.options.title&&b.x2.used){f.textAlign=b.x2.options.titleAlign||"center";f.textBaseline="bottom";f.angle=Flotr.toRad(b.x2.options.titleAngle);f=Flotr.getBestTextAlign(f.angle,f);Flotr.drawText(c,b.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-b.x2.maxLabel.height-2*g,f)}if(b.y.options.title&&b.y.used){f.textAlign=b.y.options.titleAlign||"right";f.textBaseline="middle";f.angle=Flotr.toRad(b.y.options.titleAngle);f=Flotr.getBestTextAlign(f.angle,f);Flotr.drawText(c,b.y.options.title,this.plotOffset.left-b.y.maxLabel.width-2*g,this.plotOffset.top+this.plotHeight/2,f)}if(b.y2.options.title&&b.y2.used){f.textAlign=b.y2.options.titleAlign||"left";f.textBaseline="middle";f.angle=Flotr.toRad(b.y2.options.titleAngle);f=Flotr.getBestTextAlign(f.angle,f);Flotr.drawText(c,b.y2.options.title,this.plotOffset.left+this.plotWidth+b.y2.maxLabel.width+2*g,this.plotOffset.top+this.plotHeight/2,f)}}else{e=['
'];if(d.title){e.push('
',d.title,"
")}if(d.subtitle){e.push('
',d.subtitle,"
")}e.push("
");e.push('
');if(b.x.options.title&&b.x.used){e.push('
',b.x.options.title,"
")}if(b.x2.options.title&&b.x2.used){e.push('
',b.x2.options.title,"
")}if(b.y.options.title&&b.y.used){e.push('
',b.y.options.title,"
")}if(b.y2.options.title&&b.y2.used){e.push('
',b.y2.options.title,"
")}e.push("
");this.el.insert(e.join(""))}},drawSeries:function(a){a=a||this.series;var b=false;for(type in Flotr.graphTypes){if(a[type]&&a[type].show){b=true;this[type].draw(a)}}if(!b){this[this.options.defaultType].draw(a)}},insertLegend:function(){if(!this.options.legend.show){return}var o=this.series,q=this.plotOffset,e=this.options,a=e.legend,N=[],b=false,A=this.ctx,D;var C=o.findAll(function(c){return(c.label&&!c.hide)}).length;if(C){if(!e.HtmlText&&this.textEnabled&&!$(a.container)){var F={size:e.fontSize*1.1,color:e.grid.color};var w=a.position,z=a.margin,v=a.labelBoxWidth,M=a.labelBoxHeight,E=a.labelBoxMargin,I=q.left+z,G=q.top+z;var L=0;for(D=o.length-1;D>-1;--D){if(!o[D].label||o[D].hide){continue}var j=a.labelFormatter(o[D].label);L=Math.max(L,Flotr.measureText(A,j,F).width)}var u=Math.round(v+E*3+L),f=Math.round(C*(E+M)+E);if(w.charAt(0)=="s"){G=q.top+this.plotHeight-(z+f)}if(w.charAt(1)=="e"){I=q.left+this.plotWidth-(z+u)}var B=this.processColor(e.legend.backgroundColor||"rgb(240,240,240)",{opacity:e.legend.backgroundOpacity||0.1});A.fillStyle=B;A.fillRect(I,G,u,f);A.strokeStyle=e.legend.labelBoxBorderColor;A.strokeRect(Flotr.toPixel(I),Flotr.toPixel(G),u,f);var n=I+E;var l=G+E;for(D=0;D":"");b=true}var t=o[D],j=a.labelFormatter(t.label),k=a.labelBoxWidth,h=a.labelBoxHeight,d="opacity:"+t.bars.fillOpacity+";filter:alpha(opacity="+t.bars.fillOpacity*100+");",B="background-color:"+((t.bars.show&&t.bars.fillColor&&t.bars.fill)?t.bars.fillColor:t.color)+";";N.push('','
','
','
',"
","
","",'',j,"")}if(b){N.push("")}if(N.length>0){var H=''+N.join("")+"
";if(e.legend.container!=null){$(e.legend.container).innerHTML=H}else{var g="",w=e.legend.position,z=e.legend.margin;if(w.charAt(0)=="n"){g+="top:"+(z+q.top)+"px;bottom:auto;"}else{if(w.charAt(0)=="s"){g+="bottom:"+(z+q.bottom)+"px;top:auto;"}}if(w.charAt(1)=="e"){g+="right:"+(z+q.right)+"px;left:auto;"}else{if(w.charAt(1)=="w"){g+="left:"+(z+q.left)+"px;right:auto;"}}var r=this.el.insert('
'+H+"
").select("div.flotr-legend")[0];if(e.legend.backgroundOpacity!=0){var K=e.legend.backgroundColor;if(K==null){var J=(e.grid.backgroundColor!=null)?e.grid.backgroundColor:Flotr.Color.extract(r);K=this.processColor(J,null,{opacity:1})}this.el.insert('
').select("div.flotr-legend-bg")[0].setOpacity(e.legend.backgroundOpacity)}}}}}},getEventPosition:function(a){var e=this.overlay.cumulativeOffset(),d=Event.pointer(a),c=(d.x-e.left-this.plotOffset.left),b=(d.y-e.top-this.plotOffset.top);return{x:this.axes.x.p2d(c),x2:this.axes.x2.p2d(c),y:this.axes.y.p2d(b),y2:this.axes.y2.p2d(b),relX:c,relY:b,absX:d.x,absY:d.y}},clickHandler:function(a){if(this.ignoreClick){return this.ignoreClick=false}this.el.fire("flotr:click",[this.getEventPosition(a),this])},mouseMoveHandler:function(a){var b=this.getEventPosition(a);this.lastMousePos.pageX=b.absX;this.lastMousePos.pageY=b.absY;if(this.options.crosshair.mode){this.clearCrosshair()}if(this.selectionInterval==null&&(this.options.mouse.track||this.series.any(function(c){return c.mouse&&c.mouse.track}))){this.hit(b)}if(this.options.crosshair.mode){this.drawCrosshair(b)}this.el.fire("flotr:mousemove",[a,b,this])},mouseDownHandler:function(c){if(c.isRightClick()){c.stop();var b=this.overlay;b.hide();function a(){b.show();document.stopObserving("mousemove",a)}document.observe("mousemove",a);return}if(!this.options.selection.mode||!c.isLeftClick()){return}this.setSelectionPos(this.selection.first,c);if(this.selectionInterval!=null){clearInterval(this.selectionInterval)}this.lastMousePos.pageX=null;this.selectionInterval=setInterval(this.updateSelection.bindAsEventListener(this),1000/this.options.selection.fps);this.mouseUpHandler=this.mouseUpHandler.bindAsEventListener(this);document.observe("mouseup",this.mouseUpHandler)},fireSelectEvent:function(){var b=this.axes,g=this.selection,d,c,f,e;d=b.x.p2d(g.first.x);c=b.x.p2d(g.second.x);f=b.y.p2d(g.first.y);e=b.y.p2d(g.second.y);this.el.fire("flotr:select",[{x1:Math.min(d,c),y1:Math.min(f,e),x2:Math.max(d,c),y2:Math.max(f,e),xfirst:d,xsecond:c,yfirst:f,ysecond:e},this])},mouseUpHandler:function(a){document.stopObserving("mouseup",this.mouseUpHandler);a.stop();if(this.selectionInterval!=null){clearInterval(this.selectionInterval);this.selectionInterval=null}this.setSelectionPos(this.selection.second,a);this.clearSelection();if(this.selectionIsSane()){this.drawSelection();this.fireSelectEvent();this.ignoreClick=true}},setSelectionPos:function(d,b){var a=this.options,c=this.overlay.cumulativeOffset();if(a.selection.mode.indexOf("x")==-1){d.x=(d==this.selection.first)?0:this.plotWidth}else{d.x=b.pageX-c.left-this.plotOffset.left;d.x=Math.min(Math.max(0,d.x),this.plotWidth)}if(a.selection.mode.indexOf("y")==-1){d.y=(d==this.selection.first)?0:this.plotHeight}else{d.y=b.pageY-c.top-this.plotOffset.top;d.y=Math.min(Math.max(0,d.y),this.plotHeight)}},updateSelection:function(){if(this.lastMousePos.pageX==null){return}this.setSelectionPos(this.selection.second,this.lastMousePos);this.clearSelection();if(this.selectionIsSane()){this.drawSelection()}},clearSelection:function(){if(this.prevSelection==null){return}var g=this.prevSelection,e=this.octx.lineWidth,c=this.plotOffset,a=Math.min(g.first.x,g.second.x),f=Math.min(g.first.y,g.second.y),b=Math.abs(g.second.x-g.first.x),d=Math.abs(g.second.y-g.first.y);this.octx.clearRect(a+c.left-e/2+0.5,f+c.top-e/2+0.5,b+e,d+e);this.prevSelection=null},setSelection:function(b,g){var j=this.options,a=this.axes.x,f=this.axes.y,c=f.scale,h=a.scale,e=j.selection.mode.indexOf("x")!=-1,d=j.selection.mode.indexOf("y")!=-1;this.clearSelection();this.selection.first.y=(e&&!d)?0:(f.max-b.y1)*c;this.selection.second.y=(e&&!d)?this.plotHeight:(f.max-b.y2)*c;this.selection.first.x=(d&&!e)?0:(b.x1-a.min)*h;this.selection.second.x=(d&&!e)?this.plotWidth:(b.x2-a.min)*h;this.drawSelection();if(!g){this.fireSelectEvent()}},drawSelection:function(){var c=this.prevSelection,j=this.selection,g=this.octx,k=this.options,a=this.plotOffset;if(c!=null&&j.first.x==c.first.x&&j.first.y==c.first.y&&j.second.x==c.second.x&&j.second.y==c.second.y){return}g.save();g.strokeStyle=this.processColor(k.selection.color,{opacity:0.8});g.lineWidth=1;g.lineJoin="miter";g.fillStyle=this.processColor(k.selection.color,{opacity:0.4});this.prevSelection={first:{x:j.first.x,y:j.first.y},second:{x:j.second.x,y:j.second.y}};var e=Math.min(j.first.x,j.second.x),d=Math.min(j.first.y,j.second.y),f=Math.abs(j.second.x-j.first.x),b=Math.abs(j.second.y-j.first.y);g.fillRect(e+a.left+0.5,d+a.top+0.5,f,b);g.strokeRect(e+a.left+0.5,d+a.top+0.5,f,b);g.restore()},drawCrosshair:function(f){var d=this.octx,c=this.options,b=this.plotOffset,a=b.left+f.relX+0.5,e=b.top+f.relY+0.5;if(f.relX<0||f.relY<0||f.relX>this.plotWidth||f.relY>this.plotHeight){this.el.style.cursor=null;this.el.removeClassName("flotr-crosshair");return}this.lastMousePos.relX=null;this.lastMousePos.relY=null;if(c.crosshair.hideCursor){this.el.style.cursor=Prototype.Browser.Gecko?"none":"url(blank.cur),crosshair";this.el.addClassName("flotr-crosshair")}d.save();d.strokeStyle=c.crosshair.color;d.lineWidth=1;d.beginPath();if(c.crosshair.mode.indexOf("x")!=-1){d.moveTo(a,b.top);d.lineTo(a,b.top+this.plotHeight);this.lastMousePos.relX=a}if(c.crosshair.mode.indexOf("y")!=-1){d.moveTo(b.left,e);d.lineTo(b.left+this.plotWidth,e);this.lastMousePos.relY=e}d.stroke();d.restore()},clearCrosshair:function(){if(this.lastMousePos.relX!=null){this.octx.clearRect(this.lastMousePos.relX-0.5,this.plotOffset.top,1,this.plotHeight+1)}if(this.lastMousePos.relY!=null){this.octx.clearRect(this.plotOffset.left,this.lastMousePos.relY-0.5,this.plotWidth+1,1)}},selectionIsSane:function(){return Math.abs(this.selection.second.x-this.selection.first.x)>=5&&Math.abs(this.selection.second.y-this.selection.first.y)>=5},clearHit:function(){if(!this.prevHit){return}var e=this.prevHit,f=this.plotOffset,l=e.series,g=l.bars.lineWidth,b=l.pie.lineWidth,a=e.xaxis,j=e.yaxis;if(!l.bars.show&&!l.pie.show&&!l.bubbles.show){var h=l.mouse.radius+g;this.octx.clearRect(f.left+a.d2p(e.x)-h,f.top+j.d2p(e.y)-h,h*2,h*2)}else{if(l.bars.show){var k=l.bars.barWidth;if(!l.bars.horizontal){var c=j.d2p(e.y>=0?e.y:0);if(l.bars.centered){this.octx.clearRect(a.d2p(e.x-k/2)+f.left-g,c+f.top-g,a.d2p(k+a.min)+g*2,j.d2p(e.y<0?e.y:0)-c+g*2)}else{this.octx.clearRect(a.d2p(e.x)+f.left-g,c+f.top-g,a.d2p(k+a.min)+g*2,j.d2p(e.y<0?e.y:0)-c+g*2)}}else{var d=a.d2p(e.x>=0?e.x:0);if(l.bars.centered){this.octx.clearRect(d+f.left+g,j.d2p(e.y+k/2)+f.top-g,a.d2p(e.x<0?e.x:0)-d-g*2,j.d2p(k+j.min)+g*2)}else{this.octx.clearRect(d+f.left+g,j.d2p(e.y+k)+f.top-g,a.d2p(e.x<0?e.x:0)-d-g*2,j.d2p(k+j.min)+g*2)}}}else{if(l.bubbles.show){this.bubbles.clearHit()}else{if(l.pie.show){this.pie.clearHit()}}}}},drawHit:function(f){var l=this.octx,m=f.series,b=f.xaxis,g=f.yaxis;if(m.mouse.lineColor!=null){l.save();l.lineWidth=m.points.lineWidth;l.strokeStyle=m.mouse.lineColor;l.fillStyle=this.processColor(m.mouse.fillColor||"#ffffff",{opacity:m.mouse.fillOpacity});if(!m.bars.show&&!m.pie.show&&!m.bubbles.show){l.translate(this.plotOffset.left,this.plotOffset.top);l.beginPath();l.arc(b.d2p(f.x),g.d2p(f.y),m.mouse.radius,0,2*Math.PI,true);l.fill();l.stroke();l.closePath()}else{if(m.bars.show){l.save();l.translate(this.plotOffset.left,this.plotOffset.top);l.beginPath();if(m.mouse.trackAll){l.moveTo(b.d2p(f.x),g.d2p(0));l.lineTo(b.d2p(f.x),g.d2p(f.yaxis.max))}else{var h=m.bars.barWidth,j=g.d2p(f.y),k=b.d2p(f.x);if(!m.bars.horizontal){var d=g.d2p(g.min<0?0:g.min);if(m.bars.centered){var e=b.d2p(f.x-(h/2)),c=b.d2p(f.x+(h/2));l.moveTo(e,d);l.lineTo(e,j);l.lineTo(c,j);l.lineTo(c,d)}else{var c=b.d2p(f.x+h);l.moveTo(k,d);l.lineTo(k,j);l.lineTo(c,j);l.lineTo(c,d)}}else{var e=b.d2p(b.min<0?0:b.min);if(m.bars.centered){var d=g.d2p(f.y-(h/2)),a=g.d2p(f.y+(h/2));l.moveTo(e,d);l.lineTo(k,d);l.lineTo(k,a);l.lineTo(e,a)}else{var a=g.d2p(f.y+h);l.moveTo(e,j);l.lineTo(k,j);l.lineTo(k,a);l.lineTo(e,a)}}if(m.mouse.fillColor){l.fill()}}l.stroke();l.closePath();l.restore()}else{if(m.bubbles.show){this.bubbles.drawHit(f)}else{if(m.pie.show){this.pie.drawHit(f)}}}}l.restore()}this.prevHit=f},newHit:function(d){var f=this.series,c=this.options,a,b;for(var e=f.length-1;e>-1;--e){s=f[e];if(!s.mouse.track){continue}for(var j in Flotr.graphTypes){if(!this[j].getHit){continue}var g=this[j].getHit(s,d);if(g.index!==undefined){a=s.mouse.trackDecimals;if(a==null||a<0){a=0}b=s.mouse.trackFormatter(g);this.drawTooltip(b,g.x,g.y,s.mouse);this.mouseTrack.fire("flotr:hit",[g,this])}}}},hit:function(O){var h=this.series,I=this.options,e=this.prevHit,G=this.plotOffset,ah=this.octx,ad,D,L,v,S,Q,X,J,g,f,ac,Z={dist:Number.MAX_VALUE,x:null,y:null,relX:O.relX,relY:O.relY,absX:O.absX,absY:O.absY,sAngle:null,eAngle:null,fraction:null,mouse:null,xaxis:null,yaxis:null,series:null,index:null,seriesIndex:null};if(I.mouse.trackAll){for(ac=0;acS||X.maxQ||J.maxX.max||fJ.max){continue}var P=Math.abs(S-g);if((!U.bars.show&&PQ)){var E=P;if(ES||X.maxQ||J.max0&&f>0&&fQ)))||(U.bars.horizontal&&F0&&g>0&&gS))))){var E=Math.sqrt(P*P+F*F);if(E0)?H:H+(2*Math.PI),q=(q>0)?q:q+(2*Math.PI),l=af/ae,R=Math.asin(l)%(2*Math.PI),R=(R>0)?R:R+(2*Math.PI),w=Math.asin(-l)+(Math.PI);if(ae0&&HR))||(ag<0&&Hw))||((H>q||r[ac].fraction==1)&&((ag>0&&(HR))||(ag<0&&(Hw)))))){Z.x=S;Z.y=Q;Z.sAngle=H;Z.eAngle=q,Z.mouse=U.mouse;Z.series=U;Z.allSeries=h;Z.seriesIndex=ac;Z.fraction=r[ac].fraction}}}}if(Z.series&&(Z.mouse&&Z.mouse.track&&!e||(e))){var k=this.mouseTrack,N="",U=Z.series,W=Z.mouse.position,aa=Z.mouse.margin,M="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";if(!Z.mouse.relative){if(W.charAt(0)=="n"){N+="top:"+(aa+G.top)+"px;bottom:auto;"}else{if(W.charAt(0)=="s"){N+="bottom:"+(aa+G.bottom)+"px;top:auto;"}}if(W.charAt(1)=="e"){N+="right:"+(aa+G.right)+"px;left:auto;"}else{if(W.charAt(1)=="w"){N+="left:"+(aa+G.left)+"px;right:auto;"}}}else{if(!U.bars.show&&!U.pie.show){if(W.charAt(0)=="n"){N+="bottom:"+(aa-G.top-Z.yaxis.d2p(Z.y)+this.canvasHeight)+"px;top:auto;"}else{if(W.charAt(0)=="s"){N+="top:"+(aa+G.top+Z.yaxis.d2p(Z.y))+"px;bottom:auto;"}}if(W.charAt(1)=="e"){N+="left:"+(aa+G.left+Z.xaxis.d2p(Z.x))+"px;right:auto;"}else{if(W.charAt(1)=="w"){N+="right:"+(aa-G.left-Z.xaxis.d2p(Z.x)+this.canvasWidth)+"px;left:auto;"}}}else{if(U.bars.show){N+="bottom:"+(aa-G.top-Z.yaxis.d2p(Z.y/2)+this.canvasHeight)+"px;top:auto;";N+="left:"+(aa+G.left+Z.xaxis.d2p(Z.x-I.bars.barWidth/2))+"px;right:auto;"}else{var d={x:(this.plotWidth)/2,y:(this.plotHeight)/2},V=(Math.min(this.canvasWidth,this.canvasHeight)*U.pie.sizeRatio)/2,B=Z.sAngle');k=this.mouseTrack=this.el.select(".flotr-mouse-value")[0]}else{k.style.cssText=M;this.mouseTrack=k}if(Z.x!==null&&Z.y!==null){k.show();this.clearHit();this.drawHit(Z);var K=Z.mouse.trackDecimals;if(K==null||K<0){K=0}k.innerHTML=Z.mouse.trackFormatter({x:Z.x.toFixed(K),y:Z.y.toFixed(K),series:Z.series,index:Z.index,nearest:Z,fraction:Z.fraction});k.fire("flotr:hit",[Z,this])}else{if(e){k.hide();this.clearHit()}}}else{if(this.prevHit){this.mouseTrack.hide();this.clearHit()}}},drawTooltip:function(f,h,g,j){var a=this.mouseTrack,b="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;",c=j.position,e=j.margin,d=this.plotOffset;if(!a){this.el.insert('
');a=this.mouseTrack=this.el.select(".flotr-mouse-value")[0]}if(h!==null&&g!==null){if(!j.relative){if(c.charAt(0)=="n"){b+="top:"+(e+d.top)+"px;bottom:auto;"}else{if(c.charAt(0)=="s"){b+="bottom:"+(e+d.bottom)+"px;top:auto;"}}if(c.charAt(1)=="e"){b+="right:"+(e+d.right)+"px;left:auto;"}else{if(c.charAt(1)=="w"){b+="left:"+(e+d.left)+"px;right:auto;"}}}else{if(c.charAt(0)=="n"){b+="bottom:"+(e-d.top-g+this.canvasHeight)+"px;top:auto;"}else{if(c.charAt(0)=="s"){b+="top:"+(e+d.top+g)+"px;bottom:auto;"}}if(c.charAt(1)=="e"){b+="left:"+(e+d.left+h)+"px;right:auto;"}else{if(c.charAt(1)=="w"){b+="right:"+(e-d.left-h+this.canvasWidth)+"px;left:auto;"}}}a.style.cssText=b;a.update(f).show()}else{a.hide()}},saveImage:function(d,c,a,b){var e=null;if(Prototype.Browser.IE&&!Flotr.isIE9){e=""+this.canvas.firstChild.innerHTML+"";return window.open().document.write(e)}switch(d){case"jpeg":case"jpg":e=Canvas2Image.saveAsJPEG(this.canvas,b,c,a);break;default:case"png":e=Canvas2Image.saveAsPNG(this.canvas,b,c,a);break;case"bmp":e=Canvas2Image.saveAsBMP(this.canvas,b,c,a);break}if(Object.isElement(e)&&b){this.restoreCanvas();this.canvas.hide();this.overlay.hide();this.el.insert(e.setStyle({position:"absolute"}))}},restoreCanvas:function(){this.canvas.show();this.overlay.show();this.el.select("img").invoke("remove")}});Flotr.Color=Class.create({initialize:function(h,f,d,e){this.rgba=["r","g","b","a"];var c=4;while(-1<--c){this[this.rgba[c]]=arguments[c]||((c==3)?1:0)}this.normalize()},adjust:function(d,c,e,b){var a=4;while(-1<--a){if(arguments[a]!=null){this[this.rgba[a]]+=arguments[a]}}return this.normalize()},scale:function(d,c,e,b){var a=4;while(-1<--a){if(arguments[a]!=null){this[this.rgba[a]]*=arguments[a]}}return this.normalize()},clone:function(){return new Flotr.Color(this.r,this.b,this.g,this.a)},limit:function(b,a,c){return Math.max(Math.min(b,c),a)},normalize:function(){var a=this.limit;this.r=a(parseInt(this.r),0,255);this.g=a(parseInt(this.g),0,255);this.b=a(parseInt(this.b),0,255);this.a=a(this.a,0,1);return this},distance:function(b){if(!b){return}b=new Flotr.Color.parse(b);var c=0,a=3;while(-1<--a){c+=Math.abs(this[this.rgba[a]]-b[this.rgba[a]])}return c},toString:function(){return(this.a>=1)?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}});Object.extend(Flotr.Color,{parse:function(b){if(b instanceof Flotr.Color){return b}var a,d=Flotr.Color;if((a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))){return new d(parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16))}if((a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))){return new d(parseInt(a[1]),parseInt(a[2]),parseInt(a[3]))}if((a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))){return new d(parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16))}if((a=/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(b))){return new d(parseInt(a[1]),parseInt(a[2]),parseInt(a[3]),parseFloat(a[4]))}if((a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))){return new d(parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55)}if((a=/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(b))){return new d(parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55,parseFloat(a[4]))}var c=(b+"").strip().toLowerCase();if(c=="transparent"){return new d(255,255,255,0)}return(a=d.names[c])?new d(a[0],a[1],a[2]):new d(0,0,0,0)},extract:function(b){var a;do{a=b.getStyle("background-color").toLowerCase();if(!(a==""||a=="transparent")){break}b=b.up()}while(!b.nodeName.match(/^body$/i));return new Flotr.Color(a=="rgba(0, 0, 0, 0)"?"transparent":a)},names:{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]}});Flotr.Date={format:function(j,h){if(!j){return}var g={h:j.getUTCHours().toString(),H:a(j.getUTCHours()),M:a(j.getUTCMinutes()),S:a(j.getUTCSeconds()),s:j.getUTCMilliseconds(),d:j.getUTCDate().toString(),m:(j.getUTCMonth()+1).toString(),y:j.getUTCFullYear().toString(),b:Flotr.Date.monthNames[j.getUTCMonth()]};function a(c){c+="";return c.length==1?"0"+c:c}var f=[],k,e=false;for(var b=0;b=g.second){h.setUTCMilliseconds(0)}if(b>=g.minute){h.setUTCSeconds(0)}if(b>=g.hour){h.setUTCMinutes(0)}if(b>=g.day){h.setUTCHours(0)}if(b>=g.day*4){h.setUTCDate(1)}if(b>=g.year){h.setUTCMonth(0)}var l=0,k=Number.NaN,e;do{e=k;k=h.getTime();j.push({v:k,label:Flotr.Date.formatter(k,c)});if(c.tickUnit=="month"){if(c.tickSize<1){h.setUTCDate(1);var a=h.getTime();h.setUTCMonth(h.getUTCMonth()+1);var f=h.getTime();h.setTime(k+l*g.hour+(f-a)*c.tickSize);l=h.getUTCHours();h.setUTCHours(0)}else{h.setUTCMonth(h.getUTCMonth()+c.tickSize)}}else{if(c.tickUnit=="year"){h.setUTCFullYear(h.getUTCFullYear()+c.tickSize)}else{h.setTime(k+b)}}}while(k0){b.lineWidth=a/2;var e=d/2+b.lineWidth/2;b.strokeStyle="rgba(0,0,0,0.1)";this.lines.plot(c,e+a/2,false);b.strokeStyle="rgba(0,0,0,0.2)";this.lines.plot(c,e,false);if(c.lines.fill){b.fillStyle="rgba(0,0,0,0.05)";this.lines.plotArea(c,e+a/2,false)}}b.lineWidth=d;b.strokeStyle=c.color;if(c.lines.fill){b.fillStyle=this.processColor(c.lines.fillColor||c.color,{opacity:c.lines.fillOpacity});this.lines.plotArea(c,0,true)}this.lines.plot(c,0,true);b.restore()},plot:function(j,f,u){var m=this.ctx,r=j.xaxis,b=j.yaxis,v=j.data,d=v.length-1,o;if(v.length<2){return}var h=this.plotWidth,n=this.plotHeight,l=null,k=null;m.beginPath();for(o=0;o=a&&c>=n){if(a>=n){continue}t=t-(c-n-1)/(a-c)*(q-t);c=n-1}else{if(a>=c&&a>=n){if(c>=n){continue}q=t-(c-n-1)/(a-c)*(q-t);a=n-1}}if(c<=a&&c<0){if(a<0){continue}t=t-c/(a-c)*(q-t);c=0}else{if(a<=c&&a<0){if(c<0){continue}q=t-c/(a-c)*(q-t);a=0}}if(t<=q&&t<0){if(q<0){continue}c=c-t/(q-t)*(a-c);t=0}else{if(q<=t&&q<0){if(t<0){continue}a=c-t/(q-t)*(a-c);q=0}}if(t>=q&&t>=h){if(q>=h){continue}c=c+(h-t)/(q-t)*(a-c);t=h-1}else{if(q>=t&&q>=h){if(t>=h){continue}a=c+(h-t)/(q-t)*(a-c);q=h-1}}if((l!=t)||(k!=c+f)){m.moveTo(t,c+f)}l=q;k=a+f;m.lineTo(l,k)}m.stroke();m.closePath()},plotArea:function(t,j,I){var B=this.ctx,F=t.xaxis,b=t.yaxis,J=t.data,g=J.length-1,x,q=Math.min(Math.max(0,b.min),b.max),n=0,m=true,H=[],w=0,k=0,h=0;function K(M,L){if(I){H[w]=[];H[w][0]=M;H[w][1]=L;w++}}if(J.length<2){return}B.beginPath();for(var D=0;D=E&&G>F.max){if(E>F.max){continue}d=(F.max-G)/(E-G)*(a-d)+d;G=F.max}else{if(E>=G&&E>F.max){if(G>F.max){continue}a=(F.max-G)/(E-G)*(a-d)+d;E=F.max}}var c=F.d2p(G),o=F.d2p(E),f=b.d2p(b.max),e=b.d2p(b.min);if(m){B.moveTo(c,b.d2p(q+k)+j);K(c,b.d2p(q+k)+j);m=false}n=Math.max(E,n);if(d>=b.max&&a>=b.max){B.lineTo(c,f+j);B.lineTo(o,f+j);K(c,f+j);K(o,f+j);continue}else{if(d<=b.min&&a<=b.min){B.lineTo(c,e+j);B.lineTo(o,e+j);K(c,e+j);K(o,e+j);continue}}var r=G,v=E;if(d<=a&&d=b.min){G=(b.min-d)/(a-d)*(E-G)+G;d=b.min}else{if(a<=d&&a=b.min){E=(b.min-d)/(a-d)*(E-G)+G;a=b.min}}if(d>=a&&d>b.max&&a<=b.max){G=(b.max-d)/(a-d)*(E-G)+G;d=b.max}else{if(a>=d&&a>b.max&&d<=b.max){E=(b.max-d)/(a-d)*(E-G)+G;a=b.max}}var A=F.d2p(G),C=F.d2p(E),l=b.d2p(d),u=b.d2p(a);if(G!=r){x=(d<=b.min)?e:f;B.lineTo(c,x+j);B.lineTo(A,x+j);K(c,x+j);K(A,x+j)}B.lineTo(A,l+j);B.lineTo(C,u+j);K(A,l+j);K(C,u+j);if(E!=v){x=(a<=b.min)?e:f;B.lineTo(o,x+j);K(o,x+j)}n=Math.max(E,v,n)}B.lineTo(F.d2p(n),b.d2p(q)+j);K(F.d2p(n),b.d2p(q)+j);var z=F.lastStrokePath;if(t.lines.stacked){if(z){for(var D=z.length-1;D>=0;--D){B.lineTo(z[D][0],z[D][1]-j/2)}}if(I){F.lastStrokePath=H}}B.closePath();B.fill()},extendYRange:function(b){if(b.options.max==null||b.options.min==null){var h=b.max,c=b.min,m,f,e,n,d,a={},k={},g=null;for(f=0;f0){a[m]=(a[m]||0)+n.data[e][1]}else{k[m]=(k[m]||0)+n.data[e][1]}g=n}for(e in a){h=Math.max(a[e],h)}for(e in k){c=Math.min(k[e],c)}}}}b.lastSerie=g;b.max=h;b.min=c}}});Flotr.addType("bars",{options:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,fillOpacity:0.4,horizontal:false,stacked:false,centered:true},draw:function(c){var a=this.ctx,e=c.bars.barWidth,d=Math.min(c.bars.lineWidth,e);a.save();a.translate(this.plotOffset.left,this.plotOffset.top);a.lineJoin="miter";a.lineWidth=d;a.strokeStyle=c.color;a.save();this.bars.plotShadows(c,e,0,c.bars.fill);a.restore();if(c.bars.fill){var b=c.bars.fillColor||c.color;a.fillStyle=this.processColor(b,{opacity:c.bars.fillOpacity})}this.bars.plot(c,e,0,c.bars.fill);a.restore()},plot:function(m,r,e,u){var D=m.data;if(D.length<1){return}var z=m.xaxis,c=m.yaxis,t=this.ctx,w;for(w=0;w0){c.values[j].stackPos=o+l}else{c.values[j].stackNeg=f+l}}else{o=z.values[l].stackPos||0;f=z.values[l].stackNeg||0;if(j>0){z.values[l].stackPos=o+j}else{z.values[l].stackNeg=f+j}}}var k=m.bars.centered?r/2:0;if(m.bars.horizontal){if(l>0){var d=o,A=l+o}else{var A=f,d=l+f}var h=j-k,q=j+r-k}else{if(j>0){var h=o,q=j+o}else{var q=f,h=j+f}var d=l-k,A=l+r-k}if(Az.max||qc.max){continue}if(dz.max){A=z.max;if(z.lastSerie!=m&&m.bars.horizontal){n=false}}if(hc.max){q=c.max;if(c.lastSerie!=m&&!m.bars.horizontal){n=false}}var b=z.d2p(d),v=z.d2p(A),C=c.d2p(q),B=c.d2p(h);if(u){t.fillRect(b,C,v-b,B-C)}if(m.bars.lineWidth!=0&&(g||a||n)){t.beginPath();t.moveTo(b,B+e);t[g?"lineTo":"moveTo"](b,C+e);t[n?"lineTo":"moveTo"](v,C+e);t[a?"lineTo":"moveTo"](v,B+e);t.stroke();t.closePath()}}},plotShadows:function(j,m,c){var w=j.data;if(w.length<1){return}var t,h,f,u=j.xaxis,a=j.yaxis,r=this.ctx,o=this.options.shadowSize;for(t=0;t0){a.values[f].stackShadowPos=k+h}else{a.values[f].stackShadowNeg=d+h}}else{k=u.values[h].stackShadowPos||0;d=u.values[h].stackShadowNeg||0;if(f>0){u.values[h].stackShadowPos=k+f}else{u.values[h].stackShadowNeg=d+f}}}var g=j.bars.centered?m/2:0;if(j.bars.horizontal){if(h>0){var b=k,v=h+k}else{var v=d,b=h+d}var e=f-g,l=f+m-g}else{if(f>0){var e=k,l=f+k}else{var l=d,e=f+d}var b=h-g,v=h+m-g}if(vu.max||la.max){continue}if(bu.max){v=u.max}if(ea.max){l=a.max}var q=u.d2p(v)-u.d2p(b)-((u.d2p(v)+o<=this.plotWidth)?0:o);var n=a.d2p(e)-a.d2p(l)-((a.d2p(e)+o<=this.plotHeight)?0:o);r.fillStyle="rgba(0,0,0,0.05)";r.fillRect(Math.min(u.d2p(b)+o,this.plotWidth),Math.min(a.d2p(l)+o,this.plotHeight),q,n)}},extendXRange:function(c){if(c.options.max==null){var d=c.min,h=c.max,f,e,m,n,k,a={},l={},g=null;for(f=0;fh)){h=c.max+(k.centered?k.barWidth/2:k.barWidth)}if(k.stacked&&k.horizontal){for(e=0;e0){a[y]=(a[y]||0)+n.data[e][0]}else{l[y]=(l[y]||0)+n.data[e][0]}g=n}}for(e in a){h=Math.max(a[e],h)}for(e in l){d=Math.min(l[e],d)}}}}c.lastSerie=g;c.max=h;c.min=d}},extendYRange:function(c){if(c.options.max==null){var h=c.max,d=c.min,m,f,e,n,k,a={},l={},g=null;for(f=0;fh)){h=c.max+k.barWidth}if(k.stacked&&!k.horizontal){for(e=0;e0){a[m]=(a[m]||0)+n.data[e][1]}else{l[m]=(l[m]||0)+n.data[e][1]}g=n}}for(e in a){h=Math.max(a[e],h)}for(e in l){d=Math.min(l[e],d)}}}}c.lastSerie=g;c.max=h;c.min=d}}});Flotr.addType("points",{options:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#FFFFFF",fillOpacity:0.4},draw:function(c){var b=this.ctx,d=c.lines.lineWidth,a=c.shadowSize;b.save();b.translate(this.plotOffset.left,this.plotOffset.top);if(a>0){b.lineWidth=a/2;b.strokeStyle="rgba(0,0,0,0.1)";this.points.plotShadows(c,a/2+b.lineWidth/2,c.points.radius);b.strokeStyle="rgba(0,0,0,0.2)";this.points.plotShadows(c,b.lineWidth/2,c.points.radius)}b.lineWidth=c.points.lineWidth;b.strokeStyle=c.color;b.fillStyle=c.points.fillColor!=null?c.points.fillColor:c.color;this.points.plot(c,c.points.radius,c.points.fill);b.restore()},plot:function(c,e,h){var a=c.xaxis,f=c.yaxis,j=this.ctx,d,g,b=c.data;for(d=b.length-1;d>-1;--d){g=b[d][0],y=b[d][1];if(y===null||ga.max||yf.max){continue}j.beginPath();j.arc(a.d2p(g),f.d2p(y),e,0,2*Math.PI,true);if(h){j.fill()}j.stroke();j.closePath()}},plotShadows:function(d,b,f){var a=d.xaxis,g=d.yaxis,j=this.ctx,e,h,c=d.data;for(e=c.length-1;e>-1;--e){h=c[e][0],y=c[e][1];if(y===null||ha.max||yg.max){continue}j.beginPath();j.arc(a.d2p(h),g.d2p(y)+b,f,0,Math.PI,false);j.stroke();j.closePath()}},getHit:function(g,n){var e,c,h,j,l,q,m,b=g.points,f=g.data,k=g.mouse.sensibility*(b.lineWidth+b.radius),a={index:null,series:g,distance:Number.MAX_VALUE,x:null,y:null,precision:1};for(h=f.length-1;h>-1;--h){j=f[h];q=g.xaxis.d2p(j[0]);m=g.yaxis.d2p(j[1]);e=q-n.relX;c=m-n.relY;l=Math.sqrt(e*e+c*c);if(l0){a.each(function(A){if(A.startAngle==A.endAngle){return}var w=(A.startAngle+A.endAngle)/2,x=u.x+Math.cos(w)*A.options.explode+k,z=u.y+Math.sin(w)*A.options.explode+k;this.pie.plotSlice(x,z,d,A.startAngle,A.endAngle,false,n);if(g.pie.fill){m.fillStyle="rgba(0,0,0,0.1)";m.fill()}},this)}if(c.HtmlText||!this.textEnabled){j=['
']}a.each(function(K,F){if(K.startAngle==K.endAngle){return}var E=(K.startAngle+K.endAngle)/2,C=K.series.color,x=K.options.fillColor||C,G=u.x+Math.cos(E)*K.options.explode,A=u.y+Math.sin(E)*K.options.explode;this.pie.plotSlice(G,A,d,K.startAngle,K.endAngle,false,n);if(g.pie.fill){m.fillStyle=this.processColor(x,{opacity:g.pie.fillOpacity});m.fill()}m.lineWidth=e;m.strokeStyle=C;m.stroke();var J=c.pie.labelFormatter(K),w=(Math.cos(E)<0),L=(Math.sin(E)>0),B=(K.options.explode||g.pie.explode)+d+4,I=u.x+Math.cos(E)*B,H=u.y+Math.sin(E)*B;if(K.fraction&&J){if(c.HtmlText||!this.textEnabled){var D=L?(H-5):(this.plotHeight-H+5),z="position:absolute;"+(L?"top":"bottom")+":"+D+"px;";if(w){z+="right:"+(this.canvasWidth-I)+"px;text-align:right;"}else{z+="left:"+I+"px;text-align:left;"}j.push('
',J,"
")}else{o.textAlign=w?"right":"left";o.textBaseline=L?"top":"bottom";Flotr.drawText(m,J,I,H,o)}}},this);if(c.HtmlText||!this.textEnabled){j.push("
");this.el.insert(j.join(""))}m.restore();c.pie.drawn=true},plotSlice:function(b,h,a,e,d,f,g){var c=this.ctx;g=g||1;c.scale(1,g);c.beginPath();c.moveTo(b,h);c.arc(b,h,a,e,d,f);c.lineTo(b,h);c.closePath()},drawHit:function(c){var j=this.octx,k=c.series,a=c.xaxis,h=c.yaxis;j.save();j.translate(this.plotOffset.left,this.plotOffset.top);j.beginPath();if(k.mouse.trackAll){j.moveTo(a.d2p(c.x),h.d2p(0));j.lineTo(a.d2p(c.x),h.d2p(c.yaxis.max))}else{var b={x:(this.plotWidth)/2,y:(this.plotHeight)/2},f=(Math.min(this.canvasWidth,this.canvasHeight)*k.pie.sizeRatio)/2,e=c.sAnglev.max||ob.max){continue}var r=l.candles[m>n?"downFillColor":"upFillColor"];if(l.candles.fill&&!l.candles.barcharts){q.fillStyle=this.processColor(r,{opacity:l.candles.fillOpacity});q.fillRect(v.d2p(c),b.d2p(t)+e,v.d2p(z)-v.d2p(c),b.d2p(a)-b.d2p(t))}if(l.candles.lineWidth||l.candles.wickLineWidth){var k,h,f=(l.candles.wickLineWidth%2)/2;k=Math.floor(v.d2p((c+z)/2))+f;q.save();q.strokeStyle=r;q.lineWidth=l.candles.wickLineWidth;q.lineCap="butt";if(l.candles.barcharts){q.beginPath();q.moveTo(k,Math.floor(b.d2p(o)+e));q.lineTo(k,Math.floor(b.d2p(g)+e));h=Math.floor(b.d2p(m)+e)+0.5;q.moveTo(Math.floor(v.d2p(c))+f,h);q.lineTo(k,h);h=Math.floor(b.d2p(n)+e)+0.5;q.moveTo(Math.floor(v.d2p(z))+f,h);q.lineTo(k,h)}else{q.strokeRect(v.d2p(c),b.d2p(t)+e,v.d2p(z)-v.d2p(c),b.d2p(a)-b.d2p(t));q.beginPath();q.moveTo(k,Math.floor(b.d2p(t)+e));q.lineTo(k,Math.floor(b.d2p(o)+e));q.moveTo(k,Math.floor(b.d2p(a)+e));q.lineTo(k,Math.floor(b.d2p(g)+e))}q.stroke();q.closePath();q.restore()}}},plotShadows:function(h,c){var v=h.data;if(v.length<1||h.candles.barcharts){return}var r=h.xaxis,a=h.yaxis,n=this.options.shadowSize;for(var q=0;qr.max||la.max){continue}var o=r.d2p(u)-r.d2p(b)-((r.d2p(u)+n<=this.plotWidth)?0:n);var m=Math.max(0,a.d2p(e)-a.d2p(l)-((a.d2p(e)+n<=this.plotHeight)?0:n));this.ctx.fillStyle="rgba(0,0,0,0.05)";this.ctx.fillRect(Math.min(r.d2p(b)+n,this.plotWidth),Math.min(a.d2p(l)+n,this.plotWidth),o,m)}},extendXRange:function(d){if(d.options.max==null){var a=d.min,e=d.max,b,f;for(b=0;b0){k.values[l].stackMarkPos=j+n;n=j+n}else{k.values[l].stackMarkNeg=a+n;n=a+n}}else{j=b.values[n].stackMarkPos||0;a=b.values[n].stackMarkNeg||0;if(l>0){b.values[n].stackMarkPos=j+l;l=j+l}else{b.values[n].stackMarkNeg=a+l;l=a+l}}}else{if(f.markers.stackingType=="a"){var d=b.values[n].stackMark||0;b.values[n].stackMark=d+l;l=d+l}}}var h=b.d2p(n),c=k.d2p(l);m=q.labelFormatter({x:n,y:l,index:g,data:e});this.markers.plot(h,c,m,q)}o.restore()},plot:function(g,e,f,j){var h=this.ctx,c=this.getTextDimensions(f,null,null),b=2,a=g,d=e;c.width=Math.floor(c.width+b*2);c.height=Math.floor(c.height+b*2);if(j.position.indexOf("c")!=-1){a-=c.width/2+b}else{if(j.position.indexOf("l")!=-1){a-=c.width}}if(j.position.indexOf("m")!=-1){d-=c.height/2+b}else{if(j.position.indexOf("t")!=-1){d-=c.height}}a=Math.floor(a)+0.5;d=Math.floor(d)+0.5;if(j.fill){h.fillRect(a,d,c.width,c.height)}if(j.stroke){h.strokeRect(a,d,c.width,c.height)}Flotr.drawText(h,f,a+b,d+b,{textBaseline:"top",textAlign:"left",size:j.fontSize})}});Flotr.addType("radar",{options:{show:false,lineWidth:2,fill:true,fillOpacity:0.4,radiusRatio:0.9},draw:function(c){var a=this.ctx,b=this.options;a.save();a.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2);a.lineWidth=c.radar.lineWidth;a.fillStyle="rgba(0,0,0,0.05)";a.strokeStyle="rgba(0,0,0,0.05)";this.radar.plot(c,c.shadowSize/2);a.strokeStyle="rgba(0,0,0,0.1)";this.radar.plot(c,c.shadowSize/4);a.strokeStyle=c.color;a.fillStyle=this.processColor(c.color,{opacity:c.radar.fillOpacity});this.radar.plot(c);a.restore()},plot:function(e,b){var l=this.ctx,m=this.options,d=e.data,g=Math.min(this.plotHeight,this.plotWidth)*m.radar.radiusRatio/2,f=2*(Math.PI/d.length),a=-Math.PI/2;b=b||0;l.beginPath();for(var c=0;c"];var f=[''];f.push(" ");for(d=0;d'+(n[d].label||String.fromCharCode(65+d))+"");c.push("")}f.push("");for(b=0;b");for(d=0;d"+g+"")}f.push("")}c.push("");m.update(c.join("")+f.join(""));if(!Prototype.Browser.IE||Flotr.isIE9){m.select("td").each(function(j){j.observe("mouseover",function(q){j=q.element();var r=j.previousSiblings();m.select("th[scope=col]")[r.length-1].addClassName("hover");m.select("colgroup col")[r.length].addClassName("hover")}).observe("mouseout",function(){m.select("colgroup col.hover, th.hover").invoke("removeClassName","hover")})})}var h=new Element("div").addClassName("flotr-datagrid-toolbar").insert(new Element("button",{type:"button"}).addClassName("flotr-datagrid-toolbar-button").update(this.options.spreadsheet.toolbarDownload).observe("click",this.spreadsheet.downloadCSV.bindAsEventListener(this))).insert(new Element("button",{type:"button"}).addClassName("flotr-datagrid-toolbar-button").update(this.options.spreadsheet.toolbarSelectAll).observe("click",this.spreadsheet.selectAllData.bindAsEventListener(this)));var a=new Element("div",{style:"left:0px;top:0px;width:"+this.canvasWidth+"px;height:"+(this.canvasHeight-this.spreadsheet.tabsContainer.getHeight()-2)+"px;overflow:auto;"}).addClassName("flotr-datagrid-container");a.insert(h);m.wrap(a.hide());this.el.insert(a);return m},showTab:function(b){var a="canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle";switch(b){case"graph":if(this.spreadsheet.datagrid){this.spreadsheet.datagrid.up().hide()}this.el.select(a).invoke("show");this.spreadsheet.tabs.data.removeClassName("selected");this.spreadsheet.tabs.graph.addClassName("selected");break;case"data":this.spreadsheet.constructDataGrid();this.spreadsheet.datagrid.up().show();this.el.select(a).invoke("hide");this.spreadsheet.tabs.data.addClassName("selected");this.spreadsheet.tabs.graph.removeClassName("selected");break}},selectAllData:function(){if(this.spreadsheet.tabs){var b,a,e,d,c=this.spreadsheet.constructDataGrid();this.spreadsheet.showTab("data");setTimeout(function(){if((e=c.ownerDocument)&&(d=e.defaultView)&&d.getSelection&&e.createRange&&(b=window.getSelection())&&b.removeAllRanges){a=e.createRange();a.selectNode(c);b.removeAllRanges();b.addRange(a)}else{if(document.body&&document.body.createTextRange&&(a=document.body.createTextRange())){a.moveToElementText(c);a.select()}}},0);return true}else{return false}},downloadCSV:function(){var b,a="",c=this.series,j=this.options,f=this.loadDataGrid(),d=encodeURIComponent(j.spreadsheet.csvFileSeparator);if(j.spreadsheet.decimalSeparator===j.spreadsheet.csvFileSeparator){throw"The decimal separator is the same as the column separator ("+j.spreadsheet.decimalSeparator+")"}for(b=0;bt.max||mb.max){continue}if(ct.max){v=t.max;if(t.lastSerie!=j){k=false}}if(gb.max){m=b.max;if(b.lastSerie!=j){k=false}}if(q){o.beginPath();o.moveTo(t.d2p(c),b.d2p(g)+e);o.lineTo(t.d2p(c),b.d2p(m)+e);o.lineTo(t.d2p(v),b.d2p(m)+e);o.lineTo(t.d2p(v),b.d2p(g)+e);o.fill();o.closePath()}if(j.gantt.lineWidth!=0&&(f||a||k)){o.beginPath();o.moveTo(t.d2p(c),b.d2p(g)+e);o[f?"lineTo":"moveTo"](t.d2p(c),b.d2p(m)+e);o[k?"lineTo":"moveTo"](t.d2p(v),b.d2p(m)+e);o[a?"lineTo":"moveTo"](t.d2p(v),b.d2p(g)+e);o.stroke();o.closePath()}}},plotShadows:function(g,j,c){var v=g.data;if(v.length<1){return}var q,f,h,t,r=g.xaxis,a=g.yaxis,o=this.ctx,m=this.options.shadowSize;for(q=0;qr.max||ka.max){continue}if(br.max){u=r.max}if(ea.max){k=a.max}var n=r.d2p(u)-r.d2p(b)-((r.d2p(u)+m<=this.plotWidth)?0:m);var l=a.d2p(e)-a.d2p(k)-((a.d2p(e)+m<=this.plotHeight)?0:m);o.fillStyle="rgba(0,0,0,0.05)";o.fillRect(Math.min(r.d2p(b)+m,this.plotWidth),Math.min(a.d2p(k)+m,this.plotHeight),n,l)}},extendXRange:function(b){if(b.options.max==null){var c=b.min,k=b.max,e,d,m,n,h,a={},l={},f=null;for(e=0;el){l=b.max+k.barWidth}}}b.lastSerie=h;b.max=l;b.min=d;b.tickSize=Flotr.getTickSize(b.options.noTicks,d,l,b.options.tickDecimals)}}});flotr-0.2.1~r301/examples/0000755000175000017500000000000011640000652014250 5ustar segresegreflotr-0.2.1~r301/examples/advanced-titles.html0000644000175000017500000000652611640000652020216 0ustar segresegre Flotr: Advanced Titles Example

Example

This example shows how to add titles to the graph and the axes.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/ajax-financial-data.php0000644000175000017500000000213411640000652020535 0ustar segresegrefp = fopen($file, 'r'); $this->delimiter = $delimiter; $this->enclosure = $enclosure; } function __destruct(){ fclose($this->fp); } function getFlotrData($lines_count = 1000000){ $data = array(); $ticks = array(); $this->nextLine(); $x = 0; while(($line = $this->nextLine()) && $lines_count--){ $d = array($x); $i = 1; while ($i < 5 && $value = $line[$i++]) { $d[] = floatval($value); } $data[] = $d; $ticks[] = array($x, $line[0]); $x++; } return array('ticks' => $ticks, 'data' => $data); } function reset(){ rewind($this->fp); } function nextLine(){ return fgetcsv($this->fp, null, $this->delimiter, $this->enclosure); } } $parser = new CSVParser('http://ichart.finance.yahoo.com/table.csv?s=AAPL&a=00&b=1&c=1999&d=11&e=31&f=2020&g=m&ignore=.csv'); echo json_encode($parser->getFlotrData(30)); ?>flotr-0.2.1~r301/examples/basic-axis.html0000644000175000017500000000675311640000652017174 0ustar segresegre Flotr: Basic Axis Example

Example

This graph show a 5 series. The axis are configured and the vertical grid lines are not rendered.
There are null values on the sinus series.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-bar.html0000644000175000017500000000561611640000652016771 0ustar segresegre Flotr: Basic Bar Example

Example

Random bar graph.

Note: Opera 9.2x has problems with the fill area, this should be fixed in version 9.50.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-bubble.html0000644000175000017500000000477111640000652017461 0ustar segresegre Flotr: Basic Bubbles Example

Example

Random bubble graph.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-candlesticks.html0000644000175000017500000000456011640000652020671 0ustar segresegre Flotr: Basic Candle Example

Example

This example shows how to create a candle sticks graph.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-legend.html0000644000175000017500000000652511640000652017463 0ustar segresegre Flotr: Basic Legend Example

Example

This example shows how to configure the legend. Note I also added a style tag (between the header tags) to add a 2px border to the legend container. I could also style other legend elements. The elements get the the following css classes when they're generated:

  • flotr-legend → Legend wrapper element.
  • flotr-legend-bg → Legend background element.
  • flotr-legend-color-box → Legend color box.
  • flotr-legend-label → Legend label.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-pie.html0000644000175000017500000000477511640000652017007 0ustar segresegre Flotr: Basic Pie Example

Example

This example shows the ease of using Flotr. With just one line of javascript you can draw a nice looking graph.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-radar.html0000644000175000017500000000466711640000652017323 0ustar segresegre Flotr: Basic Radar Example

Example

This example shows the radar graph.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-stacked-bars.html0000644000175000017500000000617611640000652020572 0ustar segresegre Flotr: Basic Stacked Bars Example

Example

This example shows how to configure the legend. Note I also added a style tag (between the header tags) to add a 2px border to the legend container. I could also style other legend elements. The elements get the the following css classes when they're generated:

  • flotr-legend → Legend wrapper element.
  • flotr-legend-bg → Legend background element.
  • flotr-legend-color-box → Legend color box.
  • flotr-legend-label → Legend label.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-time.html0000644000175000017500000000771011640000652017160 0ustar segresegre Flotr: Basic Time Example

Example

This example show how to simply draw time charts. You can zoom to see the different time precision levels.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic-watermark.html0000644000175000017500000000522211640000652020213 0ustar segresegre Flotr: Basic Watermark Example

Example

This example shows a basic Flotr graph with a watermark image.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/basic.html0000644000175000017500000000456011640000652016224 0ustar segresegre Flotr: Basic Example

Example

This example shows the ease of using Flotr. With just one line of javascript you can draw a nice looking graph.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/blank.cur0000644000175000017500000000011611640000652016050 0ustar segresegre8(flotr-0.2.1~r301/examples/click-event.html0000644000175000017500000000624311640000652017347 0ustar segresegre Flotr: Click Event Hook Example

Example

When you click the graph with the mouse the coordinates of the click are added to the dataset and the graph is redrawn. This is done by observing the 'flotr:click' event. Read more about the event hooks.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/color-gradients.html0000644000175000017500000000643111640000652020236 0ustar segresegre Flotr: Color Gradients Example

Example

Random bar graph with color gradient

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/data-download.html0000644000175000017500000000717611640000652017667 0ustar segresegre Flotr: Download Data Example

Example

This example shows how to download the image of a Flotr graph. This possibility isn't available with Internet Explorer though.

A graph can be downloaded or converted as PNG, JPEG or BMP.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/examples.js0000644000175000017500000000111411640000652016421 0ustar segresegre// Gets the value from a group of radio buttons function getV(nl) { var v = null; $A(nl).each(function(e) { if (e.checked) { v = e.value; return; } }); return v; } document.observe('dom:loaded', function(){ var view = $('code-view'); if (view) { var code = $$('body script')[0].innerHTML.gsub('\n\t\t\t', '\n'); if (view.outerHTML) view.outerHTML = '
' + code + '
'; else view.innerHTML = code; } $$('#wrapper h1')[0].innerHTML = $$('head title')[0].innerHTML; });flotr-0.2.1~r301/examples/extending-flotr.html0000644000175000017500000000524111640000652020251 0ustar segresegre Flotr: Extending Flotr

Flotr Structure

This example shows you how to extend Flotr. But before you begin, it's important to know how Flotr is structured. Flotr consists of two important parts. The first one is the Flotr object. This object acts like a namespace for all utility functions, just to be sure it doesn't collide with the environment.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/image-download.html0000644000175000017500000001065111640000652020030 0ustar segresegre Flotr: Download Image Example

Example

This example shows how to download the image of a Flotr graph. This possibility isn't available with Internet Explorer though.

A graph can be downloaded or converted as PNG, JPEG or BMP.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/images/0000755000175000017500000000000011640000652015515 5ustar segresegreflotr-0.2.1~r301/examples/images/butterfly.jpg0000644000175000017500000004515211640000652020246 0ustar segresegreJFIFHH-http://fohn.net/monarch-butterfly-pictures/   !'")(&"&%+0=4+-:.%&5I6:?AEEE*3KQKCP=CEB   B,&,BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB  }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzw!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz"" ?( ( ( ( ( ( ( ( ( ( xmS8@MI\/C6d\z?41Yu9#mn$9E}ֻK>0OZņU!Q(Vۜe(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@rگmta);~/[ڵvqu|<(I4OX ۺS<4,!۽+NˈzQ@Q@Q@Q@Q@Q@mLȽba&@[3}[GUn\E6LJcIu\[z\c'7sgN;UxL{{Qv.d>V{2IfY0}NJbZVu{X烙uuY7A }}hrD=nh>xV}शuzG#^ 漒xD$InTHqhXs[~fN|7 ZUկisF*H5VKY{bFWw3K9O"^*Mf MI .A?Sk=ޝ;-++kwo:rYGNU~]ME}GK`gh>Si:hy[ੁ+6 Y|CMVpd!Lg\'Ú3]Oֺ.!I*4IEPEPEPEPEPEPEPEPEPEPEPEHrIgW[t?"w?ſiZ( ,jAm? _WMDŽ,{C}QE1Q@Q@%HPps_$]Q ߯J.4: dWj4tNۏ0?qzTjWU+DG> m#w!rwpiKp|w$¼eQT|Tvg֥ƛ{#׵i7T֑>\v_,ʼĺ֨^w2⠏H~*a#hh&A/@H^+2h>ʜ* Tċ^%[ݳJyº_xƱ<08c? y XҾX r|S]UqW&%ϊ|-e >GT t:޽[c$IW>*=hr/iu Wv#o\ 3ECLkDžmf9T|`7\}Z hKhSj9?y_|G/5o5vcX%l9؞?*dh*Gy^[ T^n:jKo^bbO[T|`qךDž})iqܙ#WsbF[NsUD8Vadzzpj]p>`nޕ<[U s٘lI3& /L+kZTrXZnQxF8561FuVF[9+Q HS$9JrZeVP KZzHt\>{Ԛ߅IJٕ2_CZϨkvier͘v*#Кgc%=JuӵǨnVC>dPkzɭq]X⽫~":΋n6f۵iQEQEQEQEQEQEQEQEQA u" )x>*Y+m7v 4f<[v#CTFWy?jkiisj9 =Xm\jwZD7;\1HIu="P*EDUG`:VH#(G,ҵ˦r%QIֻ dQ@F"O:~5ŻiK ':֕Ɠ=WS,4ks>uvܟk5ߋ ht+C#tpOuyy& ś~mA'1}ׄ7gE<4)~!u~{I㢶Gu<AZ6kXы`Q9jDʌvxڒz\ʇIlU8&xfH⻒H-= 䎹ȕp ?%Ŵr]r*shryN~4 q<ɛeEs+נ=ԭ"F"\0qF~"4WW$G s]o6fczt>&{$2 bW>iV<:(((( mw8Χ%mtGXw$Uzω hs˓L~m(T(VZ F>mS^ ;4kKcyUqjzg= cY[?LOƞAwWs1Y1l@hs3~ݗr?,~;\fvfQfﲧAq<9n]>oRI*D*]ºn%=(gu;[vyJճd:1lmx @@!~_N+ᶵeK%8b>T`g\|+|96G)r0 ('$wM ]Zk:[}F52Ie`:Bkh+]f <ؔo}Wd55ΕAyj. `HSZM#Ðʱ}ݛȘyߊ#jz̰i;T'?=GV-5Y *iLv3vb;{{%Eb+6Jk]^MIIGqg#jIH{xF)QKj 1F}^eJjOZ:TWD=sGX2^j2T*/YdjH_ YD9f[O冗{!tv;hxz}fާH0\bT܊ ϏߞLH0M(rOlaC't (\Uyu(y3e9<;0_SKg>ۘح!-s9bjcvO'H w$T 6ֻɭg00@a\(;(}ܕ%㏞ n 8u9ӚOLz;+~n-㸌,=j2땫/-2w~zkwiS-Ķ1\[G^GCL6r`͉JTe8iZҬVmDLk ¸QTH{d((((((((U;{}@?=h =Vqu2C `+Wi&-<< q ێO.ZkKr:p?&3Hw`8!Uٽ*2SVis+O]T?iۢ\e|SngWW:K q*M eRņ_jq4qUYϿֵ-.ͻ^F)i=tdZt9~s'ZaBfp:Q2FGQ(5(VZK+"dOH{0/Q]VDEc璾ۛ THFclon\,*3ӢS`n#=Y?poP:_8OB6?p1@@2}>\wqvrJGJ^6iуKz7")aZ¾Е̷&3׊v41sֵZtބNfFNM;ngaAz+*BqV$T*zW?x}=xj5Koy~'T8"ckY>~P A8/j}6mt$Ww6+\$jiI)"wrF zi{8Uzu|9ak--y-vg=1ӵr8Ouc3t }Q &&eԏµW[ȵax>K[2AwAcG?fYwЙ@"(f8}ze*;sɯJmƘ$"AM"[3|)i JBgI]\lےrMmXk Z݊@U؆di:+Z'E%QEQEQEQEQEQEQEQEQLhyfcw8 =I"ĆԖM7C?,#i=}4kxBZ4PJr# }W {Ėb$D0<zWOa\ɜ^i+W^HH6+pOs] q8^Sn#;SW[*iQ%h"sQEs]K_G~!2J-!bF>|*މ<馕~[zթr奨G$5^@s]\9 u|7F911Ufps*~e-%ˑaZ5hv$c^Z)=CM-@JslҴN6Zs11+KNFJV-|JRWC;K3%/XVf6mAк:Ug*MFI}kA"x=jMN69k "4Nv{)),l89mhW ڇ@X(7<{֖9wFU0ZChɭGAx+F9: ++ԅH^,9Aوg(V=g9YtS#%f%xJ|Ól~s טdybGָj#-aK֓=";zȮsOr㡮ÜtכVI:<6[cv :*?V) Ӣ qS&*}h9eTa!%G5`[tgIMhEߚuMcP:uF"HGLc4eMwyfŬoewI'(Lgr=ze=ԓjʉ%!CQ/{cqz}#a@ö/pc$K;I4%QI|EPk:zt1`@HVV C+W 0:Ub(((((((((ZdWl=KQj6N58!]RkuocQ+qN[ܥsxW9 O[~ݫpg Kyp#&;$WXֹ(ݝl;/!N!@@V5a`Zw";Z ^ J+ބT#d*]R4&9QWU@b^MDFHxxj>z"_g7{弻drһ$E9 0*h֩ I)bk{I MSwnn9'. Lc#?LM}3͂@TzmM:kkq9ø<(f|UcX㦽J &xtj^*жJRJ_U) q^ͨ}ѣSt8G@`b/}\9v/<G#*#=ЩX:莝tv Ym.fӮLn"=_*Ub 9q[W>] rQW:}7Mh|AI ưMh Vr \!I>b':Ty}wƧ2:KHS +t4Ci|OZ4를Gx ;PjV5c0եBU)8FڭͫVK+G+o)_52$!N~¯CSR^?2։xm6p0j$R+nu8u9-ݪ:8C\xN:I׉,`~I6cӎ7ڹy,YVTvk(汳[Ɍ< U霎3U4D0!QU9ҹ+1dV!?x~ nLBT8]Ug[JlUV pQ>CJ8`?+ţ瓴=.93Q]^==> xӵ*^mzd8uI-&c&ͼ7z?*f>%wPCjtl*tyu?fc 8e>,I9 "78U8<{p6 kb2J̍^^CGJk+,KJʼg`A[^ΝA).CRvj9'g,VRZDI1k{@ եic`fiM[\qkC_nv/ԷFz5 uj\xq̤I'uiBxG<ȼuPn(8GV.?F]tm+qbh>;_(^NֶZeG鿗|s {I@=ksS]Vh8aXQK49b^[G/FwrRkM7CsvFe9d]T)~UQݙsvB7cɛ̐Ky?9`Sҡ6g'w{z ,y8c$'5}C U[mdVz0 } tDSx]ԵF2Q&l`^=]')YIV46f?x7LЃdW!7c! w0z 6Ţ(QEQEQEQEQEQEQE`đxcAnCNz~[ڍ߉sX"B`u»UA]^8WN _D>_Cާ܃yYx'Y2V_m~ӥ?ya\Z8$} |TĴg'ilDVb-ΑO͵nO@}+WN[dzgڪxN{!-ul|ؘu8+'N{?e]s#ַݞ sN$ҽ*#6/@Gf][r;u-֌q̢FmLgq{Ӯm#P,2ZќlzfzD;c0@=W=7N9VͩCL2J۰ҬKiv>VSּ8BZg{T݀$Dr y蚡c'j4@I5:$6ea9.YnI\2*AtbakNL3]᳜`(:{?̜MeJQ}C#` ɨ.R0 y=k]##*91!!NW&Y+BeFB(p1Xˆ!9`033zƜyc<ʕIsH*é\Ee\ 7k^GyQG2Ŵ6ٖLtP2Umg[r*_ GwrIX'ubsJl쮋Ai[dvfGrǟ¶tVGEZv浪+hUjZdQ@Q@Q@Q@Q@Q@Q@;TPKxp񟉦.5CSO⁥VbayH}?:;g.y2D 83&R; 5l"v7ʥ2ڒ?5M'pnp$E8~X<Pz`nGoMX$Fx֚%)rwһAA޹_ Ȓ-~[0{4= Ajs7#!YۧJ;#zZN RtVMJ[i?|:ݵm6Jȼ9kBIU~e8+hbRJ`DZO\&j-bBN7ɷn6ZI9YM9,QKb~n6ȽsSW<ɨnygWsa튶цy\n69?xV>!IBD.rH\5srSɖ@K`o)զbGCkut?l%Va1K[sz.2mكןʺ,9+WNhhbq!} $)n-,9 l$I#=9š׆t {inUYq?w0z.roqdJgVm2FlƬՏ iU ~B1g wP/u I-̫=Wɜ<,pb0x<WKuny;uMm\RMa閃Po&&l!rw;v0s7>WĠ͕<; }+t}%D ]-("Һ4#(GĸQOHQEQEQEQEQEQEQEVvAP#lKp](ѯ ѫcq53sghcV9_|6O{ =8&`dEqKv4nlJ+|aiJ;ɹ&AX1'p޵NNeSL֜fqw5ԅv9%ymTLJ6Hnkԧqۭt8ҹq8xi(oبQsW]+еd9p/on,d; Pq㑣<2*JT*gp3#sUno2 8?D^g;]0Fk2Y%?9Ȯϝ=)c&ڻ9}"F+gsԏ]F0s]X+!X*9,dT%ң(X ;Jۑ[LSVʸ#ڼXT];tI9e}+E6)wp<栻me6sYk"O#xOm Ag5G VMhvTS՜AC6'օWЭn8ĶO8?X#y$<+AXMfLgnn6Mz|#WG%lLjkR?Tm.9`9ƪ,tcW.J8|==M"]!QE.඙!Ү`5OE|- R!ϯNi2;q6(Q ޺7HU%gqM;Ttں; P{U :"+[q"@NQ@Q@Q@Q@Q@Q@Q@Q@_^CXypa3#`3_9x-Je׵&yڸ@c⮣aa3|fbO柫jvzK+ijI8{[E\g*Kgc#AHGFNZѮ[xwe2ɯ;u'v~9TWllBMuaQ9qUYhV6>^vUjވ"Re˒da3}.[Ŧ4-59ȎLd}iǍ5Z;۝Fb0ryF]?6xE'UQVօykoO-I,qKlFG#Mg$2U60kUG` c^3gs/TaY0|?Zoi8DȯyƆo>ؔ Nk@P,0d%ע2sS$l;2vXz(PtȣL*p$C%`(((((((((({X+ sg?i^NXXc Q[{{Wxs ** ԷEɤcx^RF$ETrT*nCWD9%q]/||WyG15+8߽qMmq%&({ړI7qx A+;y{yU1c[.<lN٫fyV=d5:'!Q{Cv&p1׎Jr&2kTqb@"OZk7;Jt-U2+W¾;ݗ[Il$>Rsۊ'7fΝj$77KsyeOSY()AֺĚ&[g-<6Lrr!laAf,3In&z^cgs @ 1s<0G5֚5:`:-ʵn,O5RA6'4ъ4MLUmf.cs1Xs\F܊84謣I[h58=HtXdWr8֋0mcl~P!< 9U\1Yz1P[t~5Ͷt.t%Aȟz)>&kCks@5i4b]hϘ.2}0KC6sLІtfJ#ц; 3}[G}qkulYo#w\d{=;sZn)2Ii1Vll.{-q); g=MX>8('&wVvv42+ToxkNGW1!/#d[V 77v@3RuH?8q;/dHe#V'a'Һd[;{Xt{K_(yw}hBZtgյ8Y ɞU8QZǨ4pܵݺkٸU7+0*. ,|>Wb[9m?D[sFvEoiWj2jD)ˋD(a4WPQaS(E>մ(5YZ5l"w c?ֽ")jNǕjzui,RhVPNvMl =;+M H/9m啈o\WIǀ?*1Qʂ ww5>Y*SֳtmRmwg򒊿{HVBxgk}>Xw8< HV XE= nM;Gd)WqB0;JكGXW֟"NOG8uNZ9ӡ`v:y-d ]#ö61McjO&t=LףXz= 0@stY[FY`|N tlpqQW#:JHpH(PEPEPEPEPEPIs Z(((((((((((((((((((( 0=( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?flotr-0.2.1~r301/examples/index.html0000644000175000017500000000576511640000652016262 0ustar segresegre Flotr Example Index Page

Flotr for Prototype.js Example Index

This is the Flotr for Prototype.js example index page. Here you can find links to various Flotr examples. Each example emphasizes a certain feature, but I think it's useful to read and understand all of the examples, even if they're of no use to you. Just to get a better understanding of Flotr.

If you want to play more, go the playground Flotr Playground.

Can't find what you're looking for? Check out the Flotr Documentation.

flotr-0.2.1~r301/examples/json-data.html0000644000175000017500000000742111640000652017022 0ustar segresegre Flotr: JSON Data Example

Example

This example shows you how to use JSON data with Flotr. The canvas container is hidden when the page is loaded. By pressing the button a GET request is send to json.txt. This returns a JSON string with data for three series. When the returned json is a valid object, the canvas container is shown and the graph is rendered with Flotr.draw(). Here's the requested json:

{
	series:[{
			data:[[0,1],[1,4],[2,3],[3,6],[4,4.5]],
			points:{show:true},
			lines:{show:true}
		},
		[[0,0.5],[1,0.6],[2,1.8],[3,0.9],[4,2]],
		[[0,1.5],[1,2],[2,4.5],[3,3.5],[4,5.5]]
	],
	options:{
		mouse:{track:true},
		xaxis:{noTicks:10, tickDecimals:1}
	}
}

Let me give you one advise about JSON. To make sure you receive the data in the right format, use the Firefox extension Firebug to console.log (print) the response. With Firebug you can examine the Ajax request and it's response. Also, it's worth reading Introduction to JSON on PrototypeJS.org.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/json-real-data.html0000644000175000017500000000717211640000652017746 0ustar segresegre Flotr: JSON on Real Data Example

Example

This example shows you how to use JSON data with Flotr. The canvas container is hidden when the page is loaded. By pressing the button a GET request is send to json.txt. This returns a JSON string with data for three series. When the returned json is a valid object, the canvas container is shown and the graph is rendered with Flotr.draw(). Here's the requested json:

Let me give you one advise about JSON. To make sure you receive the data in the right format, use the Firefox extension Firebug to console.log (print) the response. With Firebug you can examine the Ajax request and it's response. Also, it's worth reading Introduction to JSON on PrototypeJS.org.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/json.txt0000644000175000017500000000043111640000652015760 0ustar segresegre{ series:[{ data:[[0,1],[1,4],[2,3],[3,6],[4,4.5]], points:{show:true}, lines:{show:true} }, [[0,0.5],[1,0.6],[2,1.8],[3,0.9],[4,2]], [[0,1.5],[1,2],[2,4.5],[3,3.5],[4,5.5]] ], options:{ mouse:{track:true}, xaxis:{noTicks:10, tickDecimals:1} } }flotr-0.2.1~r301/examples/logarithmic-scale.html0000644000175000017500000000461711640000652020535 0ustar segresegre Flotr: Logarithmic Scale Example

Example

This graph shows how logarithmic scale is handled

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/mouse-drag.html0000644000175000017500000000715611640000652017212 0ustar segresegre Flotr: Mouse Drag Example

Example

This example show how to implement a dragging functionnality to Flotr.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/mouse-tracking.html0000644000175000017500000000643111640000652020072 0ustar segresegre Flotr: Mouse Track Example

Example

This example show three series, there's nothing special about them. The thing that is special, that they all have what I call mouse tracking. Flotr observes the 'flotr:mouseover' event, and shows a point on the overlay canvas whenever the mouse comes near a datapoint.

You can pass the mouse tracking options as the third argument of Flotr.draw(), or you can pass the options per series. Hereby, you can enable or disable mousetracking for certain series. As you can see, I disabled mouse tracking for the blue line (which is series d1).

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/mouse-zoom-preview.html0000644000175000017500000001136111640000652020731 0ustar segresegre Flotr: Mouse Zoom With Preview Example

Example

This example show an enhancement of the mouse-zoom example. It displays an overview of the graph. By selecting an area in the graph or in the overview, the graph is zoomed. Read more about the event hooks.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/mouse-zoom.html0000644000175000017500000000771611640000652017263 0ustar segresegre Flotr: Mouse Zoom Example

Example

This example show how to hook into events that are fired by Flotr. Note that the events are fired from the container element. Read more about the event hooks.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/negative.html0000644000175000017500000000765211640000652016752 0ustar segresegre Flotr: Negative Values Example

Example

This example shows a graph with negative values. There are three series, but the legend shows only two. This is because I didn't supply a label property in the d1 series. Read more about Flotr legends.

  • The first series is just a line through y = 0.
  • The second series contains random data presented as a scatter plot.
  • The third series draws the regression line.

Finished? Go to the example index page, play with the playground or read the Flotr Documentation Pages.

The Code

flotr-0.2.1~r301/examples/style.css0000644000175000017500000000454311640000652016130 0ustar segresegrebody{ font-family:Arial,Helvetica,sans-serif; font-size: 12px; } #wrapper{ width: 700px; margin: 50px auto; } a:link, a:visited{ color: #004892; font-weight: bold; text-decoration: none; } a:visited{ color: #1763b5; } a:focus, a:hover, a:active{ color: #3c85d4; background-color: #b6d9ff; } h1, h2, h3 { font-family:Georgia,"Times New Roman",Times,serif; font-style:italic; font-weight:normal; letter-spacing:-1px; font-size: 1.5em; color: #050404; } h2{ font-size: 1.4em; } p{ margin: 10px; } code { background: #b6d9ff; font-size: 0.9em; color: #004892; } pre{ border: 4px solid #B6D9FF; background:#d2e8ff; color: #004892; padding: 15px; margin:5px 0; } pre code{ background-color: transparent; display:block; line-height:16px; overflow-x: auto; overflow-y: hidden; white-space:pre; padding:0 5px; } #footer{ margin: 30px 0; text-align:center; } iframe { border:3px solid #B6D9FF; } .ad{ width: 740px; margin: 10px auto; } .flotr-datagrid-container { border: 1px solid #999; border-bottom: none; background: #fff; } .flotr-datagrid { border-collapse: collapse; border-spacing: 0; } .flotr-datagrid td, .flotr-datagrid th { border: 1px solid #ccc; padding: 1px 3px; min-width: 2em; } .flotr-datagrid tr:hover, .flotr-datagrid col.hover { background: #f3f3f3; } .flotr-datagrid tr:hover th, .flotr-datagrid th.hover { background: #999; color: #fff; } .flotr-datagrid th { text-align: left; background: #e3e3e3; border: 2px outset #fff; } .flotr-datagrid-toolbar { padding: 1px; border-bottom: 1px solid #ccc; background: #f9f9f9; } .flotr-datagrid td:hover { background: #ccc; } .flotr-datagrid .first-row th { text-align: center; } .flotr-canvas { margin-bottom: -3px; padding-bottom: 1px; } .flotr-tabs-group { border-top: 1px solid #999; } .flotr-tab { border: 1px solid #666; border-top: none; margin: 0 3px; padding: 1px 4px; cursor: pointer; -moz-border-radius: 0 0 4px 4px; -webkit-border-bottom-left-radius: 4px; -webkit-border-bottom-right-radius: 4px; border-radius: 0 0 4px 4px; opacity: 0.5; -moz-opacity: 0.5; } .flotr-tab.selected { background: #ddd; opacity: 1; -moz-opacity: 1; } .flotr-tab:hover { background: #ccc; }flotr-0.2.1~r301/license.txt0000644000175000017500000000206111640000652014614 0ustar segresegreCopyright (c) 2008 Bas Wenneker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.flotr-0.2.1~r301/readme.txt0000644000175000017500000000473611640000652014442 0ustar segresegreAuthor: Bas Wenneker Website: Flotr Project Page: Contact: Date: Jun 21, 2010 ============================= License ============================= Flotr is released under the MIT License (see license.txt). ============================= Flotr 0.2.1 ============================= Flotr is a javascript plotting library based on the Prototype Javascript Framework (version 1.6.1 at the moment) and inspired by Flot (written by Ole Laursen). Flotr enables you to draw appealing graphs in most modern browsers with an easy-to-learn syntax. It comes with great features like legend support, negative value support, mouse tracking, selection support, zoom support, event hooks, CSS styling support and much more. ============================= Documentation and Examples ============================= Find documentation and examples at . The example files are also included in the Flotr zip package (see the examples directory). ============================= Build Flotr from svn ============================= To build your own version from svn, do the following. Get the svn trunk url from Then check out the trunk into a local folder. You can change the flotr js file, which resides in flotr/prototype. Then, download the yui-compressor (2.3.5) from Place the contents of the zip in the root of your svn working-copy. With ANT, run build.compress. After building, a new folder 'release' has been created. Here you can find the compressed flotr js. ============================= UnitTesting Flotr with jsUnit ============================= To test Flotr using the jsUnit UnitTests. Download jsUnit from Place the contents of the zip (a folder named 'jsunit') into the trunk svn folder. Change the firefox_exe and ie7_exe properties in the ANT build.xml. You might also have to change the prototype_testsuite property to point at the tests/testRunner.html file. When everything's configured, run 'test.prototype' with ANT. The browsers will open up and start running the unit test. I use Eclipse with Aptana installed as plugin. ANT is already integrated into Eclipse. If you are looking for a way to set up Eclipse and ANT, check out the following article: flotr-0.2.1~r301/flotr.js0000644000175000017500000053770711640000652014141 0ustar segresegre/* $Id: flotr.js 301 2011-07-27 15:49:41Z fabien.menager $ */ /** * @projectDescription Flotr is a javascript plotting library based on the Prototype Javascript Framework. * @author Bas Wenneker * @license MIT License * @version 0.2.0 */ var Flotr = { version: "0.2.0-alpha", revision: ('$Revision: 301 $'.match(/(\d+)/) || [null,null])[1], author: ['Bas Wenneker', 'Fabien Ménager'], website: 'http://www.solutoire.com', isIphone: /iphone/i.test(navigator.userAgent), isIE9: document.documentMode == 9, /** * An object of the registered graph types. Use Flotr.addType(type, object) * to add your own type. */ graphTypes: {}, /** * The list of the registered plugins */ plugins: {}, /** * Can be used to add your own chart type. * @param {String} name - Type of chart, like 'pies', 'bars' etc. * @param {String} graphType - The object containing the basic drawing functions (draw, etc) */ addType: function(name, graphType){ Flotr.graphTypes[name] = graphType; Flotr.defaultOptions[name] = graphType.options || {}; Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name; }, /** * Can be used to add a plugin * @param {String} name - The name of the plugin * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...) */ addPlugin: function(name, plugin){ Flotr.plugins[name] = plugin; Flotr.defaultOptions[name] = plugin.options || {}; }, /** * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. * You could also draw graphs by directly calling Flotr.Graph(element, data, options). * @param {Element} el - element to insert the graph into * @param {Object} data - an array or object of dataseries * @param {Object} options - an object containing options * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph * @return {Object} returns a new graph object and of course draws the graph. */ draw: function(el, data, options, GraphKlass){ GraphKlass = GraphKlass || Flotr.Graph; return new GraphKlass(el, data, options); }, /** * Collects dataseries from input and parses the series into the right format. It returns an Array * of Objects each having at least the 'data' key set. * @param {Array, Object} data - Object or array of dataseries * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) */ getSeries: function(data){ return data.collect(function(serie){ serie = (serie.data) ? Object.clone(serie) : {data: serie}; for (var i = serie.data.length-1; i > -1; --i) { serie.data[i][1] = (serie.data[i][1] === null ? null : parseFloat(serie.data[i][1])); } return serie; }); }, /** * Recursively merges two objects. * @param {Object} src - source object (likely the object with the least properties) * @param {Object} dest - destination object (optional, object with the most properties) * @return {Object} recursively merged Object */ merge: function(src, dest){ var i, v, result = dest || {}; for(i in src){ v = src[i]; result[i] = (v && typeof(v) === 'object' && !(v.constructor === Array || v.constructor === RegExp) && !Object.isElement(v)) ? Flotr.merge(v, dest[i]) : result[i] = v; } return result; }, /** * Recursively clones an object. * @param {Object} object - The object to clone * @return {Object} the clone */ clone: function(object){ var i, v, clone = {}; for(i in object){ v = object[i]; clone[i] = (v && typeof(v) === 'object' && !(v.constructor === Array || v.constructor === RegExp) && !Object.isElement(v)) ? Flotr.clone(v) : v; } return clone; }, /** * Function calculates the ticksize and returns it. * @param {Integer} noTicks - number of ticks * @param {Integer} min - lower bound integer value for the current axis * @param {Integer} max - upper bound integer value for the current axis * @param {Integer} decimals - number of decimals for the ticks * @return {Integer} returns the ticksize in pixels */ getTickSize: function(noTicks, min, max, decimals){ var delta = (max - min) / noTicks, magn = Flotr.getMagnitude(delta), tickSize = 10, norm = delta / magn; // Norm is between 1.0 and 10.0. if(norm < 1.5) tickSize = 1; else if(norm < 2.25) tickSize = 2; else if(norm < 3) tickSize = ((decimals == 0) ? 2 : 2.5); else if(norm < 7.5) tickSize = 5; return tickSize * magn; }, /** * Default tick formatter. * @param {String, Integer} val - tick value integer * @return {String} formatted tick string */ defaultTickFormatter: function(val){ return val+''; }, /** * Formats the mouse tracker values. * @param {Object} obj - Track value Object {x:..,y:..} * @return {String} Formatted track string */ defaultTrackFormatter: function(obj){ return '('+obj.x+', '+obj.y+')'; }, /** * Utility function to convert file size values in bytes to kB, MB, ... * @param value {Number} - The value to convert * @param precision {Number} - The number of digits after the comma (default: 2) * @param base {Number} - The base (default: 1000) */ engineeringNotation: function(value, precision, base){ var sizes = ['Y','Z','E','P','T','G','M','k',''], fractionSizes = ['y','z','a','f','p','n','µ','m',''], total = sizes.length; base = base || 1000; precision = Math.pow(10, precision || 2); if (value == 0) return 0; if (value > 1) { while (total-- && (value >= base)) value /= base; } else { sizes = fractionSizes; total = sizes.length; while (total-- && (value < 1)) value *= base; } return (Math.round(value * precision) / precision) + sizes[total]; }, /** * Returns the magnitude of the input value. * @param {Integer, Float} x - integer or float value * @return {Integer, Float} returns the magnitude of the input value */ getMagnitude: function(x){ return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); }, toPixel: function(val){ return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); }, toRad: function(angle){ return -angle * (Math.PI/180); }, floorInBase: function(n, base) { return base * Math.floor(n / base); }, drawText: function(ctx, text, x, y, style) { if (!ctx.fillText || Flotr.isIphone) { ctx.drawText(text, x, y, style); return; } style = Object.extend({ size: Flotr.defaultOptions.fontSize, color: '#000000', textAlign: 'left', textBaseline: 'bottom', weight: 1, angle: 0 }, style); ctx.save(); ctx.translate(x, y); ctx.rotate(style.angle); ctx.fillStyle = style.color; ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; ctx.textAlign = style.textAlign; ctx.textBaseline = style.textBaseline; ctx.fillText(text, 0, 0); ctx.restore(); }, measureText: function(ctx, text, style) { if (!ctx.fillText || Flotr.isIphone) { return {width: ctx.measure(text, style)}; } style = Object.extend({ size: Flotr.defaultOptions.fontSize, weight: 1, angle: 0 }, style); ctx.save(); ctx.rotate(style.angle); ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; var metrics = ctx.measureText(text); ctx.restore(); return metrics; }, getBestTextAlign: function(angle, style) { style = style || {textAlign: 'center', textBaseline: 'middle'}; angle += Flotr.getTextAngleFromAlign(style); if (Math.abs(Math.cos(angle)) > 10e-3) style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left'); if (Math.abs(Math.sin(angle)) > 10e-3) style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom'); return style; }, alignTable: { 'right middle' : 0, 'right top' : Math.PI/4, 'center top' : Math.PI/2, 'left top' : 3*(Math.PI/4), 'left middle' : Math.PI, 'left bottom' : -3*(Math.PI/4), 'center bottom': -Math.PI/2, 'right bottom' : -Math.PI/4, 'center middle': 0 }, getTextAngleFromAlign: function(style) { return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0; } }; Flotr.defaultOptions = { colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. title: null, // => The graph's title subtitle: null, // => The graph's subtitle shadowSize: 4, // => size of the 'fake' shadow defaultType: null, // => default series type HtmlText: true, // => wether to draw the text using HTML or on the canvas fontSize: 7.5, // => canvas' text font size resolution: 1, // => resolution of the graph, to have printer-friendly graphs ! legend: { show: true, // => setting to true will show the legend, hide otherwise noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false labelFormatter: function(v){return v}, // => fn: string -> string labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes labelBoxWidth: 14, labelBoxHeight: 10, labelBoxMargin: 5, container: null, // => container (as jQuery object) to put legend in, null means default on top of graph position: 'nw', // => position of default legend container within plot margin: 5, // => distance from grid edge to default legend container within plot backgroundColor: null, // => null means auto-detect backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background }, xaxis: { ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide labelsAngle: 0, // => labels' angle, in degrees title: null, // => axis title titleAngle: 0, // => axis title's angle, in degrees noTicks: 5, // => number of ticks for automagically generated ticks minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string tickDecimals: null, // => no. of decimals, null means auto min: null, // => min. value to show, null means set automatically max: null, // => max. value to show, null means set automatically autoscale: false, // => Turns autoscaling on with true autoscaleMargin: 0, // => margin in % to add if auto-setting min/max color: null, // => color of the ticks mode: 'normal', // => can be 'time' or 'normal' timeFormat: null, scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' base: Math.E, titleAlign: 'center', margin: true // => Turn off margins with false }, x2axis: {}, yaxis: { ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide labelsAngle: 0, // => labels' angle, in degrees title: null, // => axis title titleAngle: 90, // => axis title's angle, in degrees noTicks: 5, // => number of ticks for automagically generated ticks minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string tickDecimals: null, // => no. of decimals, null means auto min: null, // => min. value to show, null means set automatically max: null, // => max. value to show, null means set automatically autoscale: false, // => Turns autoscaling on with true autoscaleMargin: 0, // => margin in % to add if auto-setting min/max color: null, // => The color of the ticks scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' base: Math.E, titleAlign: 'center', margin: true // => Turn off margins with false }, y2axis: { titleAngle: 270 }, grid: { color: '#545454', // => primary color used for outline and labels backgroundColor: null, // => null for transparent, else color backgroundImage: null, // => background image. String or object with src, left and top watermarkAlpha: 0.4, // => tickColor: '#DDDDDD', // => color used for the ticks labelMargin: 3, // => margin in pixels verticalLines: true, // => whether to show gridlines in vertical direction minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir. horizontalLines: true, // => whether to show gridlines in horizontal direction minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir. outlineWidth: 2, // => width of the grid outline/border in pixels circular: false // => if set to true, the grid will be circular, must be used when radars are drawn }, selection: { mode: null, // => one of null, 'x', 'y' or 'xy' color: '#B6D9FF', // => selection box color fps: 20 // => frames-per-second }, crosshair: { mode: null, // => one of null, 'x', 'y' or 'xy' color: '#FF0000', // => crosshair color hideCursor: true // => hide the cursor when the crosshair is shown }, mouse: { track: false, // => true to track the mouse, no tracking otherwise trackAll: false, position: 'se', // => position of the value box (default south-east) relative: false, // => next to the mouse cursor trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box margin: 5, // => margin in pixels of the valuebox lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series trackDecimals: 1, // => decimals for the track values sensibility: 2, // => the lower this number, the more precise you have to aim to show a value trackY: true, // => whether or not to track the mouse in the y axis radius: 3, // => radius of the track point fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now) fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill } }; /** * Flotr Graph class that plots a graph on creation. */ Flotr.Graph = Class.create({ /** * Flotr Graph constructor. * @param {Element} el - element to insert the graph into * @param {Object} data - an array or object of dataseries * @param {Object} options - an object containing options */ initialize: function(el, data, options){ try { this.el = $(el); if (!this.el) throw 'The target container doesn\'t exist'; if (!this.el.clientWidth) throw 'The target container must be visible'; this.registerPlugins(); this.el.fire('flotr:beforeinit', [this]); // Initialize some variables this.el.graph = this; this.data = data; this.lastMousePos = { pageX: null, pageY: null }; this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} }; this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; this.prevSelection = null; this.selectionInterval = null; this.ignoreClick = false; this.prevHit = null; this.series = Flotr.getSeries(data); this.setOptions(options); var type, p; for (type in Flotr.graphTypes) { this[type] = Object.clone(Flotr.graphTypes[type]); for (p in this[type]) { if (Object.isFunction(this[type][p])) this[type][p] = this[type][p].bind(this); } } // Create and prepare canvas. this.constructCanvas(); this.el.fire('flotr:afterconstruct', [this]); // Add event handlers for mouse tracking, clicking and selection this.initEvents(); this.findDataRanges(); this.calculateTicks(this.axes.x); this.calculateTicks(this.axes.x2); this.calculateTicks(this.axes.y); this.calculateTicks(this.axes.y2); this.calculateSpacing(); this.setupAxes(); this.draw(function() { this.insertLegend(); this.el.fire('flotr:afterinit', [this]); }.bind(this)); } catch (e) { try { console.error(e); } catch (e) {} } }, /** * Sets options and initializes some variables and color specific values, used by the constructor. * @param {Object} opts - options object */ setOptions: function(opts){ var options = Flotr.clone(Flotr.defaultOptions); options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis); options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis); this.options = Flotr.merge(opts || {}, options); // The 4 axes of the plot this.axes = { x: {options: this.options.xaxis, n: 1}, x2: {options: this.options.x2axis, n: 2}, y: {options: this.options.yaxis, n: 1}, y2: {options: this.options.y2axis, n: 2} }; if (this.options.grid.minorVerticalLines === null && this.options.xaxis.scaling === 'logarithmic') { this.options.grid.minorVerticalLines = true; } if (this.options.grid.minorHorizontalLines === null && this.options.yaxis.scaling === 'logarithmic') { this.options.grid.minorHorizontalLines = true; } // Initialize some variables used throughout this function. var assignedColors = [], colors = [], ln = this.series.length, neededColors = this.series.length, oc = this.options.colors, usedColors = [], variation = 0, c, i, j, s; // Collect user-defined colors from series. for(i = neededColors - 1; i > -1; --i){ c = this.series[i].color; if(c){ --neededColors; if(Object.isNumber(c)) assignedColors.push(c); else usedColors.push(Flotr.Color.parse(c)); } } // Calculate the number of colors that need to be generated. for(i = assignedColors.length - 1; i > -1; --i) neededColors = Math.max(neededColors, assignedColors[i] + 1); // Generate needed number of colors. for(i = 0; colors.length < neededColors;){ c = (oc.length == i) ? new Flotr.Color(100, 100, 100) : Flotr.Color.parse(oc[i]); // Make sure each serie gets a different color. var sign = variation % 2 == 1 ? -1 : 1, factor = 1 + sign * Math.ceil(variation / 2) * 0.2; c.scale(factor, factor, factor); /** * @todo if we're getting too close to something else, we should probably skip this one */ colors.push(c); if(++i >= oc.length){ i = 0; ++variation; } } // Fill the options with the generated colors. for(i = 0, j = 0; i < ln; ++i){ s = this.series[i]; // Assign the color. if(s.color == null){ s.color = colors[j++].toString(); }else if(Object.isNumber(s.color)){ s.color = colors[s.color].toString(); } // Every series needs an axis if (!s.xaxis) s.xaxis = this.axes.x; if (s.xaxis == 1) s.xaxis = this.axes.x; else if (s.xaxis == 2) s.xaxis = this.axes.x2; if (!s.yaxis) s.yaxis = this.axes.y; if (s.yaxis == 1) s.yaxis = this.axes.y; else if (s.yaxis == 2) s.yaxis = this.axes.y2; // Apply missing options to the series. for (var t in Flotr.graphTypes){ s[t] = Object.extend(Object.clone(this.options[t]), s[t]); } s.mouse = Object.extend(Object.clone(this.options.mouse), s.mouse); if(s.shadowSize == null) s.shadowSize = this.options.shadowSize; } }, setupAxes: function(){ /** * Translates data number to pixel number * @param {Number} v - data number * @return {Number} translated pixel number */ function d2p(v, o){ if (o.scaling === 'logarithmic') { v = Math.log(Math.max(v, Number.MIN_VALUE)); if (o.base !== Math.E) v /= Math.log(o.base); } return v; } /** * Translates pixel number to data number * @param {Number} v - pixel data * @return {Number} translated data number */ function p2d(v, o){ if (o.scaling === 'logarithmic') v = (o.base === Math.E) ? Math.exp(v) : Math.pow(o.base, v); return v; } var x = this.axes.x, x2 = this.axes.x2, y = this.axes.y, y2 = this.axes.y2; var pw = this.plotWidth, ph = this.plotHeight; x.scale = pw / (d2p(x.max, x.options) - d2p(x.min, x.options)); x2.scale = pw / (d2p(x2.max, x2.options) - d2p(x2.min, x2.options)); y.scale = ph / (d2p(y.max, y.options) - d2p(y.min, y.options)); y2.scale = ph / (d2p(y2.max, y2.options) - d2p(y2.min, y2.options)); if (this.options.scaling === 'logarithmic') { x.d2p = x2.d2p = function(xval){ var o = this.options; return (d2p(xval, o) - d2p(this.min, o)) * this.scale; }; x.p2d = this.axes.x2.p2d = function(xval){ var o = this.options; return p2d(xval / this.scale + d2p(this.min, o), o); }; y.d2p = y2.d2p = function(yval){ var o = this.options; return ph - (d2p(yval, o) - d2p(this.min, o)) * this.scale; }; y.p2d = y2.p2d = function(yval){ var o = this.options; return p2d((ph - yval) / this.scale + d2p(this.min, o), o); }; } else { x.d2p = x2.d2p = function(xval){ return (xval - this.min) * this.scale; }; x.p2d = this.axes.x2.p2d = function(xval){ return xval / this.scale + this.min; }; y.d2p = y2.d2p = function(yval){ return ph - (yval - this.min) * this.scale; }; y.p2d = y2.p2d = function(yval){ return (ph - yval) / this.scale + this.min; }; } }, /** * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements * are created, the elements are inserted into the container element. */ constructCanvas: function(){ var el = this.el, size, c, oc; // The old canvases are retrieved to avoid memory leaks ... this.canvas = el.select('.flotr-canvas')[0]; this.overlay = el.select('.flotr-overlay')[0]; // ... and all the child elements are removed el.descendants().invoke('remove'); // For positioning labels and overlay. el.style.position = 'relative'; el.style.cursor = el.style.cursor || 'default'; size = el.getDimensions(); this.canvasWidth = size.width; this.canvasHeight = size.height; var style = { width: size.width+'px', height: size.height+'px' }; var o = this.options; size.width *= o.resolution; size.height *= o.resolution; if(this.canvasWidth <= 0 || this.canvasHeight <= 0){ throw 'Invalid dimensions for plot, width = ' + this.canvasWidth + ', height = ' + this.canvasHeight; } // Insert main canvas. if (!this.canvas) { c = this.canvas = $(document.createElement('canvas')); // Do NOT use new Element() c.className = 'flotr-canvas'; c.style.cssText = 'position:absolute;left:0px;top:0px;'; } c = this.canvas.writeAttribute(size).show().setStyle(style); c.context_ = null; // Reset the ExCanvas context el.insert(c); // Insert overlay canvas for interactive features. if (!this.overlay) { oc = this.overlay = $(document.createElement('canvas')); // Do NOT use new Element() oc.className = 'flotr-overlay'; oc.style.cssText = 'position:absolute;left:0px;top:0px;'; } oc = this.overlay.writeAttribute(size).show().setStyle(style); oc.context_ = null; // Reset the ExCanvas context el.insert(oc); if(window.G_vmlCanvasManager){ window.G_vmlCanvasManager.initElement(c); window.G_vmlCanvasManager.initElement(oc); } this.ctx = c.getContext('2d'); this.octx = oc.getContext('2d'); if(!window.G_vmlCanvasManager){ this.ctx.scale(o.resolution, o.resolution); this.octx.scale(o.resolution, o.resolution); } // Enable text functions this.textEnabled = !!this.ctx.drawText; }, processColor: function(color, options){ if (!color) return 'rgba(0, 0, 0, 0)'; options = Object.extend({ x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx }, options); if (color instanceof Flotr.Color) return color.adjust(null, null, null, options.opacity).toString(); if (Object.isString(color)) return Flotr.Color.parse(color).scale(null, null, null, options.opacity).toString(); var grad = color.colors ? color : {colors: color}; if (!options.ctx) { if (!Object.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)'; return Flotr.Color.parse(Object.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).scale(null, null, null, options.opacity).toString(); } grad = Object.extend({start: 'top', end: 'bottom'}, grad); if (/top/i.test(grad.start)) options.x1 = 0; if (/left/i.test(grad.start)) options.y1 = 0; if (/bottom/i.test(grad.end)) options.x2 = 0; if (/right/i.test(grad.end)) options.y2 = 0; var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2); for (i = 0; i < grad.colors.length; i++) { c = grad.colors[i]; if (Object.isArray(c)) { stop = c[0]; c = c[1]; } else stop = i / (grad.colors.length-1); gradient.addColorStop(stop, Flotr.Color.parse(c).scale(null, null, null, options.opacity)); } return gradient; }, registerPlugins: function(){ var name, plugin, c; for (name in Flotr.plugins) { plugin = Flotr.plugins[name]; for (c in plugin.callbacks) { // Ensure no old handlers are still observing this element (prevent memory leaks) this.el. stopObserving(c). observe(c, plugin.callbacks[c].bindAsEventListener(this)); } this[name] = Object.clone(plugin); for (p in this[name]) { if (Object.isFunction(this[name][p])) this[name][p] = this[name][p].bind(this); } } }, /** * Calculates a text box dimensions, wether it is drawn on the canvas or inserted into the DOM * @param {String} text - The text in the box * @param {Object} canvasStyle - An object containing the style for the text if drawn on the canvas * @param {String} HtmlStyle - A CSS style for the text if inserted into the DOM * @param {Object} className - A CSS className for the text if inserted into the DOM */ getTextDimensions: function(text, canvasStyle, HtmlStyle, className) { if (!text) return {width:0, height:0}; if (!this.options.HtmlText && this.textEnabled) { var bounds = this.ctx.getTextBounds(text, canvasStyle); return { width: bounds.width+2, height: bounds.height+6 }; } else { var dummyDiv = this.el.insert('
' + text + '
').select(".flotr-dummy-div")[0], dim = dummyDiv.getDimensions(); dummyDiv.remove(); return dim; } }, /** * Builds a matrix of the data to make the correspondance between the x values and the y values : * X value => Y values from the axes * @return {Array} The data grid */ loadDataGrid: function(){ if (this.seriesData) return this.seriesData; var s = this.series, dg = []; /* The data grid is a 2 dimensions array. There is a row for each X value. * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) **/ for(i = 0; i < s.length; ++i){ s[i].data.each(function(v) { var x = v[0], y = v[1], r = dg.find(function(row) {return row[0] == x}); if (r) r[i+1] = y; else { var newRow = []; newRow[0] = x; newRow[i+1] = y; dg.push(newRow); } }); } // The data grid is sorted by x value return this.seriesData = dg.sortBy(function(v){return v[0]}); }, /** * Initializes event some handlers. */ initEvents: function () { //@todo: maybe stopObserving with only flotr functions this.overlay.stopObserving() .observe('mousedown', this.mouseDownHandler.bindAsEventListener(this)) .observe('mousemove', this.mouseMoveHandler.bindAsEventListener(this)) .observe('mouseout', this.clearHit.bindAsEventListener(this)) .observe('click', this.clickHandler.bindAsEventListener(this)); }, /** * Function determines the min and max values for the xaxis and yaxis. */ findDataRanges: function(){ var s = this.series, a = this.axes; a.x.datamin = a.x2.datamin = a.y.datamin = a.y2.datamin = Number.MAX_VALUE; a.x.datamax = a.x2.datamax = a.y.datamax = a.y2.datamax = -Number.MAX_VALUE; if(s.length > 0){ var i, j, h, x, y, data, xaxis, yaxis; // Get datamin, datamax start values for(i = 0; i < s.length; ++i) { data = s[i].data, xaxis = s[i].xaxis, yaxis = s[i].yaxis; if (data.length > 0 && !s[i].hide) { for(h = data.length - 1; h > -1; --h){ x = data[h][0]; // Logarithm is only defined for values > 0 if ((x <= 0) && (xaxis.options.scaling === 'logarithmic')) continue; if(x < xaxis.datamin) { xaxis.datamin = x; xaxis.used = true; } if(x > xaxis.datamax) { xaxis.datamax = x; xaxis.used = true; } for(j = 1; j < data[h].length; j++){ y = data[h][j]; // Logarithm is only defined for values > 0 if ((y <= 0) && (yaxis.options.scaling === 'logarithmic')) continue; if(y < yaxis.datamin) { yaxis.datamin = y; yaxis.used = true; } if(y > yaxis.datamax) { yaxis.datamax = y; yaxis.used = true; } } } } } } this.findAxesValues(); this.calculateRange(a.x, 'x'); if (a.x2.used) { this.calculateRange(a.x2, 'x'); } this.calculateRange(a.y, 'y'); if (a.y2.used) { this.calculateRange(a.y2, 'y'); } }, extendRange: function(axis, type) { var f = (type === 'y') ? 'extendYRange' : 'extendXRange'; for (var t in Flotr.graphTypes) { if(this.options[t] && this.options[t].show){ if (this[t][f]) this[t][f](axis); } else { var extend = false; for (i =0 ; i 10) o.minorTickFreq = 0; else if (maxexp - minexp > 5) o.minorTickFreq = 2; else o.minorTickFreq = 5; } } else { axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); } axis.min = min; axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled this.extendRange(axis, type);//extendRange probably changed axis.min and axis.max // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it if(o.min == null && o.autoscale){ axis.min -= axis.tickSize * margin; // Make sure we don't go below zero if all values are positive. if(axis.min < 0 && axis.datamin >= 0) axis.min = 0; axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize); } if(o.max == null && o.autoscale){ axis.max += axis.tickSize * margin; if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0; axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize); } if (axis.min == axis.max) axis.max = axis.min + 1; }, /** * Find every values of the x axes or when horizontal stacked bar chart is used also y axes */ findAxesValues: function(){ var i, j, s; for(i = this.series.length-1; i > -1 ; --i){ s = this.series[i]; this.findXAxesValues(s); if(s.bars.show && s.bars.horizontal && s.bars.stacked) this.findYAxesValues(s); } }, /** * Find every values of the x axes */ findXAxesValues: function(s){ var j; s.xaxis.values = s.xaxis.values || {}; for (j = s.data.length-1; j > -1 ; --j){ s.xaxis.values[s.data[j][0]+''] = {}; } }, /** * Find every values of the y axes */ findYAxesValues: function(s){ var j; s.yaxis.values = s.yaxis.values || {}; for (j = s.data.length-1; j > -1 ; --j){ s.yaxis.values[s.data[j][1]+''] = {}; } }, /** * Calculate axis ticks. * @param {Object} axis - The axis for what the ticks will be calculated */ calculateTicks: function(axis){ var o = axis.options, i, v; axis.ticks = []; axis.minorTicks = []; if(o.ticks){ var ticks = o.ticks, minorTicks = o.minorTicks || [], t, label; if(Object.isFunction(ticks)){ ticks = ticks({min: axis.min, max: axis.max}); } if(Object.isFunction(minorTicks)){ minorTicks = minorTicks({min: axis.min, max: axis.max}); } // Clean up the user-supplied ticks, copy them over. for(i = 0; i < ticks.length; ++i){ t = ticks[i]; if(typeof(t) === 'object'){ v = t[0]; label = (t.length > 1) ? t[1] : o.tickFormatter(v); }else{ v = t; label = o.tickFormatter(v); } axis.ticks[i] = { v: v, label: label }; } for(i = 0; i < minorTicks.length; ++i){ t = minorTicks[i]; if(typeof(t) === 'object'){ v = t[0]; label = (t.length > 1) ? t[1] : o.tickFormatter(v); } else { v = t; label = o.tickFormatter(v); } axis.minorTicks[i] = { v: v, label: label }; } } else { if (o.mode == 'time') { var tu = Flotr.Date.timeUnits, spec = Flotr.Date.spec, delta = (axis.max - axis.min) / axis.options.noTicks, size, unit; for (i = 0; i < spec.length - 1; ++i) { var d = spec[i][0] * tu[spec[i][1]]; if (delta < (d + spec[i+1][0] * tu[spec[i+1][1]]) / 2 && d >= axis.tickSize) break; } size = spec[i][0]; unit = spec[i][1]; // special-case the possibility of several years if (unit == "year") { size = Flotr.getTickSize(axis.options.noTicks*tu.year, axis.min, axis.max, 0); } axis.tickSize = size; axis.tickUnit = unit; axis.ticks = Flotr.Date.generator(axis); } else if (o.scaling === 'logarithmic') { var max = Math.log(axis.max); if (o.base != Math.E) max /= Math.log(o.base); max = Math.ceil(max); var min = Math.log(axis.min); if (o.base != Math.E) min /= Math.log(o.base); min = Math.ceil(min); for (i = min; i < max; i += axis.tickSize) { var decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); // Next decade begins here: var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize)); var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq; axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart)}); for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize) axis.minorTicks.push({v: v, label: o.tickFormatter(v)}); } // Always show the value at the would-be start of next decade (end of this decade) var decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart)}); } else { // Round to nearest multiple of tick size. var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize), decimals, minorTickSize, v2; if (o.minorTickFreq) minorTickSize = axis.tickSize / o.minorTickFreq; // Then store all possible ticks. for(i = 0; start + i * axis.tickSize <= axis.max; ++i){ v = v2 = start + i * axis.tickSize; // Round (this is always needed to fix numerical instability). decimals = o.tickDecimals; if(decimals == null) decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10); if(decimals < 0) decimals = 0; v = v.toFixed(decimals); axis.ticks.push({ v: v, label: o.tickFormatter(v) }); if (o.minorTickFreq) { for(var j = 0; j < o.minorTickFreq && (i * axis.tickSize + j * minorTickSize) < axis.max; ++j) { v = v2 + j * minorTickSize; v = v.toFixed(decimals); axis.minorTicks.push({ v: v, label: o.tickFormatter(v) }); } } } } } }, /** * Calculates axis label sizes. */ calculateSpacing: function(){ var a = this.axes, options = this.options, series = this.series, margin = options.grid.labelMargin, x = a.x, x2 = a.x2, y = a.y, y2 = a.y2, maxOutset = 2, i, j, l, dim; // Labels width and height [x, x2, y, y2].each(function(axis) { var maxLabel = ''; if (axis.options.showLabels) { for(i = 0; i < axis.ticks.length; ++i){ l = axis.ticks[i].label.length; if(l > maxLabel.length){ maxLabel = axis.ticks[i].label; } } } axis.maxLabel = this.getTextDimensions(maxLabel, {size:options.fontSize, angle: Flotr.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label'); axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: Flotr.toRad(axis.options.titleAngle)}, 'font-weight:bold;', 'flotr-axis-title'); }, this); // Title height dim = this.getTextDimensions(options.title, {size: options.fontSize*1.5}, 'font-size:1em;font-weight:bold;', 'flotr-title'); this.titleHeight = dim.height; // Subtitle height dim = this.getTextDimensions(options.subtitle, {size: options.fontSize}, 'font-size:smaller;', 'flotr-subtitle'); this.subtitleHeight = dim.height; // Grid outline line width. if(options.show){ maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2); } for(j = 0; j < options.length; ++j){ if (series[j].points.show){ maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); } } var p = this.plotOffset; if (x.options.margin === false) { p.bottom = 0; p.top = 0; } else { p.bottom += (options.grid.circular ? 0 : (x.options.showLabels ? (x.maxLabel.height + margin) : 0)) + (x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset; p.top += (options.grid.circular ? 0 : (x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + (x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset; } if (y.options.margin === false) { p.left = 0; p.right = 0; } else { p.left += (options.grid.circular ? 0 : (y.options.showLabels ? (y.maxLabel.width + margin) : 0)) + (y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset; p.right += (options.grid.circular ? 0 : (y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + (y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset; } p.top = Math.floor(p.top); // In order the outline not to be blured this.plotWidth = this.canvasWidth - p.left - p.right; this.plotHeight = this.canvasHeight - p.bottom - p.top; }, /** * Draws grid, labels, series and outline. */ draw: function(after) { var afterImageLoad = function() { this.drawGrid(); this.drawLabels(); this.drawTitles(); if(this.series.length){ this.el.fire('flotr:beforedraw', [this.series, this]); for(var i = 0; i < this.series.length; i++){ if (!this.series[i].hide) this.drawSeries(this.series[i]); } } this.drawOutline(); this.el.fire('flotr:afterdraw', [this.series, this]); after(); }.bind(this); var g = this.options.grid; if (g && g.backgroundImage) { if (Object.isString(g.backgroundImage)){ g.backgroundImage = {src: g.backgroundImage, left: 0, top: 0}; }else{ g.backgroundImage = Object.extend({left: 0, top: 0}, g.backgroundImage); } var img = new Image(); img.onload = function() { var left = this.plotOffset.left + (parseInt(g.backgroundImage.left) || 0); var top = this.plotOffset.top + (parseInt(g.backgroundImage.top) || 0); // Store the global alpha to restore it later on. var globalAlpha = this.ctx.globalAlpha; // When the watermarkAlpha is < 1 then the watermark is transparent. this.ctx.globalAlpha = (g.backgroundImage.alpha||globalAlpha); // Draw the watermark. this.ctx.drawImage(img, left, top); // Set the globalAlpha back to the alpha value before changing it to // the grid.watermarkAlpha, otherwise the graph will be transparent also. this.ctx.globalAlpha = globalAlpha; afterImageLoad(); }.bind(this); img.onabort = img.onerror = afterImageLoad; img.src = g.backgroundImage.src; } else { afterImageLoad(); } }, /** * Draws a grid for the graph. */ drawGrid: function(){ var v, o = this.options, ctx = this.ctx, a; if(o.grid.verticalLines || o.grid.minorVerticalLines || o.grid.horizontalLines || o.grid.minorHorizontalLines){ this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]); } ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = o.grid.tickColor; if (o.grid.circular) { ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2); var radius = Math.min(this.plotHeight, this.plotWidth)*o.radar.radiusRatio/2, sides = this.axes.x.ticks.length, coeff = 2*(Math.PI/sides), angle = -Math.PI/2; // Draw grid lines in vertical direction. ctx.beginPath(); if(o.grid.horizontalLines){ a = this.axes.y; for(var i = 0; i < a.ticks.length; ++i){ v = a.ticks[i].v; var ratio = v / a.max; for(var j = 0; j <= sides; ++j){ ctx[j == 0 ? 'moveTo' : 'lineTo'](Math.cos(j*coeff+angle)*radius*ratio, Math.sin(j*coeff+angle)*radius*ratio); } //ctx.moveTo(radius*ratio, 0); //ctx.arc(0, 0, radius*ratio, 0, Math.PI*2, true); } } if(o.grid.minorHorizontalLines){ a = this.axes.y; for(var i = 0; i < a.minorTicks.length; ++i){ v = a.minorTicks[i].v; var ratio = v / a.max; for(var j = 0; j <= sides; ++j){ ctx[j == 0 ? 'moveTo' : 'lineTo'](Math.cos(j*coeff+angle)*radius*ratio, Math.sin(j*coeff+angle)*radius*ratio); } //ctx.moveTo(radius*ratio, 0); //ctx.arc(0, 0, radius*ratio, 0, Math.PI*2, true); } } if(o.grid.verticalLines){ for(var i = 0; i < sides; ++i){ ctx.moveTo(0, 0); ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); } } ctx.stroke(); } else { ctx.translate(this.plotOffset.left, this.plotOffset.top); // Draw grid background, if present in options. if(o.grid.backgroundColor != null){ ctx.fillStyle = this.processColor(o.grid.backgroundColor, {x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight}); ctx.fillRect(0, 0, this.plotWidth, this.plotHeight); } // Draw grid lines in vertical direction. ctx.beginPath(); if(o.grid.verticalLines){ a = this.axes.x; for(var i = 0; i < a.ticks.length; ++i){ v = a.ticks[i].v; // Don't show lines on upper and lower bounds. if ((v <= a.min || v >= a.max) || (v == a.min || v == a.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, 0); ctx.lineTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, this.plotHeight); } } if(o.grid.minorVerticalLines){ a = this.axes.x; for(var i = 0; i < a.minorTicks.length; ++i){ v = a.minorTicks[i].v; // Don't show lines on upper and lower bounds. if ((v <= a.min || v >= a.max) || (v == a.min || v == a.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, 0); ctx.lineTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, this.plotHeight); } } // Draw grid lines in horizontal direction. if(o.grid.horizontalLines){ a = this.axes.y; for(var j = 0; j < a.ticks.length; ++j){ v = a.ticks[j].v; // Don't show lines on upper and lower bounds. if ((v <= a.min || v >= a.max) || (v == a.min || v == a.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(0, Math.floor(a.d2p(v)) + ctx.lineWidth/2); ctx.lineTo(this.plotWidth, Math.floor(a.d2p(v)) + ctx.lineWidth/2); } } if(o.grid.minorHorizontalLines){ a = this.axes.y; for(var j = 0; j < a.minorTicks.length; ++j){ v = a.minorTicks[j].v; // Don't show lines on upper and lower bounds. if ((v <= a.min || v >= a.max) || (v == a.min || v == a.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(0, Math.floor(a.d2p(v)) + ctx.lineWidth/2); ctx.lineTo(this.plotWidth, Math.floor(a.d2p(v)) + ctx.lineWidth/2); } } ctx.stroke(); } ctx.restore(); if(o.grid.verticalLines || o.grid.minorVerticalLines || o.grid.horizontalLines || o.grid.minorHorizontalLines){ this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]); } }, /** * Draws a outline for the graph. */ drawOutline: function(){ var v, o = this.options, ctx = this.ctx; if (o.grid.outlineWidth == 0) return; ctx.save(); if (o.grid.circular) { ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2); var radius = Math.min(this.plotHeight, this.plotWidth)*o.radar.radiusRatio/2, sides = this.axes.x.ticks.length, coeff = 2*(Math.PI/sides), angle = -Math.PI/2; // Draw axis/grid border. ctx.beginPath(); ctx.lineWidth = o.grid.outlineWidth; ctx.strokeStyle = o.grid.color; ctx.lineJoin = 'round'; for(var i = 0; i <= sides; ++i){ ctx[i == 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); } //ctx.arc(0, 0, radius, 0, Math.PI*2, true); ctx.stroke(); } else { ctx.translate(this.plotOffset.left, this.plotOffset.top); // Draw axis/grid border. var lw = o.grid.outlineWidth, orig = 0.5-lw+((lw+1)%2/2); ctx.lineWidth = lw; ctx.strokeStyle = o.grid.color; ctx.lineJoin = 'miter'; ctx.strokeRect(orig, orig, this.plotWidth, this.plotHeight); } ctx.restore(); }, /** * Draws labels for x and y axis. */ drawLabels: function(){ // Construct fixed width label boxes, which can be styled easily. var noLabels = 0, axis, xBoxWidth, i, html, tick, left, top, options = this.options, ctx = this.ctx, a = this.axes; for(i = 0; i < a.x.ticks.length; ++i){ if (a.x.ticks[i].label) { ++noLabels; } } xBoxWidth = this.plotWidth / noLabels; if (options.grid.circular) { ctx.save(); ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2); var radius = this.plotHeight*options.radar.radiusRatio/2 + options.fontSize, sides = this.axes.x.ticks.length, coeff = 2*(Math.PI/sides), angle = -Math.PI/2; var style = { size: options.fontSize }; // Add x labels. axis = a.x; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels; ++i){ tick = axis.ticks[i]; tick.label += ''; if(!tick.label || tick.label.length == 0) continue; var x = Math.cos(i*coeff+angle) * radius, y = Math.sin(i*coeff+angle) * radius; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textBaseline = 'middle'; style.textAlign = (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')); Flotr.drawText(ctx, tick.label, x, y, style); } for(i = 0; i < axis.minorTicks.length && axis.options.showMinorLabels; ++i){ tick = axis.minorTicks[i]; tick.label += ''; if(!tick.label || tick.label.length == 0) continue; var x = Math.cos(i*coeff+angle) * radius, y = Math.sin(i*coeff+angle) * radius; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textBaseline = 'middle'; style.textAlign = (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')); Flotr.drawText(ctx, tick.label, x, y, style); } // Add y labels. axis = a.y; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels; ++i){ tick = axis.ticks[i]; tick.label += ''; if(!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textBaseline = 'middle'; style.textAlign = 'left'; Flotr.drawText(ctx, tick.label, 3, -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), style); } for(i = 0; i < axis.minorTicks.length && axis.options.showMinorLabels; ++i){ tick = axis.minorTicks[i]; tick.label += ''; if(!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textBaseline = 'middle'; style.textAlign = 'left'; Flotr.drawText(ctx, tick.label, 3, -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), style); } ctx.restore(); return; } if (!options.HtmlText && this.textEnabled) { var style = { size: options.fontSize }; // Add x labels. axis = a.x; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; left = axis.d2p(tick.v); if (left < 0 || left > this.plotWidth) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textAlign = 'center'; style.textBaseline = 'top'; style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, tick.label, this.plotOffset.left + left, this.plotOffset.top + this.plotHeight + options.grid.labelMargin, style ); } // Add x2 labels. axis = a.x2; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; left = axis.d2p(tick.v); if(left < 0 || left > this.plotWidth) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textAlign = 'center'; style.textBaseline = 'bottom'; style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, tick.label, this.plotOffset.left + left, this.plotOffset.top + options.grid.labelMargin, style ); } // Add y labels. axis = a.y; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; top = axis.d2p(tick.v); if(top < 0 || top > this.plotHeight) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textAlign = 'right'; style.textBaseline = 'middle'; style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, tick.label, this.plotOffset.left - options.grid.labelMargin, this.plotOffset.top + top, style ); } // Add y2 labels. axis = a.y2; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; top = axis.d2p(tick.v); if(top < 0 || top > this.plotHeight) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.textAlign = 'left'; style.textBaseline = 'middle'; style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, tick.label, this.plotOffset.left + this.plotWidth + options.grid.labelMargin, this.plotOffset.top + top, style ); ctx.save(); ctx.strokeStyle = style.color; ctx.beginPath(); ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + axis.d2p(tick.v)); ctx.lineTo(this.plotOffset.left + this.plotWidth, this.plotOffset.top + axis.d2p(tick.v)); ctx.stroke(); ctx.restore(); } } else if (a.x.options.showLabels || a.x2.options.showLabels || a.y.options.showLabels || a.y2.options.showLabels) { html = ['
']; // Add x labels. axis = a.x; if (axis.options.showLabels){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0 || (this.plotOffset.left + axis.d2p(tick.v) < 0) || (this.plotOffset.left + axis.d2p(tick.v) > this.canvasWidth)) continue; html.push( '
', tick.label, '
' ); } } // Add x2 labels. axis = a.x2; if (axis.options.showLabels && axis.used){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0 || (this.plotOffset.left + axis.d2p(tick.v) < 0) || (this.plotOffset.left + axis.d2p(tick.v) > this.canvasWidth)) continue; html.push( '
', tick.label, '
' ); } } // Add y labels. axis = a.y; if (axis.options.showLabels){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0 || (this.plotOffset.top + axis.d2p(tick.v) < 0) || (this.plotOffset.top + axis.d2p(tick.v) > this.canvasHeight)) continue; html.push( '
', tick.label, '
' ); } } // Add y2 labels. axis = a.y2; if (axis.options.showLabels && axis.used){ ctx.save(); ctx.strokeStyle = axis.options.color || options.grid.color; ctx.beginPath(); for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0 || (this.plotOffset.top + axis.d2p(tick.v) < 0) || (this.plotOffset.top + axis.d2p(tick.v) > this.canvasHeight)) continue; html.push( '
', tick.label, '
' ); ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + axis.d2p(tick.v)); ctx.lineTo(this.plotOffset.left + this.plotWidth, this.plotOffset.top + axis.d2p(tick.v)); } ctx.stroke(); ctx.restore(); } html.push('
'); this.el.insert(html.join('')); } }, /** * Draws the title and the subtitle */ drawTitles: function(){ var html, options = this.options, margin = options.grid.labelMargin, ctx = this.ctx, a = this.axes; if (!options.HtmlText && this.textEnabled) { var style = { size: options.fontSize, color: options.grid.color, textAlign: 'center' }; // Add subtitle if (options.subtitle){ Flotr.drawText( ctx, options.subtitle, this.plotOffset.left + this.plotWidth/2, this.titleHeight + this.subtitleHeight - 2, style ); } style.weight = 1.5; style.size *= 1.5; // Add title if (options.title){ Flotr.drawText( ctx, options.title, this.plotOffset.left + this.plotWidth/2, this.titleHeight - 2, style ); } style.weight = 1.8; style.size *= 0.8; // Add x axis title if (a.x.options.title && a.x.used){ style.textAlign = a.x.options.titleAlign || 'center'; style.textBaseline = 'top'; style.angle = Flotr.toRad(a.x.options.titleAngle); style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, a.x.options.title, this.plotOffset.left + this.plotWidth/2, this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, style ); } // Add x2 axis title if (a.x2.options.title && a.x2.used){ style.textAlign = a.x2.options.titleAlign || 'center'; style.textBaseline = 'bottom'; style.angle = Flotr.toRad(a.x2.options.titleAngle); style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, a.x2.options.title, this.plotOffset.left + this.plotWidth/2, this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, style ); } // Add y axis title if (a.y.options.title && a.y.used){ style.textAlign = a.y.options.titleAlign || 'right'; style.textBaseline = 'middle'; style.angle = Flotr.toRad(a.y.options.titleAngle); style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, a.y.options.title, this.plotOffset.left - a.y.maxLabel.width - 2 * margin, this.plotOffset.top + this.plotHeight / 2, style ); } // Add y2 axis title if (a.y2.options.title && a.y2.used){ style.textAlign = a.y2.options.titleAlign || 'left'; style.textBaseline = 'middle'; style.angle = Flotr.toRad(a.y2.options.titleAngle); style = Flotr.getBestTextAlign(style.angle, style); Flotr.drawText( ctx, a.y2.options.title, this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, this.plotOffset.top + this.plotHeight / 2, style ); } } else { html = ['
']; // Add title if (options.title) html.push( '
', options.title, '
' ); // Add subtitle if (options.subtitle) html.push( '
', options.subtitle, '
' ); html.push('
'); html.push('
'); // Add x axis title if (a.x.options.title && a.x.used) html.push( '
', a.x.options.title, '
' ); // Add x2 axis title if (a.x2.options.title && a.x2.used) html.push( '
', a.x2.options.title, '
' ); // Add y axis title if (a.y.options.title && a.y.used) html.push( '
', a.y.options.title, '
' ); // Add y2 axis title if (a.y2.options.title && a.y2.used) html.push( '
', a.y2.options.title, '
' ); html.push('
'); this.el.insert(html.join('')); } }, /** * Actually draws the graph. * @param {Object} series - series to draw */ drawSeries: function(series){ series = series || this.series; var drawn = false; for(type in Flotr.graphTypes){ if(series[type] && series[type].show){ drawn = true; this[type].draw(series); } } if(!drawn){ this[this.options.defaultType].draw(series); } }, /** * Adds a legend div to the canvas container or draws it on the canvas. */ insertLegend: function(){ if(!this.options.legend.show) return; var series = this.series, plotOffset = this.plotOffset, options = this.options, legend = options.legend, fragments = [], rowStarted = false, ctx = this.ctx, i; var noLegendItems = series.findAll(function(s) {return (s.label && !s.hide)}).length; if (noLegendItems) { if (!options.HtmlText && this.textEnabled && !$(legend.container)) { var style = { size: options.fontSize*1.1, color: options.grid.color }; var p = legend.position, m = legend.margin, lbw = legend.labelBoxWidth, lbh = legend.labelBoxHeight, lbm = legend.labelBoxMargin, offsetX = plotOffset.left + m, offsetY = plotOffset.top + m; // We calculate the labels' max width var labelMaxWidth = 0; for(i = series.length - 1; i > -1; --i){ if(!series[i].label || series[i].hide) continue; var label = legend.labelFormatter(series[i].label); labelMaxWidth = Math.max(labelMaxWidth, Flotr.measureText(ctx, label, style).width); } var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), legendHeight = Math.round(noLegendItems*(lbm+lbh) + lbm); if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); // Legend box var color = this.processColor(options.legend.backgroundColor || 'rgb(240,240,240)', {opacity: options.legend.backgroundOpacity || 0.1}); ctx.fillStyle = color; ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); ctx.strokeStyle = options.legend.labelBoxBorderColor; ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); // Legend labels var x = offsetX + lbm; var y = offsetY + lbm; for(i = 0; i < series.length; i++){ if(!series[i].label || series[i].hide) continue; var label = legend.labelFormatter(series[i].label); ctx.fillStyle = series[i].color; ctx.fillRect(x, y, lbw-1, lbh-1); ctx.strokeStyle = legend.labelBoxBorderColor; ctx.lineWidth = 1; ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); // Legend text Flotr.drawText(ctx, label, x + lbw + lbm, y + (lbh + style.size - ctx.fontDescent(style))/2, style); y += lbh + lbm; } } else { for(i = 0; i < series.length; ++i){ if(!series[i].label || series[i].hide) continue; if(i % options.legend.noColumns == 0){ fragments.push(rowStarted ? '' : ''); rowStarted = true; } var s = series[i], label = legend.labelFormatter(s.label), boxWidth = legend.labelBoxWidth, boxHeight = legend.labelBoxHeight, opacity = 'opacity:' + s.bars.fillOpacity + ';filter:alpha(opacity=' + s.bars.fillOpacity*100 + ');', color = 'background-color:' + ((s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';'; fragments.push( '', '
', '
', // Border '
', // Background '
', '
', '', '', label, '' ); } if(rowStarted) fragments.push(''); if(fragments.length > 0){ var table = '' + fragments.join('') + '
'; if(options.legend.container != null){ $(options.legend.container).innerHTML = table; } else { var pos = '', p = options.legend.position, m = options.legend.margin; if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; var div = this.el.insert('
' + table + '
').select('div.flotr-legend')[0]; if(options.legend.backgroundOpacity != 0.0){ /** * Put in the transparent background separately to avoid blended labels and * label boxes. */ var c = options.legend.backgroundColor; if(c == null){ var tmp = (options.grid.backgroundColor != null) ? options.grid.backgroundColor : Flotr.Color.extract(div); c = this.processColor(tmp, null, {opacity: 1}); } this.el.insert( '
' ) .select('div.flotr-legend-bg')[0].setOpacity(options.legend.backgroundOpacity); } } } } } }, /** * Calculates the coordinates from a mouse event object. * @param {Event} event - Mouse Event object. * @return {Object} Object with coordinates of the mouse. */ getEventPosition: function (event){ var offset = this.overlay.cumulativeOffset(), pointer = Event.pointer(event), rx = (pointer.x - offset.left - this.plotOffset.left), ry = (pointer.y - offset.top - this.plotOffset.top); return { x: this.axes.x.p2d(rx), x2: this.axes.x2.p2d(rx), y: this.axes.y.p2d(ry), y2: this.axes.y2.p2d(ry), relX: rx, relY: ry, absX: pointer.x, absY: pointer.y }; }, /** * Observes the 'click' event and fires the 'flotr:click' event. * @param {Event} event - 'click' Event object. */ clickHandler: function(event){ if(this.ignoreClick){ return this.ignoreClick = false; } this.el.fire('flotr:click', [this.getEventPosition(event), this]); }, /** * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event. * @param {Event} event - 'mousemove' Event object. */ mouseMoveHandler: function(event){ var pos = this.getEventPosition(event); this.lastMousePos.pageX = pos.absX; this.lastMousePos.pageY = pos.absY; //@todo Add another overlay for the crosshair if (this.options.crosshair.mode) this.clearCrosshair(); if(this.selectionInterval == null && (this.options.mouse.track || this.series.any(function(s){return s.mouse && s.mouse.track;}))) this.hit(pos); //this.newHit(pos); if (this.options.crosshair.mode) this.drawCrosshair(pos); this.el.fire('flotr:mousemove', [event, pos, this]); }, /** * Observes the 'mousedown' event. * @param {Event} event - 'mousedown' Event object. */ mouseDownHandler: function (event){ if(event.isRightClick()) { event.stop(); var overlay = this.overlay; overlay.hide(); function cancelContextMenu () { overlay.show(); document.stopObserving('mousemove', cancelContextMenu); } document.observe('mousemove', cancelContextMenu); return; } if(!this.options.selection.mode || !event.isLeftClick()) return; this.setSelectionPos(this.selection.first, event); if(this.selectionInterval != null){ clearInterval(this.selectionInterval); } this.lastMousePos.pageX = null; this.selectionInterval = setInterval(this.updateSelection.bindAsEventListener(this), 1000/this.options.selection.fps); this.mouseUpHandler = this.mouseUpHandler.bindAsEventListener(this); document.observe('mouseup', this.mouseUpHandler); }, /** * Fires the 'flotr:select' event when the user made a selection. */ fireSelectEvent: function(){ var a = this.axes, s = this.selection, x1, x2, y1, y2; x1 = a.x.p2d(s.first.x); x2 = a.x.p2d(s.second.x); y1 = a.y.p2d(s.first.y); y2 = a.y.p2d(s.second.y); this.el.fire('flotr:select', [{ x1:Math.min(x1, x2), y1:Math.min(y1, y2), x2:Math.max(x1, x2), y2:Math.max(y1, y2), xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2 }, this]); }, /** * Observes the mouseup event for the document. * @param {Event} event - 'mouseup' Event object. */ mouseUpHandler: function(event){ document.stopObserving('mouseup', this.mouseUpHandler); event.stop(); if(this.selectionInterval != null){ clearInterval(this.selectionInterval); this.selectionInterval = null; } this.setSelectionPos(this.selection.second, event); this.clearSelection(); if(this.selectionIsSane()){ this.drawSelection(); this.fireSelectEvent(); this.ignoreClick = true; } }, /** * Calculates the position of the selection. * @param {Object} pos - Position object. * @param {Event} event - Event object. */ setSelectionPos: function(pos, event) { var options = this.options, offset = this.overlay.cumulativeOffset(); if(options.selection.mode.indexOf('x') == -1){ pos.x = (pos == this.selection.first) ? 0 : this.plotWidth; }else{ pos.x = event.pageX - offset.left - this.plotOffset.left; pos.x = Math.min(Math.max(0, pos.x), this.plotWidth); } if (options.selection.mode.indexOf('y') == -1){ pos.y = (pos == this.selection.first) ? 0 : this.plotHeight; }else{ pos.y = event.pageY - offset.top - this.plotOffset.top; pos.y = Math.min(Math.max(0, pos.y), this.plotHeight); } }, /** * Updates (draws) the selection box. */ updateSelection: function(){ if(this.lastMousePos.pageX == null) return; this.setSelectionPos(this.selection.second, this.lastMousePos); this.clearSelection(); if(this.selectionIsSane()) this.drawSelection(); }, /** * Removes the selection box from the overlay canvas. */ clearSelection: function() { if(this.prevSelection == null) return; var prevSelection = this.prevSelection, lw = this.octx.lineWidth, plotOffset = this.plotOffset, x = Math.min(prevSelection.first.x, prevSelection.second.x), y = Math.min(prevSelection.first.y, prevSelection.second.y), w = Math.abs(prevSelection.second.x - prevSelection.first.x), h = Math.abs(prevSelection.second.y - prevSelection.first.y); this.octx.clearRect(x + plotOffset.left - lw/2+0.5, y + plotOffset.top - lw/2+0.5, w + lw, h + lw); this.prevSelection = null; }, /** * Allows the user the manually select an area. * @param {Object} area - Object with coordinates to select. */ setSelection: function(area, preventEvent){ var options = this.options, xa = this.axes.x, ya = this.axes.y, vertScale = ya.scale, hozScale = xa.scale, selX = options.selection.mode.indexOf('x') != -1, selY = options.selection.mode.indexOf('y') != -1; this.clearSelection(); this.selection.first.y = (selX && !selY) ? 0 : (ya.max - area.y1) * vertScale; this.selection.second.y = (selX && !selY) ? this.plotHeight : (ya.max - area.y2) * vertScale; this.selection.first.x = (selY && !selX) ? 0 : (area.x1 - xa.min) * hozScale; this.selection.second.x = (selY && !selX) ? this.plotWidth : (area.x2 - xa.min) * hozScale; this.drawSelection(); if (!preventEvent) this.fireSelectEvent(); }, /** * Draws the selection box. */ drawSelection: function() { var prevSelection = this.prevSelection, s = this.selection, octx = this.octx, options = this.options, plotOffset = this.plotOffset; if(prevSelection != null && s.first.x == prevSelection.first.x && s.first.y == prevSelection.first.y && s.second.x == prevSelection.second.x && s.second.y == prevSelection.second.y) return; octx.save(); octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); octx.lineWidth = 1; octx.lineJoin = 'miter'; octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); this.prevSelection = { first: { x: s.first.x, y: s.first.y }, second: { x: s.second.x, y: s.second.y } }; var x = Math.min(s.first.x, s.second.x), y = Math.min(s.first.y, s.second.y), w = Math.abs(s.second.x - s.first.x), h = Math.abs(s.second.y - s.first.y); octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); octx.restore(); }, /** * Draws the selection box. */ drawCrosshair: function(pos) { var octx = this.octx, options = this.options, plotOffset = this.plotOffset, x = plotOffset.left+pos.relX+0.5, y = plotOffset.top+pos.relY+0.5; if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) { this.el.style.cursor = null; this.el.removeClassName('flotr-crosshair'); return; } this.lastMousePos.relX = null; this.lastMousePos.relY = null; if (options.crosshair.hideCursor) { this.el.style.cursor = Prototype.Browser.Gecko ? 'none' :'url(blank.cur),crosshair'; this.el.addClassName('flotr-crosshair'); } octx.save(); octx.strokeStyle = options.crosshair.color; octx.lineWidth = 1; octx.beginPath(); if (options.crosshair.mode.indexOf('x') != -1) { octx.moveTo(x, plotOffset.top); octx.lineTo(x, plotOffset.top + this.plotHeight); this.lastMousePos.relX = x; } if (options.crosshair.mode.indexOf('y') != -1) { octx.moveTo(plotOffset.left, y); octx.lineTo(plotOffset.left + this.plotWidth, y); this.lastMousePos.relY = y; } octx.stroke(); octx.restore(); }, /** * Removes the selection box from the overlay canvas. */ clearCrosshair: function() { if (this.lastMousePos.relX != null) this.octx.clearRect(this.lastMousePos.relX-0.5, this.plotOffset.top, 1,this.plotHeight+1); if (this.lastMousePos.relY != null) this.octx.clearRect(this.plotOffset.left, this.lastMousePos.relY-0.5, this.plotWidth+1, 1); }, /** * Determines whether or not the selection is sane and should be drawn. * @return {Boolean} - True when sane, false otherwise. */ selectionIsSane: function(){ return Math.abs(this.selection.second.x - this.selection.first.x) >= 5 && Math.abs(this.selection.second.y - this.selection.first.y) >= 5; }, /** * Removes the mouse tracking point from the overlay. */ clearHit: function(){ if(!this.prevHit) return; var prevHit = this.prevHit, plotOffset = this.plotOffset, s = prevHit.series, lw = s.bars.lineWidth, lwPie = s.pie.lineWidth, xa = prevHit.xaxis, ya = prevHit.yaxis; if(!s.bars.show && !s.pie.show && !s.bubbles.show){ var offset = s.mouse.radius + lw; this.octx.clearRect( plotOffset.left + xa.d2p(prevHit.x) - offset, plotOffset.top + ya.d2p(prevHit.y) - offset, offset*2, offset*2 ); } else if (s.bars.show){ var bw = s.bars.barWidth; if(!s.bars.horizontal){ // vertical bars (default) var lastY = ya.d2p(prevHit.y >= 0 ? prevHit.y : 0); if(s.bars.centered) { this.octx.clearRect( xa.d2p(prevHit.x - bw/2) + plotOffset.left - lw, lastY + plotOffset.top - lw, xa.d2p(bw + xa.min) + lw * 2, ya.d2p(prevHit.y < 0 ? prevHit.y : 0) - lastY + lw * 2 ); } else { this.octx.clearRect( xa.d2p(prevHit.x) + plotOffset.left - lw, lastY + plotOffset.top - lw, xa.d2p(bw + xa.min) + lw * 2, ya.d2p(prevHit.y < 0 ? prevHit.y : 0) - lastY + lw * 2 ); } } else { // horizontal bars var lastX = xa.d2p(prevHit.x >= 0 ? prevHit.x : 0); if(s.bars.centered) { this.octx.clearRect( lastX + plotOffset.left + lw, ya.d2p(prevHit.y + bw/2) + plotOffset.top - lw, xa.d2p(prevHit.x < 0 ? prevHit.x : 0) - lastX - lw*2, ya.d2p(bw + ya.min) + lw * 2 ); } else { this.octx.clearRect( lastX + plotOffset.left + lw, ya.d2p(prevHit.y + bw) + plotOffset.top - lw, xa.d2p(prevHit.x < 0 ? prevHit.x : 0) - lastX - lw*2, ya.d2p(bw + ya.min) + lw * 2 ); } } } else if (s.bubbles.show){ this.bubbles.clearHit(); } else if (s.pie.show){ this.pie.clearHit(); } }, /** * Updates the mouse tracking point on the overlay. */ drawHit: function(n){ var octx = this.octx, s = n.series, xa = n.xaxis, ya = n.yaxis; if(s.mouse.lineColor != null){ octx.save(); octx.lineWidth = s.points.lineWidth; octx.strokeStyle = s.mouse.lineColor; octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); if(!s.bars.show && !s.pie.show && !s.bubbles.show){ octx.translate(this.plotOffset.left, this.plotOffset.top); octx.beginPath(); octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.mouse.radius, 0, 2 * Math.PI, true); octx.fill(); octx.stroke(); octx.closePath(); } else if (s.bars.show){ octx.save(); octx.translate(this.plotOffset.left, this.plotOffset.top); octx.beginPath(); if (s.mouse.trackAll) { octx.moveTo(xa.d2p(n.x), ya.d2p(0)); octx.lineTo(xa.d2p(n.x), ya.d2p(n.yaxis.max)); } else { var bw = s.bars.barWidth, y = ya.d2p(n.y), x = xa.d2p(n.x); if(!s.bars.horizontal){ //vertical bars (default) var ly = ya.d2p(ya.min<0? 0 : ya.min); //lower vertex y value (in points) if(s.bars.centered){ var lx = xa.d2p(n.x-(bw/2)), rx = xa.d2p(n.x+(bw/2)); octx.moveTo(lx, ly); octx.lineTo(lx, y); octx.lineTo(rx, y); octx.lineTo(rx, ly); } else { var rx = xa.d2p(n.x+bw); //right vertex x value (in points) octx.moveTo(x, ly); octx.lineTo(x, y); octx.lineTo(rx, y); octx.lineTo(rx, ly); } } else { //horizontal bars var lx = xa.d2p(xa.min<0? 0 : xa.min); //left vertex y value (in points) if(s.bars.centered){ var ly = ya.d2p(n.y-(bw/2)), uy = ya.d2p(n.y+(bw/2)); octx.moveTo(lx, ly); octx.lineTo(x, ly); octx.lineTo(x, uy); octx.lineTo(lx, uy); } else { var uy = ya.d2p(n.y+bw); //upper vertex y value (in points) octx.moveTo(lx, y); octx.lineTo(x, y); octx.lineTo(x, uy); octx.lineTo(lx, uy); } } if(s.mouse.fillColor) octx.fill(); } octx.stroke(); octx.closePath(); octx.restore(); } else if (s.bubbles.show){ this.bubbles.drawHit(n); } else if (s.pie.show){ this.pie.drawHit(n); } octx.restore(); } this.prevHit = n; }, newHit: function(mouse){ var series = this.series, options = this.options, decimals, label; for(var i = series.length-1; i > -1; --i){ s = series[i]; if(!s.mouse.track) continue; for(var type in Flotr.graphTypes){ if (!this[type].getHit) continue; var h = this[type].getHit(s, mouse); if (h.index !== undefined) { decimals = s.mouse.trackDecimals; if(decimals == null || decimals < 0) decimals = 0; label = s.mouse.trackFormatter(h); this.drawTooltip(label, h.x, h.y, s.mouse); this.mouseTrack.fire('flotr:hit', [h, this]); } } } }, /** * Retrieves the nearest data point from the mouse cursor. If it's within * a certain range, draw a point on the overlay canvas and display the x and y * value of the data. * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor. */ hit: function(mouse){ var series = this.series, options = this.options, prevHit = this.prevHit, plotOffset = this.plotOffset, octx = this.octx, data, sens, xsens, ysens, x, y, xa, ya, mx, my, i, /** * Nearest data element. */ n = { dist:Number.MAX_VALUE, x:null, y:null, relX:mouse.relX, relY:mouse.relY, absX:mouse.absX, absY:mouse.absY, sAngle:null, eAngle:null, fraction: null, mouse:null, xaxis:null, yaxis:null, series:null, index:null, seriesIndex:null }; if (options.mouse.trackAll) { for(i = 0; i < series.length; i++){ s = series[0]; data = s.data; xa = s.xaxis; ya = s.yaxis; xsens = (2*options.points.lineWidth)/xa.scale * s.mouse.sensibility; mx = xa.p2d(mouse.relX); my = ya.p2d(mouse.relY); for(var j = 0; j < data.length; j++){ x = data[j][0]; y = data[j][1]; if (y === null || xa.min > x || xa.max < x || ya.min > y || ya.max < y || mx < xa.min || mx > xa.max || my < ya.min || my > ya.max) continue; var xdiff = Math.abs(x - mx); // Bars and Pie are not supported yet. Not sure how it should look with bars or Pie if((!s.bars.show && xdiff < xsens) || (s.bars.show && xdiff < s.bars.barWidth/2) || (y < 0 && my < 0 && my > y)) { var distance = xdiff; if (distance < n.dist) { n.dist = distance; n.x = x; n.y = y; n.xaxis = xa; n.yaxis = ya; n.mouse = s.mouse; n.series = s; n.allSeries = series; // include all series n.index = j; } } } } } else { if (!options.pie.show){ for(i = 0; i < series.length; i++){ s = series[i]; if(!s.mouse.track) continue; data = s.data; xa = s.xaxis; ya = s.yaxis; sens = 2 * options.points.lineWidth * s.mouse.sensibility; xsens = sens/xa.scale; ysens = sens/ya.scale; mx = xa.p2d(mouse.relX); my = ya.p2d(mouse.relY); //if (s.points) { // var h = this.points.getHit(s, mouse); // if (h.index !== undefined) console.log(h); //} for(var j = 0, xpow, ypow; j < data.length; j++){ x = data[j][0]; y = data[j][1]; if (y === null || xa.min > x || xa.max < x || ya.min > y || ya.max < y) continue; var xdiff, ydiff; if (s.bars.show && !s.bars.centered) { if (s.bars.horizontal) { xdiff = Math.abs(x - mx); ydiff = Math.abs(y + s.bars.barWidth / 2 - my); } else { xdiff = Math.abs(x + s.bars.barWidth / 2 - mx); ydiff = Math.abs(y - my); } } else { xdiff = Math.abs(x - mx); ydiff = Math.abs(y - my); } // we use a different set of criteria to determin if there has been a hit // depending on what type of graph we have if(((!s.bars.show) && xdiff < xsens && (!s.mouse.trackY || ydiff < ysens)) || // Bars check (s.bars.show && (!s.bars.horizontal && xdiff < s.bars.barWidth/2 + 1/xa.scale // Check x bar boundary, with adjustment for scale (when bars ~1px) && (!s.mouse.trackY || (y > 0 && my > 0 && my < y) || (y < 0 && my < 0 && my > y))) || (s.bars.horizontal && ydiff < s.bars.barWidth/2 + 1/ya.scale // Check x bar boundary, with adjustment for scale (when bars ~1px) && ((x > 0 && mx > 0 && mx < x) || (x < 0 && mx < 0 && mx > x))))){ // for horizontal bars there is need to use y-axis tracking, so s.mouse.trackY is ignored var distance = Math.sqrt(xdiff*xdiff + ydiff*ydiff); if(distance < n.dist){ n.dist = distance; n.x = x; n.y = y; n.xaxis = xa; n.yaxis = ya; n.mouse = s.mouse; n.series = s; n.allSeries = series; n.index = j; n.seriesIndex = i; } } } } } else { var radius = (Math.min(this.canvasWidth, this.canvasHeight) * options.pie.sizeRatio) / 2, vScale = 1,//Math.cos(series.pie.viewAngle), center = { x: (this.plotWidth)/2, y: (this.plotHeight)/2 }, // Pie portions portions = this.series.collect(function(hash, index){ if (hash.pie.show && hash.data[0][1] !== null) return { name: (hash.label || hash.data[0][1]), value: [index, hash.data[0][1]], options: hash.pie, series: hash }; }), // Sum of the portions' angles sum = portions.pluck('value').pluck(1).inject(0, function(acc, n) { return acc + n; }), fraction = 0.0, angle = options.pie.startAngle, value = 0.0; var slices = portions.collect(function(slice){ angle += fraction; value = parseFloat(slice.value[1]); // @warning : won't support null values !! fraction = value/sum; return { name: slice.name, fraction: fraction, x: slice.value[0], y: value, value: value, options: slice.options, series: slice.series, startAngle: 2 * angle * Math.PI, endAngle: 2 * (angle + fraction) * Math.PI }; }); for(i = 0; i < series.length; i++){ s = series[i]; x = s.data[0][0]; y = s.data[0][1]; if (y === null) continue; var a = (mouse.relX-center.x), b = (mouse.relY-center.y), c = Math.sqrt(Math.pow(a, 2)+Math.pow(b, 2)), sAngle = (slices[i].startAngle)%(2 * Math.PI), eAngle = (slices[i].endAngle)%(2 * Math.PI), sAngle = (sAngle> 0 )? sAngle : sAngle + (2 * Math.PI), eAngle = (eAngle> 0 )? eAngle : eAngle + (2 * Math.PI), xSin = b/c, kat = Math.asin(xSin)%(2 * Math.PI), kat = (kat>0) ? kat : kat + (2 * Math.PI), kat2 = Math.asin(-xSin)+(Math.PI); //if (c0 && sAngle < kat && eAngle > kat)) //I i IV quarter //if (c kat2)) //II i III quarter //if(sAngle>aAngle && ((a>0 && (sAngle < kat || eAngle > kat)) || (a<0 && (sAngle < kat2 || eAngle > kat2)))) //if a slice is crossing 0 angle if (c0 && sAngle < kat && eAngle > kat)) || (a<0 && sAngle < kat2 && eAngle > kat2)) || ( (sAngle>eAngle || slices[i].fraction==1) && ((a>0 && (sAngle < kat || eAngle > kat)) || (a<0 && (sAngle < kat2 || eAngle > kat2)))))) { n.x = x; n.y = y; n.sAngle = sAngle; n.eAngle = eAngle, n.mouse = s.mouse; n.series = s; n.allSeries = series; n.seriesIndex = i; n.fraction = slices[i].fraction; } } } } if(n.series && (n.mouse && n.mouse.track && !prevHit || (prevHit /*&& (n.x != prevHit.x || n.y != prevHit.y)*/))){ var mt = this.mouseTrack, pos = '', s = n.series, p = n.mouse.position, m = n.mouse.margin, elStyle = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; if (!n.mouse.relative) { // absolute to the canvas if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; } else { // relative to the mouse or in the case of bar like graphs to the bar if(!s.bars.show && !s.pie.show){ if(p.charAt(0) == 'n') pos += 'bottom:' + (m - plotOffset.top - n.yaxis.d2p(n.y) + this.canvasHeight) + 'px;top:auto;'; else if(p.charAt(0) == 's') pos += 'top:' + (m + plotOffset.top + n.yaxis.d2p(n.y)) + 'px;bottom:auto;'; if(p.charAt(1) == 'e') pos += 'left:' + (m + plotOffset.left + n.xaxis.d2p(n.x)) + 'px;right:auto;'; else if(p.charAt(1) == 'w') pos += 'right:' + (m - plotOffset.left - n.xaxis.d2p(n.x) + this.canvasWidth) + 'px;left:auto;'; } else if (s.bars.show) { pos += 'bottom:' + (m - plotOffset.top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;'; pos += 'left:' + (m + plotOffset.left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;'; } else { var center = { x: (this.plotWidth)/2, y: (this.plotHeight)/2 }, radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, bisection = n.sAngle'); mt = this.mouseTrack = this.el.select('.flotr-mouse-value')[0]; } else { mt.style.cssText = elStyle; this.mouseTrack = mt; } if(n.x !== null && n.y !== null){ mt.show(); this.clearHit(); this.drawHit(n); var decimals = n.mouse.trackDecimals; if(decimals == null || decimals < 0) decimals = 0; mt.innerHTML = n.mouse.trackFormatter({ x: n.x.toFixed(decimals), y: n.y.toFixed(decimals), series: n.series, index: n.index, nearest: n, fraction: n.fraction }); mt.fire('flotr:hit', [n, this]); } else if(prevHit){ mt.hide(); this.clearHit(); } } else if(this.prevHit) { this.mouseTrack.hide(); this.clearHit(); } }, drawTooltip: function(content, x, y, options) { var mt = this.mouseTrack, style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', p = options.position, m = options.margin, plotOffset = this.plotOffset; if (!mt) { this.el.insert('
'); mt = this.mouseTrack = this.el.select('.flotr-mouse-value')[0]; } if(x !== null && y !== null){ if (!options.relative) { // absolute to the canvas if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; } else { // relative to the mouse if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;'; else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;'; if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;'; else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;'; } mt.style.cssText = style; mt.update(content).show(); } else { mt.hide(); } }, saveImage: function (type, width, height, replaceCanvas) { var image = null; if (Prototype.Browser.IE && !Flotr.isIE9) { image = ''+this.canvas.firstChild.innerHTML+''; return window.open().document.write(image); } switch (type) { case 'jpeg': case 'jpg': image = Canvas2Image.saveAsJPEG(this.canvas, replaceCanvas, width, height); break; default: case 'png': image = Canvas2Image.saveAsPNG(this.canvas, replaceCanvas, width, height); break; case 'bmp': image = Canvas2Image.saveAsBMP(this.canvas, replaceCanvas, width, height); break; } if (Object.isElement(image) && replaceCanvas) { this.restoreCanvas(); this.canvas.hide(); this.overlay.hide(); this.el.insert(image.setStyle({position: 'absolute'})); } }, restoreCanvas: function() { this.canvas.show(); this.overlay.show(); this.el.select('img').invoke('remove'); } }); Flotr.Color = Class.create({ initialize: function(r, g, b, a){ this.rgba = ['r','g','b','a']; var x = 4; while(-1<--x){ this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); } this.normalize(); }, adjust: function(rd, gd, bd, ad) { var x = 4; while(-1<--x){ if(arguments[x] != null) this[this.rgba[x]] += arguments[x]; } return this.normalize(); }, scale: function(rf, gf, bf, af){ var x = 4; while(-1<--x){ if(arguments[x] != null) this[this.rgba[x]] *= arguments[x]; } return this.normalize(); }, clone: function(){ return new Flotr.Color(this.r, this.b, this.g, this.a); }, limit: function(val,minVal,maxVal){ return Math.max(Math.min(val, maxVal), minVal); }, normalize: function(){ var limit = this.limit; this.r = limit(parseInt(this.r), 0, 255); this.g = limit(parseInt(this.g), 0, 255); this.b = limit(parseInt(this.b), 0, 255); this.a = limit(this.a, 0, 1); return this; }, distance: function(color){ if (!color) return; color = new Flotr.Color.parse(color); var dist = 0, x = 3; while(-1<--x){ dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); } return dist; }, toString: function(){ return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; } }); Object.extend(Flotr.Color, { /** * Parses a color string and returns a corresponding Color. * The different tests are in order of probability to improve speed. * @param {String, Color} str - string thats representing a color * @return {Color} returns a Color object or false */ parse: function(color){ if (color instanceof Flotr.Color) return color; var result, Color = Flotr.Color; // #a0b1c2 if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))) return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)); // rgb(num,num,num) if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))) return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3])); // #fff if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))) return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); // rgba(num,num,num,num) if((result = /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(color))) return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4])); // rgb(num%,num%,num%) if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))) return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); // rgba(num%,num%,num%,num) if((result = /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(color))) return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); // Otherwise, we're most likely dealing with a named color. var name = (color+'').strip().toLowerCase(); if(name == 'transparent'){ return new Color(255, 255, 255, 0); } return (result = Color.names[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0); }, /** * Extracts the background-color of the passed element. * @param {Element} element - The element from what the background color is extracted * @return {String} color string */ extract: function(element){ var color; // Loop until we find an element with a background color and stop when we hit the body element. do { color = element.getStyle('background-color').toLowerCase(); if(!(color == '' || color == 'transparent')) break; element = element.up(); } while(!element.nodeName.match(/^body$/i)); // Catch Safari's way of signaling transparent. return new Flotr.Color(color == 'rgba(0, 0, 0, 0)' ? 'transparent' : color); }, names: { 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] } }); Flotr.Date = { format: function(d, format) { if (!d) return; // We should maybe use an "official" date format spec, like PHP date() or ColdFusion // http://fr.php.net/manual/en/function.date.php // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html var tokens = { h: d.getUTCHours().toString(), H: leftPad(d.getUTCHours()), M: leftPad(d.getUTCMinutes()), S: leftPad(d.getUTCSeconds()), s: d.getUTCMilliseconds(), d: d.getUTCDate().toString(), m: (d.getUTCMonth() + 1).toString(), y: d.getUTCFullYear().toString(), b: Flotr.Date.monthNames[d.getUTCMonth()] }; function leftPad(n){ n += ''; return n.length == 1 ? "0" + n : n; } var r = [], c, escape = false; for (var i = 0; i < format.length; ++i) { c = format.charAt(i); if (escape) { r.push(tokens[c] || c); escape = false; } else if (c == "%") escape = true; else r.push(c); } return r.join(''); }, getFormat: function(time, span) { var tu = Flotr.Date.timeUnits; if (time < tu.second) return "%h:%M:%S.%s"; else if (time < tu.minute) return "%h:%M:%S"; else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M"; else if (time < tu.month) return "%b %d"; else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y"; else return "%y"; }, formatter: function (v, axis) { var d = new Date(v); // first check global format if (axis.options.timeFormat != null) return Flotr.Date.format(d, axis.options.timeFormat); var span = axis.max - axis.min, t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit]; return Flotr.Date.format(d, Flotr.Date.getFormat(t, span)); }, generator: function(axis) { var ticks = [], d = new Date(axis.min), tu = Flotr.Date.timeUnits; var step = axis.tickSize * tu[axis.tickUnit]; switch (axis.tickUnit) { case "millisecond": d.setUTCMilliseconds(Flotr.floorInBase(d.getUTCMilliseconds(), axis.tickSize)); break; case "second": d.setUTCSeconds(Flotr.floorInBase(d.getUTCSeconds(), axis.tickSize)); break; case "minute": d.setUTCMinutes(Flotr.floorInBase(d.getUTCMinutes(), axis.tickSize)); break; case "hour": d.setUTCHours(Flotr.floorInBase(d.getUTCHours(), axis.tickSize)); break; case "month": d.setUTCMonth(Flotr.floorInBase(d.getUTCMonth(), axis.tickSize)); break; case "year": d.setUTCFullYear(Flotr.floorInBase(d.getUTCFullYear(), axis.tickSize));break; } // reset smaller components if (step >= tu.second) d.setUTCMilliseconds(0); if (step >= tu.minute) d.setUTCSeconds(0); if (step >= tu.hour) d.setUTCMinutes(0); if (step >= tu.day) d.setUTCHours(0); if (step >= tu.day * 4) d.setUTCDate(1); if (step >= tu.year) d.setUTCMonth(0); var carry = 0, v = Number.NaN, prev; do { prev = v; v = d.getTime(); ticks.push({ v:v, label:Flotr.Date.formatter(v, axis) }); if (axis.tickUnit == "month") { if (axis.tickSize < 1) { /* a bit complicated - we'll divide the month up but we need to take care of fractions so we don't end up in the middle of a day */ d.setUTCDate(1); var start = d.getTime(); d.setUTCMonth(d.getUTCMonth() + 1); var end = d.getTime(); d.setTime(v + carry * tu.hour + (end - start) * axis.tickSize); carry = d.getUTCHours(); d.setUTCHours(0); } else d.setUTCMonth(d.getUTCMonth() + axis.tickSize); } else if (axis.tickUnit == "year") { d.setUTCFullYear(d.getUTCFullYear() + axis.tickSize); } else d.setTime(v + step); } while (v < axis.max && v != prev); return ticks; }, timeUnits: { millisecond: 1, second: 1000, minute: 1000 * 60, hour: 1000 * 60 * 60, day: 1000 * 60 * 60 * 24, month: 1000 * 60 * 60 * 24 * 30, year: 1000 * 60 * 60 * 24 * 365.2425 }, // the allowed tick sizes, after 1 year we use an integer algorithm spec: [ [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], [1, "day"], [2, "day"], [3, "day"], [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], [1, "year"] ], monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }; /** Lines **/ Flotr.addType('lines', { options: { show: false, // => setting to true will show lines, false will hide lineWidth: 2, // => line width in pixels fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill stacked: false // => setting to true will show stacked lines, false will show normal lines }, /** * Draws lines series in the canvas element. * @param {Object} series - Series with options.lines.show = true. */ draw: function(series){ series = series || this.series; var ctx = this.ctx; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'round'; var lw = series.lines.lineWidth; var sw = series.shadowSize; if(sw > 0){ ctx.lineWidth = sw / 2; var offset = lw/2 + ctx.lineWidth/2; ctx.strokeStyle = "rgba(0,0,0,0.1)"; this.lines.plot(series, offset + sw/2, false); ctx.strokeStyle = "rgba(0,0,0,0.2)"; this.lines.plot(series, offset, false); if(series.lines.fill) { ctx.fillStyle = "rgba(0,0,0,0.05)"; this.lines.plotArea(series, offset + sw/2, false); } } ctx.lineWidth = lw; ctx.strokeStyle = series.color; if(series.lines.fill){ ctx.fillStyle = this.processColor(series.lines.fillColor || series.color, {opacity: series.lines.fillOpacity}); this.lines.plotArea(series, 0, true); } this.lines.plot(series, 0, true); ctx.restore(); }, plot: function(series, offset, incStack){ var ctx = this.ctx, xa = series.xaxis, ya = series.yaxis, data = series.data, length = data.length - 1, i; if(data.length < 2) return; var plotWidth = this.plotWidth, plotHeight = this.plotHeight, prevx = null, prevy = null; ctx.beginPath(); for(i = 0; i < length; ++i){ // To allow empty values if (data[i][1] === null || data[i+1][1] === null) continue; // Zero is infinity for log scales if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; if (ya.options.scaling == 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; var x1 = xa.d2p(data[i][0]), y1, x2 = xa.d2p(data[i+1][0]), y2; if (series.lines.stacked) { var stack1 = xa.values[data[i][0]].stack || 0, stack2 = xa.values[data[i+1][0]].stack || xa.values[data[i][0]].stack || 0; y1 = ya.d2p(data[i][1] + stack1); y2 = ya.d2p(data[i+1][1] + stack2); if(incStack){ xa.values[data[i][0]].stack = data[i][1]+stack1; if(i == length-1){ xa.values[data[i+1][0]].stack = data[i+1][1]+stack2; } } } else{ y1 = ya.d2p(data[i][1]); y2 = ya.d2p(data[i+1][1]); } /** * Clip against graph bottom edge. */ if(y1 >= y2 && y1 >= plotHeight){ /** * Line segment is outside the drawing area. */ if(y2 >= plotHeight) continue; /** * Compute new intersection point. */ x1 = x1 - (y1 - plotHeight - 1) / (y2 - y1) * (x2 - x1); y1 = plotHeight - 1; } else if(y2 >= y1 && y2 >= plotHeight){ if(y1 >= plotHeight) continue; x2 = x1 - (y1 - plotHeight - 1) / (y2 - y1) * (x2 - x1); y2 = plotHeight - 1; } /** * Clip against graph top edge. */ if(y1 <= y2 && y1 < 0) { if(y2 < 0) continue; x1 = x1 - y1 / (y2 - y1) * (x2 - x1); y1 = 0; } else if(y2 <= y1 && y2 < 0){ if(y1 < 0) continue; x2 = x1 - y1 / (y2 - y1) * (x2 - x1); y2 = 0; } /** * Clip against graph left edge. */ if(x1 <= x2 && x1 < 0){ if(x2 < 0) continue; y1 = y1 - x1 / (x2 - x1) * (y2 - y1); x1 = 0; } else if(x2 <= x1 && x2 < 0){ if(x1 < 0) continue; y2 = y1 - x1 / (x2 - x1) * (y2 - y1); x2 = 0; } /** * Clip against graph right edge. */ if(x1 >= x2 && x1 >= plotWidth){ if (x2 >= plotWidth) continue; y1 = y1 + (plotWidth - x1) / (x2 - x1) * (y2 - y1); x1 = plotWidth - 1; } else if(x2 >= x1 && x2 >= plotWidth){ if(x1 >= plotWidth) continue; y2 = y1 + (plotWidth - x1) / (x2 - x1) * (y2 - y1); x2 = plotWidth - 1; } if((prevx != x1) || (prevy != y1 + offset)) ctx.moveTo(x1, y1 + offset); prevx = x2; prevy = y2 + offset; ctx.lineTo(prevx, prevy); } ctx.stroke(); ctx.closePath(); }, /** * Function used to fill * @param {Object} series - The series to draw * @param {Object} offset */ plotArea: function(series, offset, saveStrokePath){ var ctx = this.ctx, xa = series.xaxis, ya = series.yaxis, data = series.data, length = data.length - 1, top, bottom = Math.min(Math.max(0, ya.min), ya.max), lastX = 0, first = true, strokePath = [], pathIdx = 0, stack1 = 0, stack2 = 0; function addStrokePath(xVal, yVal) { if (saveStrokePath) { strokePath[pathIdx] = []; strokePath[pathIdx][0] = xVal; strokePath[pathIdx][1] = yVal; pathIdx++; } } if(data.length < 2) return; ctx.beginPath(); for(var i = 0; i < length; ++i){ var x1 = data[i][0], y1, x2 = data[i+1][0], y2; if (series.lines.stacked) { var stack1 = xa.values[data[i][0]].stack || 0, stack2 = xa.values[data[i+1][0]].stack || xa.values[data[i][0]].stack || 0; y1 = data[i][1] + stack1; y2 = data[i+1][1] + stack2; } else{ y1 = data[i][1]; y2 = data[i+1][1]; } if(x1 <= x2 && x1 < xa.min){ if(x2 < xa.min) continue; y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.min; } else if(x2 <= x1 && x2 < xa.min){ if(x1 < xa.min) continue; y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.min; } if(x1 >= x2 && x1 > xa.max){ if(x2 > xa.max) continue; y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.max; } else if(x2 >= x1 && x2 > xa.max){ if (x1 > xa.max) continue; y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.max; } var x1PointValue = xa.d2p(x1), // Cache d2p values x2PointValue = xa.d2p(x2), yaMaxPointValue = ya.d2p(ya.max), yaMinPointValue = ya.d2p(ya.min); if(first){ ctx.moveTo(x1PointValue, ya.d2p(bottom + stack1) + offset); addStrokePath(x1PointValue, ya.d2p(bottom + stack1) + offset); first = false; } lastX = Math.max(x2, lastX); /** * Now check the case where both is outside. */ if(y1 >= ya.max && y2 >= ya.max){ ctx.lineTo(x1PointValue, yaMaxPointValue + offset); ctx.lineTo(x2PointValue, yaMaxPointValue + offset); addStrokePath(x1PointValue, yaMaxPointValue + offset); addStrokePath(x2PointValue, yaMaxPointValue + offset); continue; } else if(y1 <= ya.min && y2 <= ya.min){ ctx.lineTo(x1PointValue, yaMinPointValue + offset); ctx.lineTo(x2PointValue, yaMinPointValue + offset); addStrokePath(x1PointValue, yaMinPointValue + offset); addStrokePath(x2PointValue, yaMinPointValue + offset); continue; } /** * Else it's a bit more complicated, there might * be two rectangles and two triangles we need to fill * in; to find these keep track of the current x values. */ var x1old = x1, x2old = x2; /** * And clip the y values, without shortcutting. * Clip with ymin. */ if(y1 <= y2 && y1 < ya.min && y2 >= ya.min){ x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.min; } else if(y2 <= y1 && y2 < ya.min && y1 >= ya.min){ x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.min; } /** * Clip with ymax. */ if(y1 >= y2 && y1 > ya.max && y2 <= ya.max){ x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.max; } else if(y2 >= y1 && y2 > ya.max && y1 <= ya.max){ x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.max; } var x1NewPointValue = xa.d2p(x1), // Cache d2p values x2NewPointValue = xa.d2p(x2), y1PointValue = ya.d2p(y1), y2PointValue = ya.d2p(y2); /** * If the x value was changed we got a rectangle to fill. */ if(x1 != x1old){ top = (y1 <= ya.min) ? yaMinPointValue : yaMaxPointValue; ctx.lineTo(x1PointValue, top + offset); ctx.lineTo(x1NewPointValue, top + offset); addStrokePath(x1PointValue, top + offset); addStrokePath(x1NewPointValue, top + offset); } /** * Fill the triangles. */ ctx.lineTo(x1NewPointValue, y1PointValue + offset); ctx.lineTo(x2NewPointValue, y2PointValue + offset); addStrokePath(x1NewPointValue, y1PointValue + offset); addStrokePath(x2NewPointValue, y2PointValue + offset); /** * Fill the other rectangle if it's there. */ if(x2 != x2old){ top = (y2 <= ya.min) ? yaMinPointValue : yaMaxPointValue; ctx.lineTo(x2PointValue, top + offset); addStrokePath(x2PointValue, top + offset); } lastX = Math.max(x2, x2old, lastX); } ctx.lineTo(xa.d2p(lastX), ya.d2p(bottom) + offset); addStrokePath(xa.d2p(lastX), ya.d2p(bottom) + offset); // go back along previous stroke path var path = xa.lastStrokePath; if (series.lines.stacked) { if (path) { for(var i = path.length-1; i >= 0; --i){ ctx.lineTo(path[i][0], path[i][1] - offset/2); } } // add stroke path to series data if (saveStrokePath) { xa.lastStrokePath = strokePath; } } ctx.closePath(); ctx.fill(); }, extendYRange: function(axis){ if(axis.options.max == null || axis.options.min == null){ var newmax = axis.max, newmin = axis.min, x, i, j, s, l, stackedSumsPos = {}, stackedSumsNeg = {}, lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; l = s.lines; if (l.show && !s.hide && s.yaxis == axis) { // For stacked lines if(l.stacked){ for (j = 0; j < s.data.length; j++) { x = s.data[j][0]+''; if(s.data[j][1]>0) stackedSumsPos[x] = (stackedSumsPos[x] || 0) + s.data[j][1]; else stackedSumsNeg[x] = (stackedSumsNeg[x] || 0) + s.data[j][1]; lastSerie = s; } for (j in stackedSumsPos) { newmax = Math.max(stackedSumsPos[j], newmax); } for (j in stackedSumsNeg) { newmin = Math.min(stackedSumsNeg[j], newmin); } } } } axis.lastSerie = lastSerie; axis.max = newmax; axis.min = newmin; } } }); /** Bars **/ Flotr.addType('bars', { options: { show: false, // => setting to true will show bars, false will hide lineWidth: 2, // => in pixels barWidth: 1, // => in units of the x axis fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill horizontal: false, // => horizontal bars (x and y inverted) @todo: needs fix stacked: false, // => stacked bar charts centered: true // => center the bars to their x axis value }, /** * Draws bar series in the canvas element. * @param {Object} series - Series with options.bars.show = true. */ draw: function(series) { var ctx = this.ctx, bw = series.bars.barWidth, lw = Math.min(series.bars.lineWidth, bw); ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'miter'; /** * @todo linewidth not interpreted the right way. */ ctx.lineWidth = lw; ctx.strokeStyle = series.color; ctx.save(); this.bars.plotShadows(series, bw, 0, series.bars.fill); ctx.restore(); if(series.bars.fill){ var color = series.bars.fillColor || series.color; ctx.fillStyle = this.processColor(color, {opacity: series.bars.fillOpacity}); } this.bars.plot(series, bw, 0, series.bars.fill); ctx.restore(); }, plot: function(series, barWidth, offset, fill){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i; for(i = 0; i < data.length; i++){ var x = data[i][0], y = data[i][1], drawLeft = true, drawTop = true, drawRight = true; if (y === null) continue; // Stacked bars var stackOffsetPos = 0; var stackOffsetNeg = 0; if(series.bars.stacked) { if(series.bars.horizontal) { stackOffsetPos = ya.values[y].stackPos || 0; stackOffsetNeg = ya.values[y].stackNeg || 0; if(x > 0) { ya.values[y].stackPos = stackOffsetPos + x; } else { ya.values[y].stackNeg = stackOffsetNeg + x; } } else { stackOffsetPos = xa.values[x].stackPos || 0; stackOffsetNeg = xa.values[x].stackNeg || 0; if(y > 0) { xa.values[x].stackPos = stackOffsetPos + y; } else { xa.values[x].stackNeg = stackOffsetNeg + y; } } } // @todo: fix horizontal bars support // Horizontal bars var barOffset = series.bars.centered ? barWidth/2 : 0; if(series.bars.horizontal){ if (x > 0) var left = stackOffsetPos, right = x + stackOffsetPos; else var right = stackOffsetNeg, left = x + stackOffsetNeg; var bottom = y - barOffset, top = y + barWidth - barOffset; } else { if (y > 0) var bottom = stackOffsetPos, top = y + stackOffsetPos; else var top = stackOffsetNeg, bottom = y + stackOffsetNeg; var left = x - barOffset, right = x + barWidth - barOffset; } if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min){ left = xa.min; drawLeft = false; } if(right > xa.max){ right = xa.max; if (xa.lastSerie != series && series.bars.horizontal) drawTop = false; } if(bottom < ya.min) bottom = ya.min; if(top > ya.max){ top = ya.max; if (ya.lastSerie != series && !series.bars.horizontal) drawTop = false; } // Cache d2p values var xaLeft = xa.d2p(left), xaRight = xa.d2p(right), yaTop = ya.d2p(top), yaBottom = ya.d2p(bottom); /** * Fill the bar. */ if(fill){ ctx.fillRect(xaLeft, yaTop, xaRight - xaLeft, yaBottom - yaTop); } /** * Draw bar outline/border. * @todo Optimize this with rect method ? * @todo Can we move stroke, beginPath, closePath out of the main loop? * Not sure if rect screws this up. */ if(series.bars.lineWidth != 0 && (drawLeft || drawRight || drawTop)){ ctx.beginPath(); ctx.moveTo(xaLeft, yaBottom + offset); ctx[drawLeft ?'lineTo':'moveTo'](xaLeft, yaTop + offset); ctx[drawTop ?'lineTo':'moveTo'](xaRight, yaTop + offset); ctx[drawRight?'lineTo':'moveTo'](xaRight, yaBottom + offset); ctx.stroke(); ctx.closePath(); } } }, plotShadows: function(series, barWidth, offset){ var data = series.data; if(data.length < 1) return; var i, x, y, xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, sw = this.options.shadowSize; for(i = 0; i < data.length; i++){ x = data[i][0]; y = data[i][1]; if (y === null) continue; // Stacked bars var stackOffsetPos = 0; var stackOffsetNeg = 0; // TODO reconcile this with the same logic in Plot, maybe precalc if(series.bars.stacked) { if(series.bars.horizontal) { stackOffsetPos = ya.values[y].stackShadowPos || 0; stackOffsetNeg = ya.values[y].stackShadowNeg || 0; if(x > 0) { ya.values[y].stackShadowPos = stackOffsetPos + x; } else { ya.values[y].stackShadowNeg = stackOffsetNeg + x; } } else { stackOffsetPos = xa.values[x].stackShadowPos || 0; stackOffsetNeg = xa.values[x].stackShadowNeg || 0; if(y > 0) { xa.values[x].stackShadowPos = stackOffsetPos + y; } else { xa.values[x].stackShadowNeg = stackOffsetNeg + y; } } } // Horizontal bars var barOffset = series.bars.centered ? barWidth/2 : 0; if(series.bars.horizontal){ if (x > 0) var left = stackOffsetPos, right = x + stackOffsetPos; else var right = stackOffsetNeg, left = x + stackOffsetNeg; var bottom = y- barOffset, top = y + barWidth - barOffset; } else { if (y > 0) var bottom = stackOffsetPos, top = y + stackOffsetPos; else var top = stackOffsetNeg, bottom = y + stackOffsetNeg; var left = x - barOffset, right = x + barWidth - barOffset; } if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min) left = xa.min; if(right > xa.max) right = xa.max; if(bottom < ya.min) bottom = ya.min; if(top > ya.max) top = ya.max; var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); ctx.fillStyle = 'rgba(0,0,0,0.05)'; ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); } }, extendXRange: function(axis) { if(axis.options.max == null){ var newmin = axis.min, newmax = axis.max, i, j, x, s, b, stackedSumsPos = {}, stackedSumsNeg = {}, lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; b = s.bars; if(b.show && s.xaxis == axis) { if (b.centered && !b.horizontal) { newmax = Math.max(axis.datamax + 0.5, newmax); newmin = Math.min(axis.datamin - 0.5, newmin); } // For normal vertical bars if (!b.horizontal && (b.barWidth + axis.datamax > newmax)) newmax = axis.max + (b.centered ? b.barWidth/2 : b.barWidth); // For horizontal stacked bars if(b.stacked && b.horizontal){ for (j = 0; j < s.data.length; j++) { if (b.show && b.stacked) { y = s.data[j][1]+''; if(s.data[j][0] > 0) stackedSumsPos[y] = (stackedSumsPos[y] || 0) + s.data[j][0]; else stackedSumsNeg[y] = (stackedSumsNeg[y] || 0) + s.data[j][0]; lastSerie = s; } } for (j in stackedSumsPos) { newmax = Math.max(stackedSumsPos[j], newmax); } for (j in stackedSumsNeg) { newmin = Math.min(stackedSumsNeg[j], newmin); } } } } axis.lastSerie = lastSerie; axis.max = newmax; axis.min = newmin; } }, extendYRange: function(axis){ if(axis.options.max == null){ var newmax = axis.max, newmin = axis.min, x, i, j, s, b, stackedSumsPos = {}, stackedSumsNeg = {}, lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; b = s.bars; if (b.show && !s.hide && s.yaxis == axis) { if (b.centered && b.horizontal) { newmax = Math.max(axis.datamax + 0.5, newmax); newmin = Math.min(axis.datamin - 0.5, newmin); } // For normal horizontal bars if (b.horizontal && (b.barWidth + axis.datamax > newmax)){ newmax = axis.max + b.barWidth; } // For vertical stacked bars if(b.stacked && !b.horizontal){ for (j = 0; j < s.data.length; j++) { if (s.bars.show && s.bars.stacked) { x = s.data[j][0]+''; if(s.data[j][1] > 0) stackedSumsPos[x] = (stackedSumsPos[x] || 0) + s.data[j][1]; else stackedSumsNeg[x] = (stackedSumsNeg[x] || 0) + s.data[j][1]; lastSerie = s; } } for (j in stackedSumsPos) { newmax = Math.max(stackedSumsPos[j], newmax); } for (j in stackedSumsNeg) { newmin = Math.min(stackedSumsNeg[j], newmin); } } } } axis.lastSerie = lastSerie; axis.max = newmax; axis.min = newmin; } } }); /** Points **/ Flotr.addType('points', { options: { show: false, // => setting to true will show points, false will hide radius: 3, // => point radius (pixels) lineWidth: 2, // => line width in pixels fill: true, // => true to fill the points with a color, false for (transparent) no fill fillColor: '#FFFFFF', // => fill color fillOpacity: 0.4 // => opacity of color inside the points }, /** * Draws point series in the canvas element. * @param {Object} series - Series with options.points.show = true. */ draw: function(series) { var ctx = this.ctx, lw = series.lines.lineWidth, sw = series.shadowSize; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); if(sw > 0){ ctx.lineWidth = sw / 2; ctx.strokeStyle = 'rgba(0,0,0,0.1)'; this.points.plotShadows(series, sw/2 + ctx.lineWidth/2, series.points.radius); ctx.strokeStyle = 'rgba(0,0,0,0.2)'; this.points.plotShadows(series, ctx.lineWidth/2, series.points.radius); } ctx.lineWidth = series.points.lineWidth; ctx.strokeStyle = series.color; ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color; this.points.plot(series, series.points.radius, series.points.fill); ctx.restore(); }, plot: function (series, radius, fill) { var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i, x, data = series.data; for(i = data.length - 1; i > -1; --i){ x = data[i][0], y = data[i][1]; // To allow empty values if(y === null || x < xa.min || x > xa.max || y < ya.min || y > ya.max) continue; ctx.beginPath(); ctx.arc(xa.d2p(x), ya.d2p(y), radius, 0, 2 * Math.PI, true); if(fill) ctx.fill(); ctx.stroke(); ctx.closePath(); } }, plotShadows: function(series, offset, radius){ var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i, x, data = series.data; for(i = data.length - 1; i > -1; --i){ x = data[i][0], y = data[i][1]; if (y === null || x < xa.min || x > xa.max || y < ya.min || y > ya.max) continue; ctx.beginPath(); ctx.arc(xa.d2p(x), ya.d2p(y) + offset, radius, 0, Math.PI, false); ctx.stroke(); ctx.closePath(); } }, getHit: function(series, pos) { var xdiff, ydiff, i, d, dist, x, y, o = series.points, data = series.data, sens = series.mouse.sensibility * (o.lineWidth + o.radius), hit = { index: null, series: series, distance: Number.MAX_VALUE, x: null, y: null, precision: 1 }; for (i = data.length-1; i > -1; --i) { d = data[i]; x = series.xaxis.d2p(d[0]); y = series.yaxis.d2p(d[1]); xdiff = x - pos.relX; ydiff = y - pos.relY; dist = Math.sqrt(xdiff*xdiff + ydiff*ydiff); if (dist < sens && dist < hit.distance) { hit = { index: i, series: series, distance: dist, data: d, x: x, y: y, precision: 1 }; } } return hit; }, drawHit: function(series, index) { }, clearHit: function(series, index) { } }); /** Pie **/ /** * Formats the pies labels. * @param {Object} slice - Slice object * @return {String} Formatted pie label string */ Flotr.defaultPieLabelFormatter = function(slice) { return (slice.fraction*100).toFixed(2)+'%'; }; Flotr.addType('pie', { options: { show: false, // => setting to true will show bars, false will hide lineWidth: 1, // => in pixels fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill explode: 6, // => the number of pixels the splices will be far from the center sizeRatio: 0.6, // => the size ratio of the pie relative to the plot startAngle: Math.PI/4, // => the first slice start angle labelFormatter: Flotr.defaultPieLabelFormatter, pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective) pie3DviewAngle: (Math.PI/2 * 0.8), pie3DspliceThickness: 20 }, /** * Draws a pie in the canvas element. * @param {Object} series - Series with options.pie.show = true. */ draw: function(series) { if (this.options.pie.drawn) return; var ctx = this.ctx, options = this.options, lw = series.pie.lineWidth, sw = series.shadowSize, data = series.data, plotOffset = this.plotOffset, radius = (Math.min(this.canvasWidth, this.canvasHeight) * series.pie.sizeRatio) / 2, html = [], vScale = 1,//Math.cos(series.pie.viewAngle); plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale, style = { size: options.fontSize*1.2, color: options.grid.color, weight: 1.5 }, center = { x: plotOffset.left + (this.plotWidth)/2, y: plotOffset.top + (this.plotHeight)/2 }, // Pie portions portions = this.series.collect(function(hash, index){ if (hash.pie.show && hash.data[0][1] !== null) return { name: (hash.label || hash.data[0][1]), value: [index, hash.data[0][1]], options: hash.pie, series: hash }; }), // Sum of the portions' angles sum = portions.pluck('value').pluck(1).inject(0, function(acc, n) { return acc + n; }), fraction = 0.0, angle = series.pie.startAngle, value = 0.0; var slices = portions.collect(function(slice){ angle += fraction; value = parseFloat(slice.value[1]); // @warning : won't support null values !! fraction = value/sum; return { name: slice.name, fraction: fraction, x: slice.value[0], y: value, value: value, options: slice.options, series: slice.series, startAngle: 2 * angle * Math.PI, endAngle: 2 * (angle + fraction) * Math.PI }; }); ctx.save(); if(sw > 0){ slices.each(function (slice) { if (slice.startAngle == slice.endAngle) return; var bisection = (slice.startAngle + slice.endAngle) / 2, xOffset = center.x + Math.cos(bisection) * slice.options.explode + sw, yOffset = center.y + Math.sin(bisection) * slice.options.explode + sw; this.pie.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale); if (series.pie.fill) { ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fill(); } }, this); } if (options.HtmlText || !this.textEnabled) html = ['
']; slices.each(function (slice, index) { if (slice.startAngle == slice.endAngle) return; var bisection = (slice.startAngle + slice.endAngle) / 2, color = slice.series.color, fillColor = slice.options.fillColor || color, xOffset = center.x + Math.cos(bisection) * slice.options.explode, yOffset = center.y + Math.sin(bisection) * slice.options.explode; this.pie.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale); if(series.pie.fill){ ctx.fillStyle = this.processColor(fillColor, {opacity: series.pie.fillOpacity}); ctx.fill(); } ctx.lineWidth = lw; ctx.strokeStyle = color; ctx.stroke(); var label = options.pie.labelFormatter(slice), textAlignRight = (Math.cos(bisection) < 0), textAlignTop = (Math.sin(bisection) > 0), explodeCoeff = (slice.options.explode || series.pie.explode) + radius + 4, distX = center.x + Math.cos(bisection) * explodeCoeff, distY = center.y + Math.sin(bisection) * explodeCoeff; if (slice.fraction && label) { if (options.HtmlText || !this.textEnabled) { var yAlignDist = textAlignTop ? (distY - 5) : (this.plotHeight - distY + 5), divStyle = 'position:absolute;' + (textAlignTop ? 'top' : 'bottom') + ':' + yAlignDist + 'px;'; //@todo: change if (textAlignRight) divStyle += 'right:'+(this.canvasWidth - distX)+'px;text-align:right;'; else divStyle += 'left:'+distX+'px;text-align:left;'; html.push('
', label, '
'); } else { style.textAlign = textAlignRight ? 'right' : 'left'; style.textBaseline = textAlignTop ? 'top' : 'bottom'; Flotr.drawText(ctx, label, distX, distY, style); } } }, this); if (options.HtmlText || !this.textEnabled) { html.push('
'); this.el.insert(html.join('')); } ctx.restore(); options.pie.drawn = true; }, plotSlice: function(x, y, radius, startAngle, endAngle, fill, vScale) { var ctx = this.ctx; vScale = vScale || 1; ctx.scale(1, vScale); ctx.beginPath(); ctx.moveTo(x, y); ctx.arc (x, y, radius, startAngle, endAngle, fill); ctx.lineTo(x, y); ctx.closePath(); }, drawHit: function(n){ var octx = this.octx, s = n.series, xa = n.xaxis, ya = n.yaxis; octx.save(); octx.translate(this.plotOffset.left, this.plotOffset.top); octx.beginPath(); if (s.mouse.trackAll) { octx.moveTo(xa.d2p(n.x), ya.d2p(0)); octx.lineTo(xa.d2p(n.x), ya.d2p(n.yaxis.max)); } else { var center = { x: (this.plotWidth)/2, y: (this.plotHeight)/2 }, radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, bisection = n.sAngle setting to true will show candle sticks, false will hide lineWidth: 1, // => in pixels wickLineWidth: 1, // => in pixels candleWidth: 0.6, // => in units of the x axis fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill upFillColor: '#00A8F0',// => up sticks fill color downFillColor: '#CB4B4B',// => down sticks fill color fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill barcharts: false // => draw as barcharts (not standard bars but financial barcharts) }, /** * Draws candles series in the canvas element. * @param {Object} series - Series with options.candles.show = true. */ draw: function(series) { var ctx = this.ctx, bw = series.candles.candleWidth; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'miter'; /** * @todo linewidth not interpreted the right way. */ ctx.lineWidth = series.candles.lineWidth; this.candles.plotShadows(series, bw/2); this.candles.plot(series, bw/2); ctx.restore(); }, plot: function(series, offset){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx; for(var i = 0; i < data.length; i++){ var d = data[i], x = d[0], open = d[1], high = d[2], low = d[3], close = d[4]; var left = x - series.candles.candleWidth/2, right = x + series.candles.candleWidth/2, bottom = Math.max(ya.min, low), top = Math.min(ya.max, high), bottom2 = Math.max(ya.min, Math.min(open, close)), top2 = Math.min(ya.max, Math.max(open, close)); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; var color = series.candles[open>close?'downFillColor':'upFillColor']; /** * Fill the candle. */ if(series.candles.fill && !series.candles.barcharts){ ctx.fillStyle = this.processColor(color, {opacity: series.candles.fillOpacity}); ctx.fillRect(xa.d2p(left), ya.d2p(top2) + offset, xa.d2p(right) - xa.d2p(left), ya.d2p(bottom2) - ya.d2p(top2)); } /** * Draw candle outline/border, high, low. */ if(series.candles.lineWidth || series.candles.wickLineWidth){ var x, y, pixelOffset = (series.candles.wickLineWidth % 2) / 2; x = Math.floor(xa.d2p((left + right) / 2)) + pixelOffset; ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = series.candles.wickLineWidth; ctx.lineCap = 'butt'; if (series.candles.barcharts) { ctx.beginPath(); ctx.moveTo(x, Math.floor(ya.d2p(top) + offset)); ctx.lineTo(x, Math.floor(ya.d2p(bottom) + offset)); y = Math.floor(ya.d2p(open) + offset)+0.5; ctx.moveTo(Math.floor(xa.d2p(left))+pixelOffset, y); ctx.lineTo(x, y); y = Math.floor(ya.d2p(close) + offset)+0.5; ctx.moveTo(Math.floor(xa.d2p(right))+pixelOffset, y); ctx.lineTo(x, y); } else { ctx.strokeRect(xa.d2p(left), ya.d2p(top2) + offset, xa.d2p(right) - xa.d2p(left), ya.d2p(bottom2) - ya.d2p(top2)); ctx.beginPath(); ctx.moveTo(x, Math.floor(ya.d2p(top2 ) + offset)); ctx.lineTo(x, Math.floor(ya.d2p(top ) + offset)); ctx.moveTo(x, Math.floor(ya.d2p(bottom2) + offset)); ctx.lineTo(x, Math.floor(ya.d2p(bottom ) + offset)); } ctx.stroke(); ctx.closePath(); ctx.restore(); } } }, plotShadows: function(series, offset){ var data = series.data; if(data.length < 1 || series.candles.barcharts) return; var xa = series.xaxis, ya = series.yaxis, sw = this.options.shadowSize; for(var i = 0; i < data.length; i++){ var d = data[i], x = d[0], open = d[1], high = d[2], low = d[3], close = d[4]; var left = x - series.candles.candleWidth/2, right = x + series.candles.candleWidth/2, bottom = Math.max(ya.min, Math.min(open, close)), top = Math.min(ya.max, Math.max(open, close)); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); var height = Math.max(0, ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw)); this.ctx.fillStyle = 'rgba(0,0,0,0.05)'; this.ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotWidth), width, height); } }, extendXRange: function(axis){ if(axis.options.max == null){ var newmin = axis.min, newmax = axis.max, i, c; for(i = 0; i < this.series.length; ++i){ c = this.series[i].candles; if(c.show && this.series[i].xaxis == axis) { // We don't use c.candleWidth in order not to stick the borders newmax = Math.max(axis.datamax + 0.5, newmax); newmin = Math.min(axis.datamin - 0.5, newmin); } } axis.max = newmax; axis.min = newmin; } } }); /** Markers **/ /** * Formats the marker labels. * @param {Object} obj - Marker value Object {x:..,y:..} * @return {String} Formatted marker string */ Flotr.defaultMarkerFormatter = function(obj){ return (Math.round(obj.y*100)/100)+''; }; Flotr.addType('markers', { options: { show: false, // => setting to true will show markers, false will hide lineWidth: 1, // => line width of the rectangle around the marker fill: false, // => fill or not the marekers' rectangles fillColor: "#FFFFFF", // => fill color fillOpacity: 0.4, // => fill opacity stroke: false, // => draw the rectangle around the markers position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r) labelFormatter: Flotr.defaultMarkerFormatter, fontSize: Flotr.defaultOptions.fontSize, stacked: false, // => true if markers should be stacked stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details) horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly) }, /** * Draws lines series in the canvas element. * @param {Object} series - Series with options.lines.show = true. */ draw: function(series){ series = series || this.series; var ctx = this.ctx, xa = series.xaxis, ya = series.yaxis, options = series.markers, data = series.data; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'round'; ctx.lineWidth = options.lineWidth; ctx.strokeStyle = 'rgba(0,0,0,0.5)'; ctx.fillStyle = this.processColor(options.fillColor, {opacity: options.fillOpacity}); for(var i = 0; i < data.length; ++i){ var x = data[i][0], y = data[i][1], label; if(series.markers.stacked) { if(series.markers.stackingType == 'b'){ // Stacked bars var stackOffsetPos = 0, stackOffsetNeg = 0; if(series.markers.horizontal) { stackOffsetPos = ya.values[y].stackMarkPos || 0; stackOffsetNeg = ya.values[y].stackMarkNeg || 0; if(x > 0) { ya.values[y].stackMarkPos = stackOffsetPos + x; x = stackOffsetPos + x; } else { ya.values[y].stackMarkNeg = stackOffsetNeg + x; x = stackOffsetNeg + x; } } else { stackOffsetPos = xa.values[x].stackMarkPos || 0; stackOffsetNeg = xa.values[x].stackMarkNeg || 0; if(y > 0) { xa.values[x].stackMarkPos = stackOffsetPos + y; y = stackOffsetPos + y; } else { xa.values[x].stackMarkNeg = stackOffsetNeg + y; y = stackOffsetNeg + y; } } } else if(series.markers.stackingType == 'a') { var stackOffset = xa.values[x].stackMark || 0; xa.values[x].stackMark = stackOffset + y; y = stackOffset + y; } } var xPos = xa.d2p(x), yPos = ya.d2p(y); label = options.labelFormatter({x: x, y: y, index: i, data : data}); this.markers.plot(xPos, yPos, label, options); } ctx.restore(); }, plot: function(x, y, label, options) { var ctx = this.ctx, dim = this.getTextDimensions(label, null, null), margin = 2, left = x, top = y; dim.width = Math.floor(dim.width+margin*2); dim.height = Math.floor(dim.height+margin*2); if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin; else if (options.position.indexOf('l') != -1) left -= dim.width; if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin; else if (options.position.indexOf('t') != -1) top -= dim.height; left = Math.floor(left)+0.5; top = Math.floor(top)+0.5; if(options.fill) ctx.fillRect(left, top, dim.width, dim.height); if(options.stroke) ctx.strokeRect(left, top, dim.width, dim.height); Flotr.drawText(ctx, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize}); } }); Flotr.addType('radar', { options: { show: false, // => setting to true will show radar chart, false will hide lineWidth: 2, // => line width in pixels fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill radiusRatio: 0.90 // => ratio of the radar, against the plot size }, draw: function(series){ var ctx = this.ctx, options = this.options; ctx.save(); ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2); ctx.lineWidth = series.radar.lineWidth; ctx.fillStyle = 'rgba(0,0,0,0.05)'; ctx.strokeStyle = 'rgba(0,0,0,0.05)'; this.radar.plot(series, series.shadowSize / 2); ctx.strokeStyle = 'rgba(0,0,0,0.1)'; this.radar.plot(series, series.shadowSize / 4); ctx.strokeStyle = series.color; ctx.fillStyle = this.processColor(series.color, {opacity: series.radar.fillOpacity}); this.radar.plot(series); ctx.restore(); }, plot: function(series, offset){ var ctx = this.ctx, options = this.options, data = series.data, radius = Math.min(this.plotHeight, this.plotWidth)*options.radar.radiusRatio/2, coeff = 2*(Math.PI/data.length), angle = -Math.PI/2; offset = offset || 0; ctx.beginPath(); for(var i = 0; i < data.length; ++i){ var x = data[i][0], y = data[i][1], ratio = y / this.axes.y.max; ctx[i == 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius*ratio + offset, Math.sin(i*coeff+angle)*radius*ratio + offset); } ctx.closePath(); if (series.radar.fill) ctx.fill(); ctx.stroke(); } }); Flotr.addType('bubbles', { options: { show: false, // => setting to true will show radar chart, false will hide lineWidth: 2, // => line width in pixels fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill baseRadius: 2 // => ratio of the radar, against the plot size }, draw: function(series){ var ctx = this.ctx, options = this.options; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineWidth = series.bubbles.lineWidth; ctx.fillStyle = 'rgba(0,0,0,0.05)'; ctx.strokeStyle = 'rgba(0,0,0,0.05)'; this.bubbles.plot(series, series.shadowSize / 2); ctx.strokeStyle = 'rgba(0,0,0,0.1)'; this.bubbles.plot(series, series.shadowSize / 4); ctx.strokeStyle = series.color; ctx.fillStyle = this.processColor(series.color, {opacity: series.radar.fillOpacity}); this.bubbles.plot(series); ctx.restore(); }, plot: function(series, offset){ var ctx = this.ctx, options = this.options, data = series.data, radius = options.bubbles.baseRadius; offset = offset || 0; for(var i = 0; i < data.length; ++i){ var x = data[i][0], y = data[i][1], z = data[i][2]; ctx.beginPath(); ctx.arc(series.xaxis.d2p(x) + offset, series.yaxis.d2p(y) + offset, radius * z, 0, Math.PI*2, true); ctx.stroke(); if (series.bubbles.fill) ctx.fill(); ctx.closePath(); } }, drawHit: function(n){ var octx = this.octx, s = n.series, xa = n.xaxis, ya = n.yaxis, z = s.data[0][2], r = this.options.bubbles.baseRadius; octx.save(); octx.lineWidth = s.points.lineWidth; octx.strokeStyle = s.mouse.lineColor; octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); octx.translate(this.plotOffset.left, this.plotOffset.top); octx.beginPath(); octx.arc(xa.d2p(n.x), ya.d2p(n.y), z*r, 0, 2 * Math.PI, true); octx.fill(); octx.stroke(); octx.closePath(); octx.restore(); }, clearHit: function(){ var prevHit = this.prevHit, plotOffset = this.plotOffset, s = prevHit.series, lw = s.bars.lineWidth, xa = prevHit.xaxis, ya = prevHit.yaxis, z = s.data[0][2], r = this.options.bubbles.baseRadius, offset = z*r+lw; this.octx.clearRect( plotOffset.left + xa.d2p(prevHit.x) - offset, plotOffset.top + ya.d2p(prevHit.y) - offset, offset*2, offset*2 ); } /*, extendXRange: function(axis){ if(axis.options.max == null){ var newmin = axis.min, newmax = axis.max, i, j, c, r, data, d; for(i = 0; i < this.series.length; ++i){ c = this.series[i].bubbles; if(c.show && this.series[i].xaxis == axis) { data = this.series[i].data; if (data) for(j = 0; j < data.length; j++) { d = data[j]; r = d[2] * c.baseRadius * (this.plotWidth / (axis.datamax - axis.datamin)); newmax = Math.max(d[0] + r, newmax); newmin = Math.min(d[0] - r, newmin); } } } axis.max = newmax; axis.min = newmin; } }, extendYRange: function(axis){ if(axis.options.max == null){ var newmin = axis.min, newmax = axis.max, i, j, c, r, data, d; for(i = 0; i < this.series.length; ++i){ c = this.series[i].bubbles; if(c.show && this.series[i].yaxis == axis) { data = this.series[i].data; if (data) for(j = 0; j < data.length; j++) { d = data[j]; r = d[2] * c.baseRadius; newmax = Math.max(d[1] + r, newmax); newmin = Math.min(d[1] - r, newmin); } } } axis.max = newmax; axis.min = newmin; } }*/ }); Flotr.addPlugin('spreadsheet', { options: { show: false, // => show the data grid using two tabs tabGraphLabel: 'Graph', tabDataLabel: 'Data', toolbarDownload: 'Download CSV', // @todo: add better language support toolbarSelectAll: 'Select all', csvFileSeparator: ',', decimalSeparator: '.', tickFormatter: null }, /** * Builds the tabs in the DOM */ callbacks: { 'flotr:afterconstruct': function(){ this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove'); if (!this.options.spreadsheet.show) return; var ss = this.spreadsheet; ss.tabsContainer = new Element('div', {style:'position:absolute;left:0px;width:'+this.canvasWidth+'px'}).addClassName('flotr-tabs-group'); ss.tabs = { graph: new Element('div', {style:'float:left'}).addClassName('flotr-tab selected').update(this.options.spreadsheet.tabGraphLabel), data: new Element('div', {style:'float:left'}).addClassName('flotr-tab').update(this.options.spreadsheet.tabDataLabel) }; ss.tabsContainer.insert(ss.tabs.graph).insert(ss.tabs.data); this.el.insert({bottom: ss.tabsContainer}); var offset = ss.tabs.data.getHeight() + 2; this.plotOffset.bottom += offset; ss.tabsContainer.setStyle({top: this.canvasHeight-offset+'px'}); ss.tabs.graph.observe('click', function(){ss.showTab('graph')}); ss.tabs.data.observe('click', function(){ss.showTab('data')}); } }, /** * Constructs the data table for the spreadsheet * @todo make a spreadsheet manager (Flotr.Spreadsheet) * @return {Element} The resulting table element */ constructDataGrid: function(){ // If the data grid has already been built, nothing to do here if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid; var i, j, s = this.series, datagrid = this.loadDataGrid(), t = this.spreadsheet.datagrid = new Element('table').addClassName('flotr-datagrid'), colgroup = ['']; // First row : series' labels var html = ['']; html.push(' '); for (i = 0; i < s.length; ++i) { html.push(''+(s[i].label || String.fromCharCode(65+i))+''); colgroup.push(''); } html.push(''); // Data rows for (j = 0; j < datagrid.length; ++j) { html.push(''); for (i = 0; i < s.length+1; ++i) { var tag = 'td', content = (datagrid[j][i] != null ? Math.round(datagrid[j][i]*100000)/100000 : ''); if (i == 0) { tag = 'th'; var label; if(this.options.xaxis.ticks) { var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == datagrid[j][i] }); if (tick) label = tick[1]; } else if (this.options.spreadsheet.tickFormatter){ label = this.options.spreadsheet.tickFormatter(content); } else { label = this.options.xaxis.tickFormatter(content); } if (label) content = label; } html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''); } html.push(''); } colgroup.push(''); t.update(colgroup.join('')+html.join('')); if (!Prototype.Browser.IE || Flotr.isIE9) { t.select('td').each(function(td) { td.observe('mouseover', function(e){ td = e.element(); var siblings = td.previousSiblings(); t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); t.select('colgroup col')[siblings.length].addClassName('hover'); }).observe('mouseout', function(){ t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover'); }); }); } var toolbar = new Element('div').addClassName('flotr-datagrid-toolbar'). insert( new Element('button', {type:'button'}) .addClassName('flotr-datagrid-toolbar-button') .update(this.options.spreadsheet.toolbarDownload) .observe('click', this.spreadsheet.downloadCSV.bindAsEventListener(this)) ). insert( new Element('button', {type:'button'}) .addClassName('flotr-datagrid-toolbar-button') .update(this.options.spreadsheet.toolbarSelectAll) .observe('click', this.spreadsheet.selectAllData.bindAsEventListener(this)) ); var container = new Element('div', { style: 'left:0px;top:0px;width:'+this.canvasWidth+'px;height:'+ (this.canvasHeight-this.spreadsheet.tabsContainer.getHeight()-2)+'px;overflow:auto;' }).addClassName('flotr-datagrid-container'); container.insert(toolbar); t.wrap(container.hide()); this.el.insert(container); return t; }, /** * Shows the specified tab, by its name * @todo make a tab manager (Flotr.Tabs) * @param {String} tabName - The tab name */ showTab: function(tabName){ var selector = 'canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle'; switch(tabName) { case 'graph': if (this.spreadsheet.datagrid) this.spreadsheet.datagrid.up().hide(); this.el.select(selector).invoke('show'); this.spreadsheet.tabs.data.removeClassName('selected'); this.spreadsheet.tabs.graph.addClassName('selected'); break; case 'data': this.spreadsheet.constructDataGrid(); this.spreadsheet.datagrid.up().show(); this.el.select(selector).invoke('hide'); this.spreadsheet.tabs.data.addClassName('selected'); this.spreadsheet.tabs.graph.removeClassName('selected'); break; } }, /** * Selects the data table in the DOM for copy/paste */ selectAllData: function(){ if (this.spreadsheet.tabs) { var selection, range, doc, win, node = this.spreadsheet.constructDataGrid(); this.spreadsheet.showTab('data'); // deferred to be able to select the table setTimeout(function () { if ((doc = node.ownerDocument) && (win = doc.defaultView) && win.getSelection && doc.createRange && (selection = window.getSelection()) && selection.removeAllRanges) { range = doc.createRange(); range.selectNode(node); selection.removeAllRanges(); selection.addRange(range); } else if (document.body && document.body.createTextRange && (range = document.body.createTextRange())) { range.moveToElementText(node); range.select(); } }, 0); return true; } else return false; }, /** * Converts the data into CSV in order to download a file */ downloadCSV: function(){ var i, csv = '', series = this.series, options = this.options, dg = this.loadDataGrid(), separator = encodeURIComponent(options.spreadsheet.csvFileSeparator); if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) { throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")"; } // The first row for (i = 0; i < series.length; ++i) { csv += separator+'"'+(series[i].label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"'; } csv += "%0D%0A"; // \r\n // For each row for (i = 0; i < dg.length; ++i) { var rowLabel = ''; // The first column if (options.xaxis.ticks) { var tick = options.xaxis.ticks.find(function(x){return x[0] == dg[i][0]}); if (tick) rowLabel = tick[1]; } else if (options.spreadsheet.tickFormatter){ rowLabel = options.spreadsheet.tickFormatter(dg[i][0]); } else { rowLabel = options.xaxis.tickFormatter(dg[i][0]); } rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"'; var numbers = dg[i].slice(1).join(separator); if (options.spreadsheet.decimalSeparator !== '.') { numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator); } csv += rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n } if (Prototype.Browser.IE && !Flotr.isIE9) { csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r'); window.open().document.write(csv); } else window.open('data:text/csv,'+csv); } }); /** Gantt * Base on data in form [s,y,d] where: * y - executor or simply y value * s - task start value * d - task duration * **/ Flotr.addType('gantt', { options: { show: false, // => setting to true will show gantt, false will hide lineWidth: 2, // => in pixels barWidth: 1, // => in units of the x axis fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill centered: true // => center the bars to their x axis value }, /** * Draws gantt series in the canvas element. * @param {Object} series - Series with options.gantt.show = true. */ draw: function(series) { var ctx = this.ctx, bw = series.gantt.barWidth, lw = Math.min(series.gantt.lineWidth, bw); ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'miter'; /** * @todo linewidth not interpreted the right way. */ ctx.lineWidth = lw; ctx.strokeStyle = series.color; ctx.save(); this.gantt.plotShadows(series, bw, 0, series.gantt.fill); ctx.restore(); if(series.gantt.fill){ var color = series.gantt.fillColor || series.color; ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity}); } this.gantt.plot(series, bw, 0, series.gantt.fill); ctx.restore(); }, plot: function(series, barWidth, offset, fill){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i; for(i = 0; i < data.length; i++){ var y = data[i][0], s = data[i][1], d = data[i][2], drawLeft = true, drawTop = true, drawRight = true; if (s === null || d === null) continue; var left = s, right = s + d, bottom = y - (series.gantt.centered ? barWidth/2 : 0), top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min){ left = xa.min; drawLeft = false; } if(right > xa.max){ right = xa.max; if (xa.lastSerie != series) drawTop = false; } if(bottom < ya.min) bottom = ya.min; if(top > ya.max){ top = ya.max; if (ya.lastSerie != series) drawTop = false; } /** * Fill the bar. */ if(fill){ ctx.beginPath(); ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset); ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset); ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset); ctx.fill(); ctx.closePath(); } /** * Draw bar outline/border. */ if(series.gantt.lineWidth != 0 && (drawLeft || drawRight || drawTop)){ ctx.beginPath(); ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset); ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset); ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset); ctx.stroke(); ctx.closePath(); } } }, plotShadows: function(series, barWidth, offset){ var data = series.data; if(data.length < 1) return; var i, y, s, d, xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, sw = this.options.shadowSize; for(i = 0; i < data.length; i++){ y = data[i][0]; s = data[i][1]; d = data[i][2]; if (s === null || d === null) continue; var left = s, right = s + d, bottom = y - (series.gantt.centered ? barWidth/2 : 0), top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min) left = xa.min; if(right > xa.max) right = xa.max; if(bottom < ya.min) bottom = ya.min; if(top > ya.max) top = ya.max; var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); ctx.fillStyle = 'rgba(0,0,0,0.05)'; ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); } }, extendXRange: function(axis) { if(axis.options.max == null){ var newmin = axis.min, newmax = axis.max, i, j, x, s, g, stackedSumsPos = {}, stackedSumsNeg = {}, lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; g = s.gantt; if(g.show && s.xaxis == axis) { for (j = 0; j < s.data.length; j++) { if (g.show) { y = s.data[j][0]+''; stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]); lastSerie = s; } } for (j in stackedSumsPos) { newmax = Math.max(stackedSumsPos[j], newmax); } } } axis.lastSerie = lastSerie; axis.max = newmax; axis.min = newmin; } }, extendYRange: function(axis){ if(axis.options.max == null){ var newmax = Number.MIN_VALUE, newmin = Number.MAX_VALUE, i, j, s, g, stackedSumsPos = {}, stackedSumsNeg = {}, lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; g = s.gantt; if (g.show && !s.hide && s.yaxis == axis) { var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE; for(j=0; j < s.data.length; j++){ datamax = Math.max(datamax,s.data[j][0]); datamin = Math.min(datamin,s.data[j][0]); } if (g.centered) { newmax = Math.max(datamax + 0.5, newmax); newmin = Math.min(datamin - 0.5, newmin); } else { newmax = Math.max(datamax + 1, newmax); newmin = Math.min(datamin, newmin); } // For normal horizontal bars if (g.barWidth + datamax > newmax){ newmax = axis.max + g.barWidth; } } } axis.lastSerie = lastSerie; axis.max = newmax; axis.min = newmin; axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals); } } }); flotr-0.2.1~r301/base64.js0000644000175000017500000000564111640000652014062 0ustar segresegre/* Copyright (C) 1999 Masanao Izumo * Version: 1.0 * LastModified: Dec 25 1999 * This library is free. You can redistribute it and/or modify it. */ /* * Interfaces: * b64 = base64encode(data); * data = base64decode(b64); */ (function() { var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var base64DecodeChars = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; function base64encode(str) { var out, i, len; var c1, c2, c3; len = str.length; i = 0; out = ""; while(i < len) { c1 = str.charCodeAt(i++) & 0xff; if(i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt((c1 & 0x3) << 4); out += "=="; break; } c2 = str.charCodeAt(i++); if(i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt((c2 & 0xF) << 2); out += "="; break; } c3 = str.charCodeAt(i++); out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); out += base64EncodeChars.charAt(c3 & 0x3F); } return out; } function base64decode(str) { var c1, c2, c3, c4; var i, len, out; len = str.length; i = 0; out = ""; while(i < len) { /* c1 */ do { c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; } while(i < len && c1 == -1); if(c1 == -1) break; /* c2 */ do { c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; } while(i < len && c2 == -1); if(c2 == -1) break; out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); /* c3 */ do { c3 = str.charCodeAt(i++) & 0xff; if(c3 == 61) return out; c3 = base64DecodeChars[c3]; } while(i < len && c3 == -1); if(c3 == -1) break; out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); /* c4 */ do { c4 = str.charCodeAt(i++) & 0xff; if(c4 == 61) return out; c4 = base64DecodeChars[c4]; } while(i < len && c4 == -1); if(c4 == -1) break; out += String.fromCharCode(((c3 & 0x03) << 6) | c4); } return out; } if (!window.btoa) window.btoa = base64encode; if (!window.atob) window.atob = base64decode; })();flotr-0.2.1~r301/canvas2image.js0000644000175000017500000001454411640000652015340 0ustar segresegre/* * Canvas2Image v0.1 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com * MIT License [http://www.opensource.org/licenses/mit-license.php] */ var Canvas2Image = (function() { // check if we have canvas support var oCanvas = document.createElement("canvas"), sc = String.fromCharCode, strDownloadMime = "image/octet-stream", bReplaceDownloadMime = false; // no canvas, bail out. if (!oCanvas.getContext) { return { saveAsBMP : function(){}, saveAsPNG : function(){}, saveAsJPEG : function(){} } } var bHasImageData = !!(oCanvas.getContext("2d").getImageData), bHasDataURL = !!(oCanvas.toDataURL), bHasBase64 = !!(window.btoa); // ok, we're good var readCanvasData = function(oCanvas) { var iWidth = parseInt(oCanvas.width), iHeight = parseInt(oCanvas.height); return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight); } // base64 encodes either a string or an array of charcodes var encodeData = function(data) { var i, aData, strData = ""; if (typeof data == "string") { strData = data; } else { aData = data; for (i = 0; i < aData.length; i++) { strData += sc(aData[i]); } } return btoa(strData); } // creates a base64 encoded string containing BMP data takes an imagedata object as argument var createBMP = function(oData) { var strHeader = '', iWidth = oData.width, iHeight = oData.height; strHeader += 'BM'; var iFileSize = iWidth*iHeight*4 + 54; // total header size = 54 bytes strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); strHeader += sc(iFileSize % 256); strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset strHeader += sc(40, 0, 0, 0); // info header size var iImageWidth = iWidth; strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); strHeader += sc(iImageWidth % 256); var iImageHeight = iHeight; strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); strHeader += sc(iImageHeight % 256); strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel strHeader += sc(0, 0, 0, 0); // compression = none var iDataSize = iWidth*iHeight*4; strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); strHeader += sc(iDataSize % 256); strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used var aImgData = oData.data, strPixelData = "", c, x, y = iHeight, iOffsetX, iOffsetY, strPixelRow; do { iOffsetY = iWidth*(y-1)*4; strPixelRow = ""; for (x = 0; x < iWidth; x++) { iOffsetX = 4*x; strPixelRow += sc( aImgData[iOffsetY + iOffsetX + 2], // B aImgData[iOffsetY + iOffsetX + 1], // G aImgData[iOffsetY + iOffsetX], // R aImgData[iOffsetY + iOffsetX + 3] // A ); } strPixelData += strPixelRow; } while (--y); return encodeData(strHeader + strPixelData); } // sends the generated file to the client var saveFile = function(strData) { if (!window.open(strData)) { document.location.href = strData; } } var makeDataURI = function(strData, strMime) { return "data:" + strMime + ";base64," + strData; } // generates a object containing the imagedata var makeImageObject = function(strSource) { var oImgElement = document.createElement("img"); oImgElement.src = strSource; return oImgElement; } var scaleCanvas = function(oCanvas, iWidth, iHeight) { if (iWidth && iHeight) { var oSaveCanvas = document.createElement("canvas"); oSaveCanvas.width = iWidth; oSaveCanvas.height = iHeight; oSaveCanvas.style.width = iWidth+"px"; oSaveCanvas.style.height = iHeight+"px"; var oSaveCtx = oSaveCanvas.getContext("2d"); oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth); return oSaveCanvas; } return oCanvas; } return { saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!bHasDataURL) return false; var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), strMime = "image/png", strData = oScaledCanvas.toDataURL(strMime); if (bReturnImg) { return makeImageObject(strData); } else { saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); } return true; }, saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!bHasDataURL) return false; var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), strMime = "image/jpeg", strData = oScaledCanvas.toDataURL(strMime); // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false if (strData.indexOf(strMime) != 5) return false; if (bReturnImg) { return makeImageObject(strData); } else { saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); } return true; }, saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!(bHasDataURL && bHasImageData && bHasBase64)) return false; var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), strMime = "image/bmp", oData = readCanvasData(oScaledCanvas), strImgData = createBMP(oData); if (bReturnImg) { return makeImageObject(makeDataURI(strImgData, strMime)); } else { saveFile(makeDataURI(strImgData, strMime)); } return true; } }; })();flotr-0.2.1~r301/canvastext.js0000644000175000017500000005410211640000652015152 0ustar segresegre/** * This code is released to the public domain by Jim Studt, 2007. * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/ * It as been modified by Fabien Mnager to handle font style like size, weight, color and rotation. * A partial support for special characters has been added too. */ var CanvasText = { /** The letters definition. It is a list of letters, * with their width, and the coordinates of points compositing them. * The syntax for the points is : [x, y], null value means "pen up" */ letters: { '\n':{ width: -1, points: [] }, ' ': { width: 10, points: [] }, '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] }, '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] }, '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] }, '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] }, '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] }, '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] }, ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] }, '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] }, '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] }, ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, '-': { width: 26, points: [[4,9],[22,9]] }, '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] }, '/': { width: 22, points: [[20,25],[2,-7]] }, '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] }, '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] }, '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] }, '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] }, '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] }, '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] }, '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] }, '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] }, ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, '<': { width: 24, points: [[20,18],[4,9],[20,0]] }, '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] }, '>': { width: 24, points: [[4,18],[20,9],[4,0]] }, '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] }, '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] }, 'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] }, 'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] }, 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] }, 'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] }, 'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] }, 'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] }, 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] }, 'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] }, 'I': { width: 8, points: [[4,21],[4,0]] }, 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] }, 'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] }, 'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] }, 'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] }, 'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] }, 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] }, 'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] }, 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] }, 'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] }, 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, 'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] }, 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] }, 'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] }, 'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] }, 'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] }, 'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] }, 'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] }, '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] }, '\\':{ width: 14, points: [[0,21],[14,-3]] }, ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] }, '^': { width: 14, points: [[3,10],[8,18],[13,10]] }, '_': { width: 16, points: [[0,-2],[16,-2]] }, '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] }, 'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] }, 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] }, 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] }, 'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] }, 'l': { width: 8, points: [[4,21],[4,0]] }, 'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] }, 'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] }, 'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, 'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] }, 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] }, 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] }, 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] }, 'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] }, 'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] }, 'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] }, 'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] }, 'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] }, '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] }, '|': { width: 8, points: [[4,25],[4,-7]] }, '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] }, '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }, // Lower case Latin-1 '': { diacritic: '`', letter: 'a' }, '': { diacritic: '', letter: 'a' }, '': { diacritic: '^', letter: 'a' }, '': { diacritic: '', letter: 'a' }, '': { diacritic: '~', letter: 'a' }, '': { diacritic: '`', letter: 'e' }, '': { diacritic: '', letter: 'e' }, '': { diacritic: '^', letter: 'e' }, '': { diacritic: '', letter: 'e' }, '': { diacritic: '`', letter: 'i' }, '': { diacritic: '', letter: 'i' }, '': { diacritic: '^', letter: 'i' }, '': { diacritic: '', letter: 'i' }, '': { diacritic: '`', letter: 'o' }, '': { diacritic: '', letter: 'o' }, '': { diacritic: '^', letter: 'o' }, '': { diacritic: '', letter: 'o' }, '': { diacritic: '~', letter: 'o' }, '': { diacritic: '`', letter: 'u' }, '': { diacritic: '', letter: 'u' }, '': { diacritic: '^', letter: 'u' }, '': { diacritic: '', letter: 'u' }, '': { diacritic: '', letter: 'y' }, '': { diacritic: '', letter: 'y' }, '': { diacritic: '', letter: 'c' }, '': { diacritic: '~', letter: 'n' }, // Upper case Latin-1 '': { diacritic: '`', letter: 'A' }, '': { diacritic: '', letter: 'A' }, '': { diacritic: '^', letter: 'A' }, '': { diacritic: '', letter: 'A' }, '': { diacritic: '~', letter: 'A' }, '': { diacritic: '`', letter: 'E' }, '': { diacritic: '', letter: 'E' }, '': { diacritic: '^', letter: 'E' }, '': { diacritic: '', letter: 'E' }, '': { diacritic: '`', letter: 'I' }, '': { diacritic: '', letter: 'I' }, '': { diacritic: '^', letter: 'I' }, '': { diacritic: '', letter: 'I' }, '': { diacritic: '`', letter: 'O' }, '': { diacritic: '', letter: 'O' }, '': { diacritic: '^', letter: 'O' }, '': { diacritic: '', letter: 'O' }, '': { diacritic: '~', letter: 'O' }, '': { diacritic: '`', letter: 'U' }, '': { diacritic: '', letter: 'U' }, '': { diacritic: '^', letter: 'U' }, '': { diacritic: '', letter: 'U' }, '': { diacritic: '', letter: 'Y' }, '': { diacritic: '', letter: 'C' }, '': { diacritic: '~', letter: 'N' } }, specialchars: { 'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] } }, /** Diacritics, used to draw accentuated letters */ diacritics: { '': { entity: 'cedil', points: [[6,-4],[4,-6],[2,-7],[1,-7]] }, '': { entity: 'acute', points: [[8,19],[13,22]] }, '`': { entity: 'grave', points: [[7,22],[12,19]] }, '^': { entity: 'circ', points: [[5.5,19],[9.5,23],[12.5,19]] }, '': { entity: 'trema', points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]] }, '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] } }, /** The default font styling */ style: { size: 8, // font height in pixels font: null, // not yet implemented color: '#000000', // font color weight: 1, // float, 1 for 'normal' textAlign: 'left', // left, right, center textBaseline: 'bottom', // top, middle, bottom adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position angle: 0, // in radians, anticlockwise tracking: 1, // space between the letters, float, 1 for 'normal' boundingBoxColor: '#ff0000', // color of the bounding box (null to hide), can be used for debug and font drawing originPointColor: '#000000' // color of the bounding box (null to hide), can be used for debug and font drawing }, debug: false, _bufferLexemes: {}, extend: function(dest, src) { for (var property in src) { if (property in dest) continue; dest[property] = src[property]; } return dest; }, /** Get the letter data corresponding to a char * @param {String} ch - The char */ letter: function(ch) { return CanvasText.letters[ch]; }, parseLexemes: function(str) { if (CanvasText._bufferLexemes[str]) return CanvasText._bufferLexemes[str]; var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g), result = [], chars = []; for (i = 0; i < matches.length; i++) { c = matches[i]; if (c.length == 1) chars.push(c); else { var entity = c.substring(1, c.length-1); if (CanvasText.specialchars[entity]) chars.push(entity); else chars = chars.concat(c.toArray()); } } for (i = 0; i < chars.length; i++) { c = chars[i]; if (c = CanvasText.letters[c] || CanvasText.specialchars[c]) result.push(c); } for (i = 0; i < result.length; i++) { if (result === null || typeof result === 'undefined') delete result[i]; } return CanvasText._bufferLexemes[str] = result; }, /** Get the font ascent for a given style * @param {Object} style - The reference style */ ascent: function(style) { style = style || CanvasText.style; return (style.size || CanvasText.style.size); }, /** Get the font descent for a given style * @param {Object} style - The reference style * */ descent: function(style) { style = style || CanvasText.style; return 7.0*(style.size || CanvasText.style.size)/25.0; }, /** Measure the text horizontal size * @param {String} str - The text * @param {Object} style - Text style * */ measure: function(str, style) { if (!str) return; style = style || CanvasText.style; var i, width, lexemes = CanvasText.parseLexemes(str), total = 0; for (i = lexemes.length-1; i > -1; --i) { c = lexemes[i]; width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width; total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0; } return total; }, getDimensions: function(str, style) { style = style || CanvasText.style; var width = CanvasText.measure(str, style), height = style.size || CanvasText.style.size, angle = style.angle || CanvasText.style.angle; if (style.angle == 0) return {width: width, height: height}; return { width: Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height), height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height) } }, /** Draws serie of points at given coordinates * @param {Canvas context} ctx - The canvas context * @param {Array} points - The points to draw * @param {Number} x - The X coordinate * @param {Number} y - The Y coordinate * @param {Number} mag - The scale */ drawPoints: function (ctx, points, x, y, mag, offset) { var i, a, penUp = true, needStroke = 0; offset = offset || {x:0, y:0}; ctx.beginPath(); for (i = 0; i < points.length; i++) { a = points[i]; if (!a) { penUp = true; continue; } if (penUp) { ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); penUp = false; } else { ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); } } ctx.stroke(); ctx.closePath(); }, /** Draws a text at given coordinates and with a given style * @param {String} str - The text to draw * @param {Number} xOrig - The X coordinate * @param {Number} yOrig - The Y coordinate * @param {Object} style - The font style */ draw: function(str, xOrig, yOrig, style) { if (!str) return; CanvasText.extend(style, CanvasText.style); var i, c, total = 0, mag = style.size / 25.0, x = 0, y = 0, lexemes = CanvasText.parseLexemes(str), offset = {x: 0, y: 0}, measure = CanvasText.measure(str, style), align; if (style.adjustAlign) { align = CanvasText.getBestAlign(style.angle, style); CanvasText.extend(style, align); } switch (style.textAlign) { case 'left': break; case 'center': offset.x = -measure / 2; break; case 'right': offset.x = -measure; break; } switch (style.textBaseline) { case 'bottom': break; case 'middle': offset.y = style.size / 2; break; case 'top': offset.y = style.size; break; } this.save(); this.translate(xOrig, yOrig); this.rotate(style.angle); this.lineCap = "round"; this.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight); this.strokeStyle = style.color || CanvasText.style.color; for (i = 0; i < lexemes.length; i++) { c = lexemes[i]; if (c.width == -1) { x = 0; y = style.size * 1.4; continue; } var points = c.points, width = c.width; if (c.diacritic) { var dia = CanvasText.diacritics[c.diacritic], character = CanvasText.letter(c.letter); CanvasText.drawPoints(this, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset); points = character.points; width = character.width; } CanvasText.drawPoints(this, points, x, y, mag, offset); if (CanvasText.debug) { this.save(); this.lineJoin = "miter"; this.lineWidth = 0.5; this.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor); this.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size); this.fillStyle = (style.originPointColor || CanvasText.style.originPointColor); this.beginPath(); this.arc(0, 0, 1.5, 0, Math.PI*2, true); this.fill(); this.closePath(); this.restore(); } x += width*mag*(style.tracking || CanvasText.style.tracking); } this.restore(); return total; } }; /** The text functions are bound to the CanvasRenderingContext2D prototype */ CanvasText.proto = window.CanvasRenderingContext2D ? window.CanvasRenderingContext2D.prototype : document.createElement('canvas').getContext('2d').__proto__; if (CanvasText.proto) { CanvasText.proto.drawText = CanvasText.draw; CanvasText.proto.measure = CanvasText.measure; CanvasText.proto.getTextBounds = CanvasText.getDimensions; CanvasText.proto.fontAscent = CanvasText.ascent; CanvasText.proto.fontDescent = CanvasText.descent; }