var Prototype={Version:"1.6.0.2",Browser:{IE:!!(window.attachEvent&&!window.opera),Opera:!!window.opera,WebKit:navigator.userAgent.indexOf("AppleWebKit/")>-1,Gecko:navigator.userAgent.indexOf("Gecko")>-1&&navigator.userAgent.indexOf("KHTML")==-1,MobileSafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/)},BrowserFeatures:{XPath:!!document.evaluate,ElementExtensions:!!window.HTMLElement,SpecificElementExtensions:document.createElement("div").__proto__&&document.createElement("div").__proto__!==document.createElement("form").__proto__},ScriptFragment:"<script[^>]*>([\\S\\s]*?)<\/script>",JSONFilter:/^\/\*-secure-([\s\S]*)\*\/\s*$/,emptyFunction:function(){},K:function(A){return A}};if(Prototype.Browser.MobileSafari){Prototype.BrowserFeatures.SpecificElementExtensions=false}var Class={create:function(){var E=null,D=$A(arguments);if(Object.isFunction(D[0])){E=D.shift()}function A(){this.initialize.apply(this,arguments)}Object.extend(A,Class.Methods);A.superclass=E;A.subclasses=[];if(E){var B=function(){};B.prototype=E.prototype;A.prototype=new B;E.subclasses.push(A)}for(var C=0;C<D.length;C++){A.addMethods(D[C])}if(!A.prototype.initialize){A.prototype.initialize=Prototype.emptyFunction}A.prototype.constructor=A;return A}};Class.Methods={addMethods:function(G){var C=this.superclass&&this.superclass.prototype;var B=Object.keys(G);if(!Object.keys({toString:true}).length){B.push("toString","valueOf")}for(var A=0,D=B.length;A<D;A++){var F=B[A],E=G[F];if(C&&Object.isFunction(E)&&E.argumentNames().first()=="$super"){var H=E,E=Object.extend((function(I){return function(){return C[I].apply(this,arguments)}})(F).wrap(H),{valueOf:function(){return H},toString:function(){return H.toString()}})}this.prototype[F]=E}return this}};var Abstract={};Object.extend=function(A,C){for(var B in C){A[B]=C[B]}return A};Object.extend(Object,{inspect:function(A){try{if(Object.isUndefined(A)){return"undefined"}if(A===null){return"null"}return A.inspect?A.inspect():String(A)}catch(B){if(B instanceof RangeError){return"..."}throw B}},toJSON:function(A){var C=typeof A;switch(C){case"undefined":case"function":case"unknown":return ;case"boolean":return A.toString()}if(A===null){return"null"}if(A.toJSON){return A.toJSON()}if(Object.isElement(A)){return }var B=[];for(var E in A){var D=Object.toJSON(A[E]);if(!Object.isUndefined(D)){B.push(E.toJSON()+": "+D)}}return"{"+B.join(", ")+"}"},toQueryString:function(A){return $H(A).toQueryString()},toHTML:function(A){return A&&A.toHTML?A.toHTML():String.interpret(A)},keys:function(A){var B=[];for(var C in A){B.push(C)}return B},values:function(B){var A=[];for(var C in B){A.push(B[C])}return A},clone:function(A){return Object.extend({},A)},isElement:function(A){return A&&A.nodeType==1},isArray:function(A){return A!=null&&typeof A=="object"&&"splice" in A&&"join" in A},isHash:function(A){return A instanceof Hash},isFunction:function(A){return typeof A=="function"},isString:function(A){return typeof A=="string"},isNumber:function(A){return typeof A=="number"},isUndefined:function(A){return typeof A=="undefined"}});Object.extend(Function.prototype,{argumentNames:function(){var A=this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");return A.length==1&&!A[0]?[]:A},bind:function(){if(arguments.length<2&&Object.isUndefined(arguments[0])){return this}var A=this,C=$A(arguments),B=C.shift();return function(){return A.apply(B,C.concat($A(arguments)))}},bindAsEventListener:function(){var A=this,C=$A(arguments),B=C.shift();return function(D){return A.apply(B,[D||window.event].concat(C))}},curry:function(){if(!arguments.length){return this}var A=this,B=$A(arguments);return function(){return A.apply(this,B.concat($A(arguments)))}},delay:function(){var A=this,B=$A(arguments),C=B.shift()*1000;return window.setTimeout(function(){return A.apply(A,B)},C)},wrap:function(B){var A=this;return function(){return B.apply(this,[A.bind(this)].concat($A(arguments)))}},methodize:function(){if(this._methodized){return this._methodized}var A=this;return this._methodized=function(){return A.apply(null,[this].concat($A(arguments)))}}});Function.prototype.defer=Function.prototype.delay.curry(0.01);Date.prototype.toJSON=function(){return'"'+this.getUTCFullYear()+"-"+(this.getUTCMonth()+1).toPaddedString(2)+"-"+this.getUTCDate().toPaddedString(2)+"T"+this.getUTCHours().toPaddedString(2)+":"+this.getUTCMinutes().toPaddedString(2)+":"+this.getUTCSeconds().toPaddedString(2)+'Z"'};var Try={these:function(){var C;for(var B=0,D=arguments.length;B<D;B++){var A=arguments[B];try{C=A();break}catch(E){}}return C}};RegExp.prototype.match=RegExp.prototype.test;RegExp.escape=function(A){return String(A).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")};var PeriodicalExecuter=Class.create({initialize:function(B,A){this.callback=B;this.frequency=A;this.currentlyExecuting=false;this.registerCallback()},registerCallback:function(){this.timer=setInterval(this.onTimerEvent.bind(this),this.frequency*1000)},execute:function(){this.callback(this)},stop:function(){if(!this.timer){return }clearInterval(this.timer);this.timer=null},onTimerEvent:function(){if(!this.currentlyExecuting){try{this.currentlyExecuting=true;this.execute()}finally{this.currentlyExecuting=false}}}});Object.extend(String,{interpret:function(A){return A==null?"":String(A)},specialChar:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r","\\":"\\\\"}});Object.extend(String.prototype,{gsub:function(E,C){var A="",D=this,B;C=arguments.callee.prepareReplacement(C);while(D.length>0){if(B=D.match(E)){A+=D.slice(0,B.index);A+=String.interpret(C(B));D=D.slice(B.index+B[0].length)}else{A+=D,D=""}}return A},sub:function(C,A,B){A=this.gsub.prepareReplacement(A);B=Object.isUndefined(B)?1:B;return this.gsub(C,function(D){if(--B<0){return D[0]}return A(D)})},scan:function(B,A){this.gsub(B,A);return String(this)},truncate:function(B,A){B=B||30;A=Object.isUndefined(A)?"...":A;return this.length>B?this.slice(0,B-A.length)+A:String(this)},strip:function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,"")},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,"img"),"")},extractScripts:function(){var B=new RegExp(Prototype.ScriptFragment,"img");var A=new RegExp(Prototype.ScriptFragment,"im");return(this.match(B)||[]).map(function(C){return(C.match(A)||["",""])[1]})},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)})},escapeHTML:function(){var A=arguments.callee;A.text.data=this;return A.div.innerHTML},unescapeHTML:function(){var A=new Element("div");A.innerHTML=this.stripTags();return A.childNodes[0]?(A.childNodes.length>1?$A(A.childNodes).inject("",function(B,C){return B+C.nodeValue}):A.childNodes[0].nodeValue):""},toQueryParams:function(B){var A=this.strip().match(/([^?#]*)(#.*)?$/);if(!A){return{}}return A[1].split(B||"&").inject({},function(E,F){if((F=F.split("="))[0]){var C=decodeURIComponent(F.shift());var D=F.length>1?F.join("="):F[0];if(D!=undefined){D=decodeURIComponent(D)}if(C in E){if(!Object.isArray(E[C])){E[C]=[E[C]]}E[C].push(D)}else{E[C]=D}}return E})},toArray:function(){return this.split("")},succ:function(){return this.slice(0,this.length-1)+String.fromCharCode(this.charCodeAt(this.length-1)+1)},times:function(A){return A<1?"":new Array(A+1).join(this)},camelize:function(){var D=this.split("-"),A=D.length;if(A==1){return D[0]}var C=this.charAt(0)=="-"?D[0].charAt(0).toUpperCase()+D[0].substring(1):D[0];for(var B=1;B<A;B++){C+=D[B].charAt(0).toUpperCase()+D[B].substring(1)}return C},capitalize:function(){return this.charAt(0).toUpperCase()+this.substring(1).toLowerCase()},underscore:function(){return this.gsub(/::/,"/").gsub(/([A-Z]+)([A-Z][a-z])/,"#{1}_#{2}").gsub(/([a-z\d])([A-Z])/,"#{1}_#{2}").gsub(/-/,"_").toLowerCase()},dasherize:function(){return this.gsub(/_/,"-")},inspect:function(B){var A=this.gsub(/[\x00-\x1f\\]/,function(C){var D=String.specialChar[C[0]];return D?D:"\\u00"+C[0].charCodeAt().toPaddedString(2,16)});if(B){return'"'+A.replace(/"/g,'\\"')+'"'}return"'"+A.replace(/'/g,"\\'")+"'"},toJSON:function(){return this.inspect(true)},unfilterJSON:function(A){return this.sub(A||Prototype.JSONFilter,"#{1}")},isJSON:function(){var A=this;if(A.blank()){return false}A=this.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,"");return(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(A)},evalJSON:function(sanitize){var json=this.unfilterJSON();try{if(!sanitize||json.isJSON()){return eval("("+json+")")}}catch(e){}throw new SyntaxError("Badly formed JSON string: "+this.inspect())},include:function(A){return this.indexOf(A)>-1},startsWith:function(A){return this.indexOf(A)===0},endsWith:function(A){var B=this.length-A.length;return B>=0&&this.lastIndexOf(A)===B},empty:function(){return this==""},blank:function(){return/^\s*$/.test(this)},interpolate:function(A,B){return new Template(this,B).evaluate(A)}});if(Prototype.Browser.WebKit||Prototype.Browser.IE){Object.extend(String.prototype,{escapeHTML:function(){return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},unescapeHTML:function(){return this.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")}})}String.prototype.gsub.prepareReplacement=function(B){if(Object.isFunction(B)){return B}var A=new Template(B);return function(C){return A.evaluate(C)}};String.prototype.parseQuery=String.prototype.toQueryParams;Object.extend(String.prototype.escapeHTML,{div:document.createElement("div"),text:document.createTextNode("")});with(String.prototype.escapeHTML){div.appendChild(text)}var Template=Class.create({initialize:function(A,B){this.template=A.toString();this.pattern=B||Template.Pattern},evaluate:function(A){if(Object.isFunction(A.toTemplateReplacements)){A=A.toTemplateReplacements()}return this.template.gsub(this.pattern,function(D){if(A==null){return""}var F=D[1]||"";if(F=="\\"){return D[2]}var B=A,G=D[3];var E=/^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;D=E.exec(G);if(D==null){return F}while(D!=null){var C=D[1].startsWith("[")?D[2].gsub("\\\\]","]"):D[1];B=B[C];if(null==B||""==D[3]){break}G=G.substring("["==D[3]?D[1].length:D[0].length);D=E.exec(G)}return F+String.interpret(B)})}});Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;var $break={};var Enumerable={each:function(C,B){var A=0;C=C.bind(B);try{this._each(function(E){C(E,A++)})}catch(D){if(D!=$break){throw D}}return this},eachSlice:function(D,C,B){C=C?C.bind(B):Prototype.K;var A=-D,E=[],F=this.toArray();while((A+=D)<F.length){E.push(F.slice(A,A+D))}return E.collect(C,B)},all:function(C,B){C=C?C.bind(B):Prototype.K;var A=true;this.each(function(E,D){A=A&&!!C(E,D);if(!A){throw $break}});return A},any:function(C,B){C=C?C.bind(B):Prototype.K;var A=false;this.each(function(E,D){if(A=!!C(E,D)){throw $break}});return A},collect:function(C,B){C=C?C.bind(B):Prototype.K;var A=[];this.each(function(E,D){A.push(C(E,D))});return A},detect:function(C,B){C=C.bind(B);var A;this.each(function(E,D){if(C(E,D)){A=E;throw $break}});return A},findAll:function(C,B){C=C.bind(B);var A=[];this.each(function(E,D){if(C(E,D)){A.push(E)}});return A},grep:function(D,C,B){C=C?C.bind(B):Prototype.K;var A=[];if(Object.isString(D)){D=new RegExp(D)}this.each(function(F,E){if(D.match(F)){A.push(C(F,E))}});return A},include:function(A){if(Object.isFunction(this.indexOf)){if(this.indexOf(A)!=-1){return true}}var B=false;this.each(function(C){if(C==A){B=true;throw $break}});return B},inGroupsOf:function(B,A){A=Object.isUndefined(A)?null:A;return this.eachSlice(B,function(C){while(C.length<B){C.push(A)}return C})},inject:function(A,C,B){C=C.bind(B);this.each(function(E,D){A=C(A,E,D)});return A},invoke:function(B){var A=$A(arguments).slice(1);return this.map(function(C){return C[B].apply(C,A)})},max:function(C,B){C=C?C.bind(B):Prototype.K;var A;this.each(function(E,D){E=C(E,D);if(A==null||E>=A){A=E}});return A},min:function(C,B){C=C?C.bind(B):Prototype.K;var A;this.each(function(E,D){E=C(E,D);if(A==null||E<A){A=E}});return A},partition:function(D,B){D=D?D.bind(B):Prototype.K;var C=[],A=[];this.each(function(F,E){(D(F,E)?C:A).push(F)});return[C,A]},pluck:function(B){var A=[];this.each(function(C){A.push(C[B])});return A},reject:function(C,B){C=C.bind(B);var A=[];this.each(function(E,D){if(!C(E,D)){A.push(E)}});return A},sortBy:function(B,A){B=B.bind(A);return this.map(function(D,C){return{value:D,criteria:B(D,C)}}).sort(function(F,E){var D=F.criteria,C=E.criteria;return D<C?-1:D>C?1:0}).pluck("value")},toArray:function(){return this.map()},zip:function(){var B=Prototype.K,A=$A(arguments);if(Object.isFunction(A.last())){B=A.pop()}var C=[this].concat(A).map($A);return this.map(function(E,D){return B(C.pluck(D))})},size:function(){return this.toArray().length},inspect:function(){return"#<Enumerable:"+this.toArray().inspect()+">"}};Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,filter:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray,every:Enumerable.all,some:Enumerable.any});function $A(C){if(!C){return[]}if(C.toArray){return C.toArray()}var B=C.length||0,A=new Array(B);while(B--){A[B]=C[B]}return A}if(Prototype.Browser.WebKit){$A=function(C){if(!C){return[]}if(!(Object.isFunction(C)&&C=="[object NodeList]")&&C.toArray){return C.toArray()}var B=C.length||0,A=new Array(B);while(B--){A[B]=C[B]}return A}}Array.from=$A;Object.extend(Array.prototype,Enumerable);if(!Array.prototype._reverse){Array.prototype._reverse=Array.prototype.reverse}Object.extend(Array.prototype,{_each:function(B){for(var A=0,C=this.length;A<C;A++){B(this[A])}},clear:function(){this.length=0;return this},first:function(){return this[0]},last:function(){return this[this.length-1]},compact:function(){return this.select(function(A){return A!=null})},flatten:function(){return this.inject([],function(B,A){return B.concat(Object.isArray(A)?A.flatten():[A])})},without:function(){var A=$A(arguments);return this.select(function(B){return !A.include(B)})},reverse:function(A){return(A!==false?this:this.toArray())._reverse()},reduce:function(){return this.length>1?this:this[0]},uniq:function(A){return this.inject([],function(D,C,B){if(0==B||(A?D.last()!=C:!D.include(C))){D.push(C)}return D})},intersect:function(A){return this.uniq().findAll(function(B){return A.detect(function(C){return B===C})})},clone:function(){return[].concat(this)},size:function(){return this.length},inspect:function(){return"["+this.map(Object.inspect).join(", ")+"]"},toJSON:function(){var A=[];this.each(function(B){var C=Object.toJSON(B);if(!Object.isUndefined(C)){A.push(C)}});return"["+A.join(", ")+"]"}});if(Object.isFunction(Array.prototype.forEach)){Array.prototype._each=Array.prototype.forEach}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(C,A){A||(A=0);var B=this.length;if(A<0){A=B+A}for(;A<B;A++){if(this[A]===C){return A}}return -1}}if(!Array.prototype.lastIndexOf){Array.prototype.lastIndexOf=function(B,A){A=isNaN(A)?this.length:(A<0?this.length+A:A)+1;var C=this.slice(0,A).reverse().indexOf(B);return(C<0)?C:A-C-1}}Array.prototype.toArray=Array.prototype.clone;function $w(A){if(!Object.isString(A)){return[]}A=A.strip();return A?A.split(/\s+/):[]}if(Prototype.Browser.Opera){Array.prototype.concat=function(){var E=[];for(var B=0,C=this.length;B<C;B++){E.push(this[B])}for(var B=0,C=arguments.length;B<C;B++){if(Object.isArray(arguments[B])){for(var A=0,D=arguments[B].length;A<D;A++){E.push(arguments[B][A])}}else{E.push(arguments[B])}}return E}}Object.extend(Number.prototype,{toColorPart:function(){return this.toPaddedString(2,16)},succ:function(){return this+1},times:function(A){$R(0,this,true).each(A);return this},toPaddedString:function(C,B){var A=this.toString(B||10);return"0".times(C-A.length)+A},toJSON:function(){return isFinite(this)?this.toString():"null"}});$w("abs round ceil floor").each(function(A){Number.prototype[A]=Math[A].methodize()});function $H(A){return new Hash(A)}var Hash=Class.create(Enumerable,(function(){function A(B,C){if(Object.isUndefined(C)){return B}return B+"="+encodeURIComponent(String.interpret(C))}return{initialize:function(B){this._object=Object.isHash(B)?B.toObject():Object.clone(B)},_each:function(C){for(var B in this._object){var D=this._object[B],E=[B,D];E.key=B;E.value=D;C(E)}},set:function(B,C){return this._object[B]=C},get:function(B){return this._object[B]},unset:function(B){var C=this._object[B];delete this._object[B];return C},toObject:function(){return Object.clone(this._object)},keys:function(){return this.pluck("key")},values:function(){return this.pluck("value")},index:function(C){var B=this.detect(function(D){return D.value===C});return B&&B.key},merge:function(B){return this.clone().update(B)},update:function(B){return new Hash(B).inject(this,function(C,D){C.set(D.key,D.value);return C})},toQueryString:function(){return this.map(function(D){var C=encodeURIComponent(D.key),B=D.value;if(B&&typeof B=="object"){if(Object.isArray(B)){return B.map(A.curry(C)).join("&")}}return A(C,B)}).join("&")},inspect:function(){return"#<Hash:{"+this.map(function(B){return B.map(Object.inspect).join(": ")}).join(", ")+"}>"},toJSON:function(){return Object.toJSON(this.toObject())},clone:function(){return new Hash(this)}}})());Hash.prototype.toTemplateReplacements=Hash.prototype.toObject;Hash.from=$H;var ObjectRange=Class.create(Enumerable,{initialize:function(C,A,B){this.start=C;this.end=A;this.exclusive=B},_each:function(A){var B=this.start;while(this.include(B)){A(B);B=B.succ()}},include:function(A){if(A<this.start){return false}if(this.exclusive){return A<this.end}return A<=this.end}});var $R=function(C,A,B){return new ObjectRange(C,A,B)};var Ajax={getTransport:function(){return Try.these(function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")})||false},activeRequestCount:0};Ajax.Responders={responders:[],_each:function(A){this.responders._each(A)},register:function(A){if(!this.include(A)){this.responders.push(A)}},unregister:function(A){this.responders=this.responders.without(A)},dispatch:function(D,B,C,A){this.each(function(E){if(Object.isFunction(E[D])){try{E[D].apply(E,[B,C,A])}catch(F){}}})}};Object.extend(Ajax.Responders,Enumerable);Ajax.Responders.register({onCreate:function(){Ajax.activeRequestCount++},onComplete:function(){Ajax.activeRequestCount--}});Ajax.Base=Class.create({initialize:function(A){this.options={method:"post",asynchronous:true,contentType:"application/x-www-form-urlencoded",encoding:"UTF-8",parameters:"",evalJSON:true,evalJS:true};Object.extend(this.options,A||{});this.options.method=this.options.method.toLowerCase();if(Object.isString(this.options.parameters)){this.options.parameters=this.options.parameters.toQueryParams()}else{if(Object.isHash(this.options.parameters)){this.options.parameters=this.options.parameters.toObject()}}}});Ajax.Request=Class.create(Ajax.Base,{_complete:false,initialize:function($super,B,A){$super(A);this.transport=Ajax.getTransport();this.request(B)},request:function(B){this.url=B;this.method=this.options.method;var D=Object.clone(this.options.parameters);if(!["get","post"].include(this.method)){D._method=this.method;this.method="post"}this.parameters=D;if(D=Object.toQueryString(D)){if(this.method=="get"){this.url+=(this.url.include("?")?"&":"?")+D}else{if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){D+="&_="}}}try{var A=new Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(A)}Ajax.Responders.dispatch("onCreate",this,A);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){this.respondToReadyState.bind(this).defer(1)}this.transport.onreadystatechange=this.onStateChange.bind(this);this.setRequestHeaders();this.body=this.method=="post"?(this.options.postBody||D):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange()}}catch(C){this.dispatchException(C)}},onStateChange:function(){var A=this.transport.readyState;if(A>1&&!((A==4)&&this._complete)){this.respondToReadyState(this.transport.readyState)}},setRequestHeaders:function(){var E={"X-Requested-With":"XMLHttpRequest","X-Prototype-Version":Prototype.Version,Accept:"text/javascript, text/html, application/xml, text/xml, */*"};if(this.method=="post"){E["Content-type"]=this.options.contentType+(this.options.encoding?"; charset="+this.options.encoding:"");if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){E.Connection="close"}}if(typeof this.options.requestHeaders=="object"){var C=this.options.requestHeaders;if(Object.isFunction(C.push)){for(var B=0,D=C.length;B<D;B+=2){E[C[B]]=C[B+1]}}else{$H(C).each(function(F){E[F.key]=F.value})}}for(var A in E){this.transport.setRequestHeader(A,E[A])}},success:function(){var A=this.getStatus();return !A||(A>=200&&A<300)},getStatus:function(){try{return this.transport.status||0}catch(A){return 0}},respondToReadyState:function(A){var C=Ajax.Request.Events[A],B=new Ajax.Response(this);if(C=="Complete"){try{this._complete=true;(this.options["on"+B.status]||this.options["on"+(this.success()?"Success":"Failure")]||Prototype.emptyFunction)(B,B.headerJSON)}catch(D){this.dispatchException(D)}var E=B.getHeader("Content-type");if(this.options.evalJS=="force"||(this.options.evalJS&&this.isSameOrigin()&&E&&E.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))){this.evalResponse()}}try{(this.options["on"+C]||Prototype.emptyFunction)(B,B.headerJSON);Ajax.Responders.dispatch("on"+C,this,B,B.headerJSON)}catch(D){this.dispatchException(D)}if(C=="Complete"){this.transport.onreadystatechange=Prototype.emptyFunction}},isSameOrigin:function(){var A=this.url.match(/^\s*https?:\/\/[^\/]*/);return !A||(A[0]=="#{protocol}//#{domain}#{port}".interpolate({protocol:location.protocol,domain:document.domain,port:location.port?":"+location.port:""}))},getHeader:function(A){try{return this.transport.getResponseHeader(A)||null}catch(B){return null}},evalResponse:function(){try{return eval((this.transport.responseText||"").unfilterJSON())}catch(e){this.dispatchException(e)}},dispatchException:function(A){(this.options.onException||Prototype.emptyFunction)(this,A);Ajax.Responders.dispatch("onException",this,A)}});Ajax.Request.Events=["Uninitialized","Loading","Loaded","Interactive","Complete"];Ajax.Response=Class.create({initialize:function(C){this.request=C;var D=this.transport=C.transport,A=this.readyState=D.readyState;if((A>2&&!Prototype.Browser.IE)||A==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=String.interpret(D.responseText);this.headerJSON=this._getHeaderJSON()}if(A==4){var B=D.responseXML;this.responseXML=Object.isUndefined(B)?null:B;this.responseJSON=this._getResponseJSON()}},status:0,statusText:"",getStatus:Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||""}catch(A){return""}},getHeader:Ajax.Request.prototype.getHeader,getAllHeaders:function(){try{return this.getAllResponseHeaders()}catch(A){return null}},getResponseHeader:function(A){return this.transport.getResponseHeader(A)},getAllResponseHeaders:function(){return this.transport.getAllResponseHeaders()},_getHeaderJSON:function(){var A=this.getHeader("X-JSON");if(!A){return null}A=decodeURIComponent(escape(A));try{return A.evalJSON(this.request.options.sanitizeJSON||!this.request.isSameOrigin())}catch(B){this.request.dispatchException(B)}},_getResponseJSON:function(){var A=this.request.options;if(!A.evalJSON||(A.evalJSON!="force"&&!(this.getHeader("Content-type")||"").include("application/json"))||this.responseText.blank()){return null}try{return this.responseText.evalJSON(A.sanitizeJSON||!this.request.isSameOrigin())}catch(B){this.request.dispatchException(B)}}});Ajax.Updater=Class.create(Ajax.Request,{initialize:function($super,A,C,B){this.container={success:(A.success||A),failure:(A.failure||(A.success?null:A))};B=Object.clone(B);var D=B.onComplete;B.onComplete=(function(E,F){this.updateContent(E.responseText);if(Object.isFunction(D)){D(E,F)}}).bind(this);$super(C,B)},updateContent:function(D){var C=this.container[this.success()?"success":"failure"],A=this.options;if(!A.evalScripts){D=D.stripScripts()}if(C=$(C)){if(A.insertion){if(Object.isString(A.insertion)){var B={};B[A.insertion]=D;C.insert(B)}else{A.insertion(C,D)}}else{C.update(D)}}}});Ajax.PeriodicalUpdater=Class.create(Ajax.Base,{initialize:function($super,A,C,B){$super(B);this.onComplete=this.options.onComplete;this.frequency=(this.options.frequency||2);this.decay=(this.options.decay||1);this.updater={};this.container=A;this.url=C;this.start()},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent()},stop:function(){this.updater.options.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,arguments)},updateComplete:function(A){if(this.options.decay){this.decay=(A.responseText==this.lastText?this.decay*this.options.decay:1);this.lastText=A.responseText}this.timer=this.onTimerEvent.bind(this).delay(this.decay*this.frequency)},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options)}});function $(B){if(arguments.length>1){for(var A=0,D=[],C=arguments.length;A<C;A++){D.push($(arguments[A]))}return D}if(Object.isString(B)){B=document.getElementById(B)}return Element.extend(B)}if(Prototype.BrowserFeatures.XPath){document._getElementsByXPath=function(F,A){var C=[];var E=document.evaluate(F,$(A)||document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);for(var B=0,D=E.snapshotLength;B<D;B++){C.push(Element.extend(E.snapshotItem(B)))}return C}}if(!window.Node){var Node={}}if(!Node.ELEMENT_NODE){Object.extend(Node,{ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12})}(function(){var A=this.Element;this.Element=function(D,C){C=C||{};D=D.toLowerCase();var B=Element.cache;if(Prototype.Browser.IE&&C.name){D="<"+D+' name="'+C.name+'">';delete C.name;return Element.writeAttribute(document.createElement(D),C)}if(!B[D]){B[D]=Element.extend(document.createElement(D))}return Element.writeAttribute(B[D].cloneNode(false),C)};Object.extend(this.Element,A||{})}).call(window);Element.cache={};Element.Methods={visible:function(A){return $(A).style.display!="none"},toggle:function(A){A=$(A);Element[Element.visible(A)?"hide":"show"](A);return A},hide:function(A){$(A).style.display="none";return A},show:function(A){$(A).style.display="";return A},remove:function(A){A=$(A);A.parentNode.removeChild(A);return A},update:function(A,B){A=$(A);if(B&&B.toElement){B=B.toElement()}if(Object.isElement(B)){return A.update().insert(B)}B=Object.toHTML(B);A.innerHTML=B.stripScripts();B.evalScripts.bind(B).defer();return A},replace:function(B,C){B=$(B);if(C&&C.toElement){C=C.toElement()}else{if(!Object.isElement(C)){C=Object.toHTML(C);var A=B.ownerDocument.createRange();A.selectNode(B);C.evalScripts.bind(C).defer();C=A.createContextualFragment(C.stripScripts())}}B.parentNode.replaceChild(C,B);return B},insert:function(C,E){C=$(C);if(Object.isString(E)||Object.isNumber(E)||Object.isElement(E)||(E&&(E.toElement||E.toHTML))){E={bottom:E}}var D,F,B,G;for(var A in E){D=E[A];A=A.toLowerCase();F=Element._insertionTranslations[A];if(D&&D.toElement){D=D.toElement()}if(Object.isElement(D)){F(C,D);continue}D=Object.toHTML(D);B=((A=="before"||A=="after")?C.parentNode:C).tagName.toUpperCase();G=Element._getContentFromAnonymousElement(B,D.stripScripts());if(A=="top"||A=="after"){G.reverse()}G.each(F.curry(C));D.evalScripts.bind(D).defer()}return C},wrap:function(B,C,A){B=$(B);if(Object.isElement(C)){$(C).writeAttribute(A||{})}else{if(Object.isString(C)){C=new Element(C,A)}else{C=new Element("div",C)}}if(B.parentNode){B.parentNode.replaceChild(C,B)}C.appendChild(B);return C},inspect:function(B){B=$(B);var A="<"+B.tagName.toLowerCase();$H({id:"id",className:"class"}).each(function(F){var E=F.first(),C=F.last();var D=(B[E]||"").toString();if(D){A+=" "+C+"="+D.inspect(true)}});return A+">"},recursivelyCollect:function(A,C){A=$(A);var B=[];while(A=A[C]){if(A.nodeType==1){B.push(Element.extend(A))}}return B},ancestors:function(A){return $(A).recursivelyCollect("parentNode")},descendants:function(A){return $(A).select("*")},firstDescendant:function(A){A=$(A).firstChild;while(A&&A.nodeType!=1){A=A.nextSibling}return $(A)},immediateDescendants:function(A){if(!(A=$(A).firstChild)){return[]}while(A&&A.nodeType!=1){A=A.nextSibling}if(A){return[A].concat($(A).nextSiblings())}return[]},previousSiblings:function(A){return $(A).recursivelyCollect("previousSibling")},nextSiblings:function(A){return $(A).recursivelyCollect("nextSibling")},siblings:function(A){A=$(A);return A.previousSiblings().reverse().concat(A.nextSiblings())},match:function(B,A){if(Object.isString(A)){A=new Selector(A)}return A.match($(B))},up:function(B,D,A){B=$(B);if(arguments.length==1){return $(B.parentNode)}var C=B.ancestors();return Object.isNumber(D)?C[D]:Selector.findElement(C,D,A)},down:function(B,C,A){B=$(B);if(arguments.length==1){return B.firstDescendant()}return Object.isNumber(C)?B.descendants()[C]:B.select(C)[A||0]},previous:function(B,D,A){B=$(B);if(arguments.length==1){return $(Selector.handlers.previousElementSibling(B))}var C=B.previousSiblings();return Object.isNumber(D)?C[D]:Selector.findElement(C,D,A)},next:function(C,D,B){C=$(C);if(arguments.length==1){return $(Selector.handlers.nextElementSibling(C))}var A=C.nextSiblings();return Object.isNumber(D)?A[D]:Selector.findElement(A,D,B)},select:function(){var A=$A(arguments),B=$(A.shift());return Selector.findChildElements(B,A)},adjacent:function(){var A=$A(arguments),B=$(A.shift());return Selector.findChildElements(B.parentNode,A).without(B)},identify:function(B){B=$(B);var C=B.readAttribute("id"),A=arguments.callee;if(C){return C}do{C="anonymous_element_"+A.counter++}while($(C));B.writeAttribute("id",C);return C},readAttribute:function(C,A){C=$(C);if(Prototype.Browser.IE){var B=Element._attributeTranslations.read;if(B.values[A]){return B.values[A](C,A)}if(B.names[A]){A=B.names[A]}if(A.include(":")){return(!C.attributes||!C.attributes[A])?null:C.attributes[A].value}}return C.getAttribute(A)},writeAttribute:function(E,C,F){E=$(E);var B={},D=Element._attributeTranslations.write;if(typeof C=="object"){B=C}else{B[C]=Object.isUndefined(F)?true:F}for(var A in B){C=D.names[A]||A;F=B[A];if(D.values[A]){C=D.values[A](E,F)}if(F===false||F===null){E.removeAttribute(C)}else{if(F===true){E.setAttribute(C,C)}else{E.setAttribute(C,F)}}}return E},getHeight:function(A){return $(A).getDimensions().height},getWidth:function(A){return $(A).getDimensions().width},classNames:function(A){return new Element.ClassNames(A)},hasClassName:function(A,B){if(!(A=$(A))){return }var C=A.className;return(C.length>0&&(C==B||new RegExp("(^|\\s)"+B+"(\\s|$)").test(C)))},addClassName:function(A,B){if(!(A=$(A))){return }if(!A.hasClassName(B)){A.className+=(A.className?" ":"")+B}return A},removeClassName:function(A,B){if(!(A=$(A))){return }A.className=A.className.replace(new RegExp("(^|\\s+)"+B+"(\\s+|$)")," ").strip();return A},toggleClassName:function(A,B){if(!(A=$(A))){return }return A[A.hasClassName(B)?"removeClassName":"addClassName"](B)},cleanWhitespace:function(B){B=$(B);var C=B.firstChild;while(C){var A=C.nextSibling;if(C.nodeType==3&&!/\S/.test(C.nodeValue)){B.removeChild(C)}C=A}return B},empty:function(A){return $(A).innerHTML.blank()},descendantOf:function(D,C){D=$(D),C=$(C);var F=C;if(D.compareDocumentPosition){return(D.compareDocumentPosition(C)&8)===8}if(D.sourceIndex&&!Prototype.Browser.Opera){var E=D.sourceIndex,B=C.sourceIndex,A=C.nextSibling;if(!A){do{C=C.parentNode}while(!(A=C.nextSibling)&&C.parentNode)}if(A&&A.sourceIndex){return(E>B&&E<A.sourceIndex)}}while(D=D.parentNode){if(D==F){return true}}return false},scrollTo:function(A){A=$(A);var B=A.cumulativeOffset();window.scrollTo(B[0],B[1]);return A},getStyle:function(B,C){B=$(B);C=C=="float"?"cssFloat":C.camelize();var D=B.style[C];if(!D){var A=document.defaultView.getComputedStyle(B,null);D=A?A[C]:null}if(C=="opacity"){return D?parseFloat(D):1}return D=="auto"?null:D},getOpacity:function(A){return $(A).getStyle("opacity")},setStyle:function(B,C){B=$(B);var E=B.style,A;if(Object.isString(C)){B.style.cssText+=";"+C;return C.include("opacity")?B.setOpacity(C.match(/opacity:\s*(\d?\.?\d*)/)[1]):B}for(var D in C){if(D=="opacity"){B.setOpacity(C[D])}else{E[(D=="float"||D=="cssFloat")?(Object.isUndefined(E.styleFloat)?"cssFloat":"styleFloat"):D]=C[D]}}return B},setOpacity:function(A,B){A=$(A);A.style.opacity=(B==1||B==="")?"":(B<0.00001)?0:B;return A},getDimensions:function(C){C=$(C);var G=$(C).getStyle("display");if(G!="none"&&G!=null){return{width:C.offsetWidth,height:C.offsetHeight}}var B=C.style;var F=B.visibility;var D=B.position;var A=B.display;B.visibility="hidden";B.position="absolute";B.display="block";var H=C.clientWidth;var E=C.clientHeight;B.display=A;B.position=D;B.visibility=F;return{width:H,height:E}},makePositioned:function(A){A=$(A);var B=Element.getStyle(A,"position");if(B=="static"||!B){A._madePositioned=true;A.style.position="relative";if(window.opera){A.style.top=0;A.style.left=0}}return A},undoPositioned:function(A){A=$(A);if(A._madePositioned){A._madePositioned=undefined;A.style.position=A.style.top=A.style.left=A.style.bottom=A.style.right=""}return A},makeClipping:function(A){A=$(A);if(A._overflow){return A}A._overflow=Element.getStyle(A,"overflow")||"auto";if(A._overflow!=="hidden"){A.style.overflow="hidden"}return A},undoClipping:function(A){A=$(A);if(!A._overflow){return A}A.style.overflow=A._overflow=="auto"?"":A._overflow;A._overflow=null;return A},cumulativeOffset:function(B){var A=0,C=0;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;B=B.offsetParent}while(B);return Element._returnOffset(C,A)},positionedOffset:function(B){var A=0,D=0;do{A+=B.offsetTop||0;D+=B.offsetLeft||0;B=B.offsetParent;if(B){if(B.tagName=="BODY"){break}var C=Element.getStyle(B,"position");if(C!=="static"){break}}}while(B);return Element._returnOffset(D,A)},absolutize:function(B){B=$(B);if(B.getStyle("position")=="absolute"){return }var D=B.positionedOffset();var F=D[1];var E=D[0];var C=B.clientWidth;var A=B.clientHeight;B._originalLeft=E-parseFloat(B.style.left||0);B._originalTop=F-parseFloat(B.style.top||0);B._originalWidth=B.style.width;B._originalHeight=B.style.height;B.style.position="absolute";B.style.top=F+"px";B.style.left=E+"px";B.style.width=C+"px";B.style.height=A+"px";return B},relativize:function(A){A=$(A);if(A.getStyle("position")=="relative"){return }A.style.position="relative";var C=parseFloat(A.style.top||0)-(A._originalTop||0);var B=parseFloat(A.style.left||0)-(A._originalLeft||0);A.style.top=C+"px";A.style.left=B+"px";A.style.height=A._originalHeight;A.style.width=A._originalWidth;return A},cumulativeScrollOffset:function(B){var A=0,C=0;do{A+=B.scrollTop||0;C+=B.scrollLeft||0;B=B.parentNode}while(B);return Element._returnOffset(C,A)},getOffsetParent:function(A){if(A.offsetParent){return $(A.offsetParent)}if(A==document.body){return $(A)}while((A=A.parentNode)&&A!=document.body){if(Element.getStyle(A,"position")!="static"){return $(A)}}return $(document.body)},viewportOffset:function(D){var A=0,C=0;var B=D;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;if(B.offsetParent==document.body&&Element.getStyle(B,"position")=="absolute"){break}}while(B=B.offsetParent);B=D;do{if(!Prototype.Browser.Opera||B.tagName=="BODY"){A-=B.scrollTop||0;C-=B.scrollLeft||0}}while(B=B.parentNode);return Element._returnOffset(C,A)},clonePosition:function(B,D){var A=Object.extend({setLeft:true,setTop:true,setWidth:true,setHeight:true,offsetTop:0,offsetLeft:0},arguments[2]||{});D=$(D);var E=D.viewportOffset();B=$(B);var F=[0,0];var C=null;if(Element.getStyle(B,"position")=="absolute"){C=B.getOffsetParent();F=C.viewportOffset()}if(C==document.body){F[0]-=document.body.offsetLeft;F[1]-=document.body.offsetTop}if(A.setLeft){B.style.left=(E[0]-F[0]+A.offsetLeft)+"px"}if(A.setTop){B.style.top=(E[1]-F[1]+A.offsetTop)+"px"}if(A.setWidth){B.style.width=D.offsetWidth+"px"}if(A.setHeight){B.style.height=D.offsetHeight+"px"}return B}};Element.Methods.identify.counter=1;Object.extend(Element.Methods,{getElementsBySelector:Element.Methods.select,childElements:Element.Methods.immediateDescendants});Element._attributeTranslations={write:{names:{className:"class",htmlFor:"for"},values:{}}};if(Prototype.Browser.Opera){Element.Methods.getStyle=Element.Methods.getStyle.wrap(function(D,B,C){switch(C){case"left":case"top":case"right":case"bottom":if(D(B,"position")==="static"){return null}case"height":case"width":if(!Element.visible(B)){return null}var E=parseInt(D(B,C),10);if(E!==B["offset"+C.capitalize()]){return E+"px"}var A;if(C==="height"){A=["border-top-width","padding-top","padding-bottom","border-bottom-width"]}else{A=["border-left-width","padding-left","padding-right","border-right-width"]}return A.inject(E,function(F,G){var H=D(B,G);return H===null?F:F-parseInt(H,10)})+"px";default:return D(B,C)}});Element.Methods.readAttribute=Element.Methods.readAttribute.wrap(function(C,A,B){if(B==="title"){return A.title}return C(A,B)})}else{if(Prototype.Browser.IE){Element.Methods.getOffsetParent=Element.Methods.getOffsetParent.wrap(function(C,B){B=$(B);var A=B.getStyle("position");if(A!=="static"){return C(B)}B.setStyle({position:"relative"});var D=C(B);B.setStyle({position:A});return D});$w("positionedOffset viewportOffset").each(function(A){Element.Methods[A]=Element.Methods[A].wrap(function(E,C){C=$(C);var B=C.getStyle("position");if(B!=="static"){return E(C)}var D=C.getOffsetParent();if(D&&D.getStyle("position")==="fixed"){D.setStyle({zoom:1})}C.setStyle({position:"relative"});var F=E(C);C.setStyle({position:B});return F})});Element.Methods.getStyle=function(A,B){A=$(A);B=(B=="float"||B=="cssFloat")?"styleFloat":B.camelize();var C=A.style[B];if(!C&&A.currentStyle){C=A.currentStyle[B]}if(B=="opacity"){if(C=(A.getStyle("filter")||"").match(/alpha\(opacity=(.*)\)/)){if(C[1]){return parseFloat(C[1])/100}}return 1}if(C=="auto"){if((B=="width"||B=="height")&&(A.getStyle("display")!="none")){return A["offset"+B.capitalize()]+"px"}return null}return C};Element.Methods.setOpacity=function(B,E){function F(G){return G.replace(/alpha\([^\)]*\)/gi,"")}B=$(B);var A=B.currentStyle;if((A&&!A.hasLayout)||(!A&&B.style.zoom=="normal")){B.style.zoom=1}var D=B.getStyle("filter"),C=B.style;if(E==1||E===""){(D=F(D))?C.filter=D:C.removeAttribute("filter");return B}else{if(E<0.00001){E=0}}C.filter=F(D)+"alpha(opacity="+(E*100)+")";return B};Element._attributeTranslations={read:{names:{"class":"className","for":"htmlFor"},values:{_getAttr:function(A,B){return A.getAttribute(B,2)},_getAttrNode:function(A,C){var B=A.getAttributeNode(C);return B?B.value:""},_getEv:function(A,B){B=A.getAttribute(B);return B?B.toString().slice(23,-2):null},_flag:function(A,B){return $(A).hasAttribute(B)?B:null},style:function(A){return A.style.cssText.toLowerCase()},title:function(A){return A.title}}}};Element._attributeTranslations.write={names:Object.extend({cellpadding:"cellPadding",cellspacing:"cellSpacing"},Element._attributeTranslations.read.names),values:{checked:function(A,B){A.checked=!!B},style:function(A,B){A.style.cssText=B?B:""}}};Element._attributeTranslations.has={};$w("colSpan rowSpan vAlign dateTime accessKey tabIndex encType maxLength readOnly longDesc").each(function(A){Element._attributeTranslations.write.names[A.toLowerCase()]=A;Element._attributeTranslations.has[A.toLowerCase()]=A});(function(A){Object.extend(A,{href:A._getAttr,src:A._getAttr,type:A._getAttr,action:A._getAttrNode,disabled:A._flag,checked:A._flag,readonly:A._flag,multiple:A._flag,onload:A._getEv,onunload:A._getEv,onclick:A._getEv,ondblclick:A._getEv,onmousedown:A._getEv,onmouseup:A._getEv,onmouseover:A._getEv,onmousemove:A._getEv,onmouseout:A._getEv,onfocus:A._getEv,onblur:A._getEv,onkeypress:A._getEv,onkeydown:A._getEv,onkeyup:A._getEv,onsubmit:A._getEv,onreset:A._getEv,onselect:A._getEv,onchange:A._getEv})})(Element._attributeTranslations.read.values)}else{if(Prototype.Browser.Gecko&&/rv:1\.8\.0/.test(navigator.userAgent)){Element.Methods.setOpacity=function(A,B){A=$(A);A.style.opacity=(B==1)?0.999999:(B==="")?"":(B<0.00001)?0:B;return A}}else{if(Prototype.Browser.WebKit){Element.Methods.setOpacity=function(A,B){A=$(A);A.style.opacity=(B==1||B==="")?"":(B<0.00001)?0:B;if(B==1){if(A.tagName=="IMG"&&A.width){A.width++;A.width--}else{try{var D=document.createTextNode(" ");A.appendChild(D);A.removeChild(D)}catch(C){}}}return A};Element.Methods.cumulativeOffset=function(B){var A=0,C=0;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;if(B.offsetParent==document.body){if(Element.getStyle(B,"position")=="absolute"){break}}B=B.offsetParent}while(B);return Element._returnOffset(C,A)}}}}}if(Prototype.Browser.IE||Prototype.Browser.Opera){Element.Methods.update=function(B,C){B=$(B);if(C&&C.toElement){C=C.toElement()}if(Object.isElement(C)){return B.update().insert(C)}C=Object.toHTML(C);var A=B.tagName.toUpperCase();if(A in Element._insertionTranslations.tags){$A(B.childNodes).each(function(D){B.removeChild(D)});Element._getContentFromAnonymousElement(A,C.stripScripts()).each(function(D){B.appendChild(D)})}else{B.innerHTML=C.stripScripts()}C.evalScripts.bind(C).defer();return B}}if("outerHTML" in document.createElement("div")){Element.Methods.replace=function(C,E){C=$(C);if(E&&E.toElement){E=E.toElement()}if(Object.isElement(E)){C.parentNode.replaceChild(E,C);return C}E=Object.toHTML(E);var D=C.parentNode,B=D.tagName.toUpperCase();if(Element._insertionTranslations.tags[B]){var F=C.next();var A=Element._getContentFromAnonymousElement(B,E.stripScripts());D.removeChild(C);if(F){A.each(function(G){D.insertBefore(G,F)})}else{A.each(function(G){D.appendChild(G)})}}else{C.outerHTML=E.stripScripts()}E.evalScripts.bind(E).defer();return C}}Element._returnOffset=function(B,C){var A=[B,C];A.left=B;A.top=C;return A};Element._getContentFromAnonymousElement=function(C,B){var D=new Element("div"),A=Element._insertionTranslations.tags[C];if(A){D.innerHTML=A[0]+B+A[1];A[2].times(function(){D=D.firstChild})}else{D.innerHTML=B}return $A(D.childNodes)};Element._insertionTranslations={before:function(A,B){A.parentNode.insertBefore(B,A)},top:function(A,B){A.insertBefore(B,A.firstChild)},bottom:function(A,B){A.appendChild(B)},after:function(A,B){A.parentNode.insertBefore(B,A.nextSibling)},tags:{TABLE:["<table>","</table>",1],TBODY:["<table><tbody>","</tbody></table>",2],TR:["<table><tbody><tr>","</tr></tbody></table>",3],TD:["<table><tbody><tr><td>","</td></tr></tbody></table>",4],SELECT:["<select>","</select>",1]}};(function(){Object.extend(this.tags,{THEAD:this.tags.TBODY,TFOOT:this.tags.TBODY,TH:this.tags.TD})}).call(Element._insertionTranslations);Element.Methods.Simulated={hasAttribute:function(A,C){C=Element._attributeTranslations.has[C]||C;var B=$(A).getAttributeNode(C);return B&&B.specified}};Element.Methods.ByTag={};Object.extend(Element,Element.Methods);if(!Prototype.BrowserFeatures.ElementExtensions&&document.createElement("div").__proto__){window.HTMLElement={};window.HTMLElement.prototype=document.createElement("div").__proto__;Prototype.BrowserFeatures.ElementExtensions=true}Element.extend=(function(){if(Prototype.BrowserFeatures.SpecificElementExtensions){return Prototype.K}var A={},B=Element.Methods.ByTag;var C=Object.extend(function(F){if(!F||F._extendedByPrototype||F.nodeType!=1||F==window){return F}var D=Object.clone(A),E=F.tagName,H,G;if(B[E]){Object.extend(D,B[E])}for(H in D){G=D[H];if(Object.isFunction(G)&&!(H in F)){F[H]=G.methodize()}}F._extendedByPrototype=Prototype.emptyFunction;return F},{refresh:function(){if(!Prototype.BrowserFeatures.ElementExtensions){Object.extend(A,Element.Methods);Object.extend(A,Element.Methods.Simulated)}}});C.refresh();return C})();Element.hasAttribute=function(A,B){if(A.hasAttribute){return A.hasAttribute(B)}return Element.Methods.Simulated.hasAttribute(A,B)};Element.addMethods=function(C){var I=Prototype.BrowserFeatures,D=Element.Methods.ByTag;if(!C){Object.extend(Form,Form.Methods);Object.extend(Form.Element,Form.Element.Methods);Object.extend(Element.Methods.ByTag,{FORM:Object.clone(Form.Methods),INPUT:Object.clone(Form.Element.Methods),SELECT:Object.clone(Form.Element.Methods),TEXTAREA:Object.clone(Form.Element.Methods)})}if(arguments.length==2){var B=C;C=arguments[1]}if(!B){Object.extend(Element.Methods,C||{})}else{if(Object.isArray(B)){B.each(H)}else{H(B)}}function H(F){F=F.toUpperCase();if(!Element.Methods.ByTag[F]){Element.Methods.ByTag[F]={}}Object.extend(Element.Methods.ByTag[F],C)}function A(L,K,F){F=F||false;for(var N in L){var M=L[N];if(!Object.isFunction(M)){continue}if(!F||!(N in K)){K[N]=M.methodize()}}}function E(L){var F;var K={OPTGROUP:"OptGroup",TEXTAREA:"TextArea",P:"Paragraph",FIELDSET:"FieldSet",UL:"UList",OL:"OList",DL:"DList",DIR:"Directory",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading",Q:"Quote",INS:"Mod",DEL:"Mod",A:"Anchor",IMG:"Image",CAPTION:"TableCaption",COL:"TableCol",COLGROUP:"TableCol",THEAD:"TableSection",TFOOT:"TableSection",TBODY:"TableSection",TR:"TableRow",TH:"TableCell",TD:"TableCell",FRAMESET:"FrameSet",IFRAME:"IFrame"};if(K[L]){F="HTML"+K[L]+"Element"}if(window[F]){return window[F]}F="HTML"+L+"Element";if(window[F]){return window[F]}F="HTML"+L.capitalize()+"Element";if(window[F]){return window[F]}window[F]={};window[F].prototype=document.createElement(L).__proto__;return window[F]}if(I.ElementExtensions){A(Element.Methods,HTMLElement.prototype);A(Element.Methods.Simulated,HTMLElement.prototype,true)}if(I.SpecificElementExtensions){for(var J in Element.Methods.ByTag){var G=E(J);if(Object.isUndefined(G)){continue}A(D[J],G.prototype)}}Object.extend(Element,Element.Methods);delete Element.ByTag;if(Element.extend.refresh){Element.extend.refresh()}Element.cache={}};document.viewport={getDimensions:function(){var A={};var C=Prototype.Browser;$w("width height").each(function(E){var B=E.capitalize();A[E]=(C.WebKit&&!document.evaluate)?self["inner"+B]:(C.Opera)?document.body["client"+B]:document.documentElement["client"+B]});return A},getWidth:function(){return this.getDimensions().width},getHeight:function(){return this.getDimensions().height},getScrollOffsets:function(){return Element._returnOffset(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop)}};var Selector=Class.create({initialize:function(A){this.expression=A.strip();this.compileMatcher()},shouldUseXPath:function(){if(!Prototype.BrowserFeatures.XPath){return false}var A=this.expression;if(Prototype.Browser.WebKit&&(A.include("-of-type")||A.include(":empty"))){return false}if((/(\[[\w-]*?:|:checked)/).test(this.expression)){return false}return true},compileMatcher:function(){if(this.shouldUseXPath()){return this.compileXPathMatcher()}var e=this.expression,ps=Selector.patterns,h=Selector.handlers,c=Selector.criteria,le,p,m;if(Selector._cache[e]){this.matcher=Selector._cache[e];return }this.matcher=["this.matcher = function(root) {","var r = root, h = Selector.handlers, c = false, n;"];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i in ps){p=ps[i];if(m=e.match(p)){this.matcher.push(Object.isFunction(c[i])?c[i](m):new Template(c[i]).evaluate(m));e=e.replace(m[0],"");break}}}this.matcher.push("return h.unique(n);\n}");eval(this.matcher.join("\n"));Selector._cache[this.expression]=this.matcher},compileXPathMatcher:function(){var E=this.expression,F=Selector.patterns,B=Selector.xpath,D,A;if(Selector._cache[E]){this.xpath=Selector._cache[E];return }this.matcher=[".//*"];while(E&&D!=E&&(/\S/).test(E)){D=E;for(var C in F){if(A=E.match(F[C])){this.matcher.push(Object.isFunction(B[C])?B[C](A):new Template(B[C]).evaluate(A));E=E.replace(A[0],"");break}}}this.xpath=this.matcher.join("");Selector._cache[this.expression]=this.xpath},findElements:function(A){A=A||document;if(this.xpath){return document._getElementsByXPath(this.xpath,A)}return this.matcher(A)},match:function(H){this.tokens=[];var L=this.expression,A=Selector.patterns,E=Selector.assertions;var B,D,F;while(L&&B!==L&&(/\S/).test(L)){B=L;for(var I in A){D=A[I];if(F=L.match(D)){if(E[I]){this.tokens.push([I,Object.clone(F)]);L=L.replace(F[0],"")}else{return this.findElements(document).include(H)}}}}var K=true,C,J;for(var I=0,G;G=this.tokens[I];I++){C=G[0],J=G[1];if(!Selector.assertions[C](H,J)){K=false;break}}return K},toString:function(){return this.expression},inspect:function(){return"#<Selector:"+this.expression.inspect()+">"}});Object.extend(Selector,{_cache:{},xpath:{descendant:"//*",child:"/*",adjacent:"/following-sibling::*[1]",laterSibling:"/following-sibling::*",tagName:function(A){if(A[1]=="*"){return""}return"[local-name()='"+A[1].toLowerCase()+"' or local-name()='"+A[1].toUpperCase()+"']"},className:"[contains(concat(' ', @class, ' '), ' #{1} ')]",id:"[@id='#{1}']",attrPresence:function(A){A[1]=A[1].toLowerCase();return new Template("[@#{1}]").evaluate(A)},attr:function(A){A[1]=A[1].toLowerCase();A[3]=A[5]||A[6];return new Template(Selector.xpath.operators[A[2]]).evaluate(A)},pseudo:function(A){var B=Selector.xpath.pseudos[A[1]];if(!B){return""}if(Object.isFunction(B)){return B(A)}return new Template(Selector.xpath.pseudos[A[1]]).evaluate(A)},operators:{"=":"[@#{1}='#{3}']","!=":"[@#{1}!='#{3}']","^=":"[starts-with(@#{1}, '#{3}')]","$=":"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']","*=":"[contains(@#{1}, '#{3}')]","~=":"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]","|=":"[contains(concat('-', @#{1}, '-'), '-#{3}-')]"},pseudos:{"first-child":"[not(preceding-sibling::*)]","last-child":"[not(following-sibling::*)]","only-child":"[not(preceding-sibling::* or following-sibling::*)]",empty:"[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",checked:"[@checked]",disabled:"[@disabled]",enabled:"[not(@disabled)]",not:function(B){var H=B[6],G=Selector.patterns,A=Selector.xpath,E,C;var F=[];while(H&&E!=H&&(/\S/).test(H)){E=H;for(var D in G){if(B=H.match(G[D])){C=Object.isFunction(A[D])?A[D](B):new Template(A[D]).evaluate(B);F.push("("+C.substring(1,C.length-1)+")");H=H.replace(B[0],"");break}}}return"[not("+F.join(" and ")+")]"},"nth-child":function(A){return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ",A)},"nth-last-child":function(A){return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ",A)},"nth-of-type":function(A){return Selector.xpath.pseudos.nth("position() ",A)},"nth-last-of-type":function(A){return Selector.xpath.pseudos.nth("(last() + 1 - position()) ",A)},"first-of-type":function(A){A[6]="1";return Selector.xpath.pseudos["nth-of-type"](A)},"last-of-type":function(A){A[6]="1";return Selector.xpath.pseudos["nth-last-of-type"](A)},"only-of-type":function(A){var B=Selector.xpath.pseudos;return B["first-of-type"](A)+B["last-of-type"](A)},nth:function(E,C){var F,G=C[6],B;if(G=="even"){G="2n+0"}if(G=="odd"){G="2n+1"}if(F=G.match(/^(\d+)$/)){return"["+E+"= "+F[1]+"]"}if(F=G.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(F[1]=="-"){F[1]=-1}var D=F[1]?Number(F[1]):1;var A=F[2]?Number(F[2]):0;B="[((#{fragment} - #{b}) mod #{a} = 0) and ((#{fragment} - #{b}) div #{a} >= 0)]";return new Template(B).evaluate({fragment:E,a:D,b:A})}}}},criteria:{tagName:'n = h.tagName(n, r, "#{1}", c);      c = false;',className:'n = h.className(n, r, "#{1}", c);    c = false;',id:'n = h.id(n, r, "#{1}", c);           c = false;',attrPresence:'n = h.attrPresence(n, r, "#{1}", c); c = false;',attr:function(A){A[3]=(A[5]||A[6]);return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(A)},pseudo:function(A){if(A[6]){A[6]=A[6].replace(/"/g,'\\"')}return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(A)},descendant:'c = "descendant";',child:'c = "child";',adjacent:'c = "adjacent";',laterSibling:'c = "laterSibling";'},patterns:{laterSibling:/^\s*~\s*/,child:/^\s*>\s*/,adjacent:/^\s*\+\s*/,descendant:/^\s/,tagName:/^\s*(\*|[\w\-]+)(\b|$)?/,id:/^#([\w\-\*]+)(\b|$)/,className:/^\.([\w\-\*]+)(\b|$)/,pseudo:/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,attrPresence:/^\[([\w]+)\]/,attr:/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/},assertions:{tagName:function(A,B){return B[1].toUpperCase()==A.tagName.toUpperCase()},className:function(A,B){return Element.hasClassName(A,B[1])},id:function(A,B){return A.id===B[1]},attrPresence:function(A,B){return Element.hasAttribute(A,B[1])},attr:function(B,C){var A=Element.readAttribute(B,C[1]);return A&&Selector.operators[C[2]](A,C[5]||C[6])}},handlers:{concat:function(B,A){for(var C=0,D;D=A[C];C++){B.push(D)}return B},mark:function(A){var D=Prototype.emptyFunction;for(var B=0,C;C=A[B];B++){C._countedByPrototype=D}return A},unmark:function(A){for(var B=0,C;C=A[B];B++){C._countedByPrototype=undefined}return A},index:function(A,D,G){A._countedByPrototype=Prototype.emptyFunction;if(D){for(var B=A.childNodes,E=B.length-1,C=1;E>=0;E--){var F=B[E];if(F.nodeType==1&&(!G||F._countedByPrototype)){F.nodeIndex=C++}}}else{for(var E=0,C=1,B=A.childNodes;F=B[E];E++){if(F.nodeType==1&&(!G||F._countedByPrototype)){F.nodeIndex=C++}}}},unique:function(B){if(B.length==0){return B}var D=[],E;for(var C=0,A=B.length;C<A;C++){if(!(E=B[C])._countedByPrototype){E._countedByPrototype=Prototype.emptyFunction;D.push(Element.extend(E))}}return Selector.handlers.unmark(D)},descendant:function(A){var D=Selector.handlers;for(var C=0,B=[],E;E=A[C];C++){D.concat(B,E.getElementsByTagName("*"))}return B},child:function(A){var E=Selector.handlers;for(var D=0,C=[],F;F=A[D];D++){for(var B=0,G;G=F.childNodes[B];B++){if(G.nodeType==1&&G.tagName!="!"){C.push(G)}}}return C},adjacent:function(A){for(var C=0,B=[],E;E=A[C];C++){var D=this.nextElementSibling(E);if(D){B.push(D)}}return B},laterSibling:function(A){var D=Selector.handlers;for(var C=0,B=[],E;E=A[C];C++){D.concat(B,Element.nextSiblings(E))}return B},nextElementSibling:function(A){while(A=A.nextSibling){if(A.nodeType==1){return A}}return null},previousElementSibling:function(A){while(A=A.previousSibling){if(A.nodeType==1){return A}}return null},tagName:function(A,H,C,B){var I=C.toUpperCase();var E=[],G=Selector.handlers;if(A){if(B){if(B=="descendant"){for(var F=0,D;D=A[F];F++){G.concat(E,D.getElementsByTagName(C))}return E}else{A=this[B](A)}if(C=="*"){return A}}for(var F=0,D;D=A[F];F++){if(D.tagName.toUpperCase()===I){E.push(D)}}return E}else{return H.getElementsByTagName(C)}},id:function(B,A,H,F){var G=$(H),D=Selector.handlers;if(!G){return[]}if(!B&&A==document){return[G]}if(B){if(F){if(F=="child"){for(var C=0,E;E=B[C];C++){if(G.parentNode==E){return[G]}}}else{if(F=="descendant"){for(var C=0,E;E=B[C];C++){if(Element.descendantOf(G,E)){return[G]}}}else{if(F=="adjacent"){for(var C=0,E;E=B[C];C++){if(Selector.handlers.previousElementSibling(G)==E){return[G]}}}else{B=D[F](B)}}}}for(var C=0,E;E=B[C];C++){if(E==G){return[G]}}return[]}return(G&&Element.descendantOf(G,A))?[G]:[]},className:function(B,A,C,D){if(B&&D){B=this[D](B)}return Selector.handlers.byClassName(B,A,C)},byClassName:function(C,B,F){if(!C){C=Selector.handlers.descendant([B])}var H=" "+F+" ";for(var E=0,D=[],G,A;G=C[E];E++){A=G.className;if(A.length==0){continue}if(A==F||(" "+A+" ").include(H)){D.push(G)}}return D},attrPresence:function(C,B,A,G){if(!C){C=B.getElementsByTagName("*")}if(C&&G){C=this[G](C)}var E=[];for(var D=0,F;F=C[D];D++){if(Element.hasAttribute(F,A)){E.push(F)}}return E},attr:function(A,I,H,J,C,B){if(!A){A=I.getElementsByTagName("*")}if(A&&B){A=this[B](A)}var K=Selector.operators[C],F=[];for(var E=0,D;D=A[E];E++){var G=Element.readAttribute(D,H);if(G===null){continue}if(K(G,J)){F.push(D)}}return F},pseudo:function(B,C,E,A,D){if(B&&D){B=this[D](B)}if(!B){B=A.getElementsByTagName("*")}return Selector.pseudos[C](B,E,A)}},pseudos:{"first-child":function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(Selector.handlers.previousElementSibling(E)){continue}C.push(E)}return C},"last-child":function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(Selector.handlers.nextElementSibling(E)){continue}C.push(E)}return C},"only-child":function(B,G,A){var E=Selector.handlers;for(var D=0,C=[],F;F=B[D];D++){if(!E.previousElementSibling(F)&&!E.nextElementSibling(F)){C.push(F)}}return C},"nth-child":function(B,C,A){return Selector.pseudos.nth(B,C,A)},"nth-last-child":function(B,C,A){return Selector.pseudos.nth(B,C,A,true)},"nth-of-type":function(B,C,A){return Selector.pseudos.nth(B,C,A,false,true)},"nth-last-of-type":function(B,C,A){return Selector.pseudos.nth(B,C,A,true,true)},"first-of-type":function(B,C,A){return Selector.pseudos.nth(B,"1",A,false,true)},"last-of-type":function(B,C,A){return Selector.pseudos.nth(B,"1",A,true,true)},"only-of-type":function(B,D,A){var C=Selector.pseudos;return C["last-of-type"](C["first-of-type"](B,D,A),D,A)},getIndices:function(B,A,C){if(B==0){return A>0?[A]:[]}return $R(1,C).inject([],function(D,E){if(0==(E-A)%B&&(E-A)/B>=0){D.push(E)}return D})},nth:function(A,L,N,K,C){if(A.length==0){return[]}if(L=="even"){L="2n+0"}if(L=="odd"){L="2n+1"}var J=Selector.handlers,I=[],B=[],E;J.mark(A);for(var H=0,D;D=A[H];H++){if(!D.parentNode._countedByPrototype){J.index(D.parentNode,K,C);B.push(D.parentNode)}}if(L.match(/^\d+$/)){L=Number(L);for(var H=0,D;D=A[H];H++){if(D.nodeIndex==L){I.push(D)}}}else{if(E=L.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(E[1]=="-"){E[1]=-1}var O=E[1]?Number(E[1]):1;var M=E[2]?Number(E[2]):0;var P=Selector.pseudos.getIndices(O,M,A.length);for(var H=0,D,F=P.length;D=A[H];H++){for(var G=0;G<F;G++){if(D.nodeIndex==P[G]){I.push(D)}}}}}J.unmark(A);J.unmark(B);return I},empty:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.tagName=="!"||(E.firstChild&&!E.innerHTML.match(/^\s*$/))){continue}C.push(E)}return C},not:function(A,D,I){var G=Selector.handlers,J,C;var H=new Selector(D).findElements(I);G.mark(H);for(var F=0,E=[],B;B=A[F];F++){if(!B._countedByPrototype){E.push(B)}}G.unmark(H);return E},enabled:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(!E.disabled){C.push(E)}}return C},disabled:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.disabled){C.push(E)}}return C},checked:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.checked){C.push(E)}}return C}},operators:{"=":function(B,A){return B==A},"!=":function(B,A){return B!=A},"^=":function(B,A){return B.startsWith(A)},"$=":function(B,A){return B.endsWith(A)},"*=":function(B,A){return B.include(A)},"~=":function(B,A){return(" "+B+" ").include(" "+A+" ")},"|=":function(B,A){return("-"+B.toUpperCase()+"-").include("-"+A.toUpperCase()+"-")}},split:function(B){var A=[];B.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/,function(C){A.push(C[1].strip())});return A},matchElements:function(F,G){var E=$$(G),D=Selector.handlers;D.mark(E);for(var C=0,B=[],A;A=F[C];C++){if(A._countedByPrototype){B.push(A)}}D.unmark(E);return B},findElement:function(B,C,A){if(Object.isNumber(C)){A=C;C=false}return Selector.matchElements(B,C||"*")[A||0]},findChildElements:function(E,G){G=Selector.split(G.join(","));var D=[],F=Selector.handlers;for(var C=0,B=G.length,A;C<B;C++){A=new Selector(G[C].strip());F.concat(D,A.findElements(E))}return(B>1)?F.unique(D):D}});if(Prototype.Browser.IE){Object.extend(Selector.handlers,{concat:function(B,A){for(var C=0,D;D=A[C];C++){if(D.tagName!=="!"){B.push(D)}}return B},unmark:function(A){for(var B=0,C;C=A[B];B++){C.removeAttribute("_countedByPrototype")}return A}})}function $$(){return Selector.findChildElements(document,$A(arguments))}var Form={reset:function(A){$(A).reset();return A},serializeElements:function(G,B){if(typeof B!="object"){B={hash:!!B}}else{if(Object.isUndefined(B.hash)){B.hash=true}}var C,F,A=false,E=B.submit;var D=G.inject({},function(H,I){if(!I.disabled&&I.name){C=I.name;F=$(I).getValue();if(F!=null&&(I.type!="submit"||(!A&&E!==false&&(!E||C==E)&&(A=true)))){if(C in H){if(!Object.isArray(H[C])){H[C]=[H[C]]}H[C].push(F)}else{H[C]=F}}}return H});return B.hash?D:Object.toQueryString(D)}};Form.Methods={serialize:function(B,A){return Form.serializeElements(Form.getElements(B),A)},getElements:function(A){return $A($(A).getElementsByTagName("*")).inject([],function(B,C){if(Form.Element.Serializers[C.tagName.toLowerCase()]){B.push(Element.extend(C))}return B})},getInputs:function(G,C,D){G=$(G);var A=G.getElementsByTagName("input");if(!C&&!D){return $A(A).map(Element.extend)}for(var E=0,H=[],F=A.length;E<F;E++){var B=A[E];if((C&&B.type!=C)||(D&&B.name!=D)){continue}H.push(Element.extend(B))}return H},disable:function(A){A=$(A);Form.getElements(A).invoke("disable");return A},enable:function(A){A=$(A);Form.getElements(A).invoke("enable");return A},findFirstElement:function(B){var C=$(B).getElements().findAll(function(D){return"hidden"!=D.type&&!D.disabled});var A=C.findAll(function(D){return D.hasAttribute("tabIndex")&&D.tabIndex>=0}).sortBy(function(D){return D.tabIndex}).first();return A?A:C.find(function(D){return["input","select","textarea"].include(D.tagName.toLowerCase())})},focusFirstElement:function(A){A=$(A);A.findFirstElement().activate();return A},request:function(B,A){B=$(B),A=Object.clone(A||{});var D=A.parameters,C=B.readAttribute("action")||"";if(C.blank()){C=window.location.href}A.parameters=B.serialize(true);if(D){if(Object.isString(D)){D=D.toQueryParams()}Object.extend(A.parameters,D)}if(B.hasAttribute("method")&&!A.method){A.method=B.method}return new Ajax.Request(C,A)}};Form.Element={focus:function(A){$(A).focus();return A},select:function(A){$(A).select();return A}};Form.Element.Methods={serialize:function(A){A=$(A);if(!A.disabled&&A.name){var B=A.getValue();if(B!=undefined){var C={};C[A.name]=B;return Object.toQueryString(C)}}return""},getValue:function(A){A=$(A);var B=A.tagName.toLowerCase();return Form.Element.Serializers[B](A)},setValue:function(A,B){A=$(A);var C=A.tagName.toLowerCase();Form.Element.Serializers[C](A,B);return A},clear:function(A){$(A).value="";return A},present:function(A){return $(A).value!=""},activate:function(A){A=$(A);try{A.focus();if(A.select&&(A.tagName.toLowerCase()!="input"||!["button","reset","submit"].include(A.type))){A.select()}}catch(B){}return A},disable:function(A){A=$(A);A.blur();A.disabled=true;return A},enable:function(A){A=$(A);A.disabled=false;return A}};var Field=Form.Element;var $F=Form.Element.Methods.getValue;Form.Element.Serializers={input:function(A,B){switch(A.type.toLowerCase()){case"checkbox":case"radio":return Form.Element.Serializers.inputSelector(A,B);default:return Form.Element.Serializers.textarea(A,B)}},inputSelector:function(A,B){if(Object.isUndefined(B)){return A.checked?A.value:null}else{A.checked=!!B}},textarea:function(A,B){if(Object.isUndefined(B)){return A.value}else{A.value=B}},select:function(D,A){if(Object.isUndefined(A)){return this[D.type=="select-one"?"selectOne":"selectMany"](D)}else{var C,F,G=!Object.isArray(A);for(var B=0,E=D.length;B<E;B++){C=D.options[B];F=this.optionValue(C);if(G){if(F==A){C.selected=true;return }}else{C.selected=A.include(F)}}}},selectOne:function(B){var A=B.selectedIndex;return A>=0?this.optionValue(B.options[A]):null},selectMany:function(D){var A,E=D.length;if(!E){return null}for(var C=0,A=[];C<E;C++){var B=D.options[C];if(B.selected){A.push(this.optionValue(B))}}return A},optionValue:function(A){return Element.extend(A).hasAttribute("value")?A.value:A.text}};Abstract.TimedObserver=Class.create(PeriodicalExecuter,{initialize:function($super,A,B,C){$super(C,B);this.element=$(A);this.lastValue=this.getValue()},execute:function(){var A=this.getValue();if(Object.isString(this.lastValue)&&Object.isString(A)?this.lastValue!=A:String(this.lastValue)!=String(A)){this.callback(this.element,A);this.lastValue=A}}});Form.Element.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.serialize(this.element)}});Abstract.EventObserver=Class.create({initialize:function(A,B){this.element=$(A);this.callback=B;this.lastValue=this.getValue();if(this.element.tagName.toLowerCase()=="form"){this.registerFormCallbacks()}else{this.registerCallback(this.element)}},onElementEvent:function(){var A=this.getValue();if(this.lastValue!=A){this.callback(this.element,A);this.lastValue=A}},registerFormCallbacks:function(){Form.getElements(this.element).each(this.registerCallback,this)},registerCallback:function(A){if(A.type){switch(A.type.toLowerCase()){case"checkbox":case"radio":Event.observe(A,"click",this.onElementEvent.bind(this));break;default:Event.observe(A,"change",this.onElementEvent.bind(this));break}}}});Form.Element.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.serialize(this.element)}});if(!window.Event){var Event={}}Object.extend(Event,{KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,KEY_HOME:36,KEY_END:35,KEY_PAGEUP:33,KEY_PAGEDOWN:34,KEY_INSERT:45,cache:{},relatedTarget:function(B){var A;switch(B.type){case"mouseover":A=B.fromElement;break;case"mouseout":A=B.toElement;break;default:return null}return Element.extend(A)}});Event.Methods=(function(){var A;if(Prototype.Browser.IE){var B={0:1,1:4,2:2};A=function(D,C){return D.button==B[C]}}else{if(Prototype.Browser.WebKit){A=function(D,C){switch(C){case 0:return D.which==1&&!D.metaKey;case 1:return D.which==1&&D.metaKey;default:return false}}}else{A=function(D,C){return D.which?(D.which===C+1):(D.button===C)}}}return{isLeftClick:function(C){return A(C,0)},isMiddleClick:function(C){return A(C,1)},isRightClick:function(C){return A(C,2)},element:function(D){var C=Event.extend(D).target;return Element.extend(C.nodeType==Node.TEXT_NODE?C.parentNode:C)},findElement:function(D,F){var C=Event.element(D);if(!F){return C}var E=[C].concat(C.ancestors());return Selector.findElement(E,F,0)},pointer:function(C){return{x:C.pageX||(C.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft)),y:C.pageY||(C.clientY+(document.documentElement.scrollTop||document.body.scrollTop))}},pointerX:function(C){return Event.pointer(C).x},pointerY:function(C){return Event.pointer(C).y},stop:function(C){Event.extend(C);C.preventDefault();C.stopPropagation();C.stopped=true}}})();Event.extend=(function(){var A=Object.keys(Event.Methods).inject({},function(B,C){B[C]=Event.Methods[C].methodize();return B});if(Prototype.Browser.IE){Object.extend(A,{stopPropagation:function(){this.cancelBubble=true},preventDefault:function(){this.returnValue=false},inspect:function(){return"[object Event]"}});return function(B){if(!B){return false}if(B._extendedByPrototype){return B}B._extendedByPrototype=Prototype.emptyFunction;var C=Event.pointer(B);Object.extend(B,{target:B.srcElement,relatedTarget:Event.relatedTarget(B),pageX:C.x,pageY:C.y});return Object.extend(B,A)}}else{Event.prototype=Event.prototype||document.createEvent("HTMLEvents").__proto__;Object.extend(Event.prototype,A);return Prototype.K}})();Object.extend(Event,(function(){var B=Event.cache;function C(J){if(J._prototypeEventID){return J._prototypeEventID[0]}arguments.callee.id=arguments.callee.id||1;return J._prototypeEventID=[++arguments.callee.id]}function G(J){if(J&&J.include(":")){return"dataavailable"}return J}function A(J){return B[J]=B[J]||{}}function F(L,J){var K=A(L);return K[J]=K[J]||[]}function H(K,J,L){var O=C(K);var N=F(O,J);if(N.pluck("handler").include(L)){return false}var M=function(P){if(!Event||!Event.extend||(P.eventName&&P.eventName!=J)){return false}Event.extend(P);L.call(K,P)};M.handler=L;N.push(M);return M}function I(M,J,K){var L=F(M,J);return L.find(function(N){return N.handler==K})}function D(M,J,K){var L=A(M);if(!L[J]){return false}L[J]=L[J].without(I(M,J,K))}function E(){for(var K in B){for(var J in B[K]){B[K][J]=null}}}if(window.attachEvent){window.attachEvent("onunload",E)}return{observe:function(L,J,M){L=$(L);var K=G(J);var N=H(L,J,M);if(!N){return L}if(L.addEventListener){L.addEventListener(K,N,false)}else{L.attachEvent("on"+K,N)}return L},stopObserving:function(L,J,M){L=$(L);var O=C(L),K=G(J);if(!M&&J){F(O,J).each(function(P){L.stopObserving(J,P.handler)});return L}else{if(!J){Object.keys(A(O)).each(function(P){L.stopObserving(P)});return L}}var N=I(O,J,M);if(!N){return L}if(L.removeEventListener){L.removeEventListener(K,N,false)}else{L.detachEvent("on"+K,N)}D(O,J,M);return L},fire:function(L,K,J){L=$(L);if(L==document&&document.createEvent&&!L.dispatchEvent){L=document.documentElement}var M;if(document.createEvent){M=document.createEvent("HTMLEvents");M.initEvent("dataavailable",true,true)}else{M=document.createEventObject();M.eventType="ondataavailable"}M.eventName=K;M.memo=J||{};if(document.createEvent){L.dispatchEvent(M)}else{L.fireEvent(M.eventType,M)}return Event.extend(M)}}})());Object.extend(Event,Event.Methods);Element.addMethods({fire:Event.fire,observe:Event.observe,stopObserving:Event.stopObserving});Object.extend(document,{fire:Element.Methods.fire.methodize(),observe:Element.Methods.observe.methodize(),stopObserving:Element.Methods.stopObserving.methodize(),loaded:false});(function(){var B;function A(){if(document.loaded){return }if(B){window.clearInterval(B)}document.fire("dom:loaded");document.loaded=true}if(document.addEventListener){if(Prototype.Browser.WebKit){B=window.setInterval(function(){if(/loaded|complete/.test(document.readyState)){A()}},0);Event.observe(window,"load",A)}else{document.addEventListener("DOMContentLoaded",A,false)}}else{document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");$("__onDOMContentLoaded").onreadystatechange=function(){if(this.readyState=="complete"){this.onreadystatechange=null;A()}}}})();Hash.toQueryString=Object.toQueryString;var Toggle={display:Element.toggle};Element.Methods.childOf=Element.Methods.descendantOf;var Insertion={Before:function(A,B){return Element.insert(A,{before:B})},Top:function(A,B){return Element.insert(A,{top:B})},Bottom:function(A,B){return Element.insert(A,{bottom:B})},After:function(A,B){return Element.insert(A,{after:B})}};var $continue=new Error('"throw $continue" is deprecated, use "return" instead');var Position={includeScrollOffsets:false,prepare:function(){this.deltaX=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;this.deltaY=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0},within:function(B,A,C){if(this.includeScrollOffsets){return this.withinIncludingScrolloffsets(B,A,C)}this.xcomp=A;this.ycomp=C;this.offset=Element.cumulativeOffset(B);return(C>=this.offset[1]&&C<this.offset[1]+B.offsetHeight&&A>=this.offset[0]&&A<this.offset[0]+B.offsetWidth)},withinIncludingScrolloffsets:function(B,A,D){var C=Element.cumulativeScrollOffset(B);this.xcomp=A+C[0]-this.deltaX;this.ycomp=D+C[1]-this.deltaY;this.offset=Element.cumulativeOffset(B);return(this.ycomp>=this.offset[1]&&this.ycomp<this.offset[1]+B.offsetHeight&&this.xcomp>=this.offset[0]&&this.xcomp<this.offset[0]+B.offsetWidth)},overlap:function(B,A){if(!B){return 0}if(B=="vertical"){return((this.offset[1]+A.offsetHeight)-this.ycomp)/A.offsetHeight}if(B=="horizontal"){return((this.offset[0]+A.offsetWidth)-this.xcomp)/A.offsetWidth}},cumulativeOffset:Element.Methods.cumulativeOffset,positionedOffset:Element.Methods.positionedOffset,absolutize:function(A){Position.prepare();return Element.absolutize(A)},relativize:function(A){Position.prepare();return Element.relativize(A)},realOffset:Element.Methods.cumulativeScrollOffset,offsetParent:Element.Methods.getOffsetParent,page:Element.Methods.viewportOffset,clone:function(B,C,A){A=A||{};return Element.clonePosition(C,B,A)}};if(!document.getElementsByClassName){document.getElementsByClassName=function(B){function A(C){return C.blank()?null:"[contains(concat(' ', @class, ' '), ' "+C+" ')]"}B.getElementsByClassName=Prototype.BrowserFeatures.XPath?function(C,E){E=E.toString().strip();var D=/\s/.test(E)?$w(E).map(A).join(""):A(E);return D?document._getElementsByXPath(".//*"+D,C):[]}:function(E,F){F=F.toString().strip();var G=[],H=(/\s/.test(F)?$w(F):null);if(!H&&!F){return G}var C=$(E).getElementsByTagName("*");F=" "+F+" ";for(var D=0,J,I;J=C[D];D++){if(J.className&&(I=" "+J.className+" ")&&(I.include(F)||(H&&H.all(function(K){return !K.toString().blank()&&I.include(" "+K+" ")})))){G.push(Element.extend(J))}}return G};return function(D,C){return $(C||document.body).getElementsByClassName(D)}}(Element.Methods)}Element.ClassNames=Class.create();Element.ClassNames.prototype={initialize:function(A){this.element=$(A)},_each:function(A){this.element.className.split(/\s+/).select(function(B){return B.length>0})._each(A)},set:function(A){this.element.className=A},add:function(A){if(this.include(A)){return }this.set($A(this).concat(A).join(" "))},remove:function(A){if(!this.include(A)){return }this.set($A(this).without(A).join(" "))},toString:function(){return $A(this).join(" ")}};Object.extend(Element.ClassNames.prototype,Enumerable);Element.addMethods();/*
moo.fx, simple effects library built with prototype.js (http://prototype.conio.net).
by Valerio Proietti (http://mad4milk.net) MIT-style LICENSE.
for more info (http://moofx.mad4milk.net).
Sunday, March 05, 2006
v 1.2.3
*/
 
moo_fx_js_version = "$Revision: 1.1 $";

var fx = new Object();
//base
fx.Base = function(){};
fx.Base.prototype = {
	setOptions: function(options) {
	this.options = {
		duration: 500,
		onComplete: '',
		transition: fx.sinoidal
	}
	Object.extend(this.options, options || {});
	},

	step: function() {
		var time  = (new Date).getTime();
		if (time >= this.options.duration+this.startTime) {
			this.now = this.to;
			clearInterval (this.timer);
			this.timer = null;
			if (this.options.onComplete) setTimeout(this.options.onComplete.bind(this), 10);
		}
		else {
			var Tpos = (time - this.startTime) / (this.options.duration);
			this.now = this.options.transition(Tpos) * (this.to-this.from) + this.from;
		}
		this.increase();
	},

	custom: function(from, to) {
		if (this.timer != null) return;
		this.from = from;
		this.to = to;
		this.startTime = (new Date).getTime();
		this.timer = setInterval (this.step.bind(this), 13);
	},

	hide: function() {
		this.now = 0;
		this.increase();
	},

	clearTimer: function() {
		clearInterval(this.timer);
		this.timer = null;
	}
}

//stretchers
fx.Layout = Class.create();
fx.Layout.prototype = Object.extend(new fx.Base(), {
	initialize: function(el, options) {
		this.el = $(el);
		this.el.style.overflow = "hidden";
		this.iniWidth = this.el.offsetWidth;
		this.iniHeight = this.el.offsetHeight;
		this.setOptions(options);
	}
});

fx.Height = Class.create();
Object.extend(Object.extend(fx.Height.prototype, fx.Layout.prototype), {	
	increase: function() {
		this.el.style.height = this.now + "px";
	},

	toggle: function() {
		if (this.el.offsetHeight > 0) this.custom(this.el.offsetHeight, 0);
		else this.custom(0, this.el.scrollHeight);
	}
});

fx.Width = Class.create();
Object.extend(Object.extend(fx.Width.prototype, fx.Layout.prototype), {	
	increase: function() {
		this.el.style.width = this.now + "px";
	},

	toggle: function(){
		if (this.el.offsetWidth > 0) this.custom(this.el.offsetWidth, 0);
		else this.custom(0, this.iniWidth);
	}
});

//fader
fx.Opacity = Class.create();
fx.Opacity.prototype = Object.extend(new fx.Base(), {
	initialize: function(el, options) {
		this.el = $(el);
		this.now = 1;
		this.increase();
		this.setOptions(options);
	},

	increase: function() {
		if (this.now == 1 && (/Firefox/.test(navigator.userAgent))) this.now = 0.9999;
		this.setOpacity(this.now);
	},
	
	setOpacity: function(opacity) {
		if (opacity == 0 && this.el.style.visibility != "hidden") this.el.style.visibility = "hidden";
		else if (this.el.style.visibility != "visible") this.el.style.visibility = "visible";
		if (window.ActiveXObject) this.el.style.filter = "alpha(opacity=" + opacity*100 + ")";
		this.el.style.opacity = opacity;
	},

	toggle: function() {
		if (this.now > 0) this.custom(1, 0);
		else this.custom(0, 1);
	}
});

//transitions
fx.sinoidal = function(pos){
	return ((-Math.cos(pos*Math.PI)/2) + 0.5);
	//this transition is from script.aculo.us
}
fx.linear = function(pos){
	return pos;
}
fx.cubic = function(pos){
	return Math.pow(pos, 3);
}
fx.circ = function(pos){
	return Math.sqrt(pos);
}


fx.omgHeight = Class.create();
Object.extend(Object.extend(fx.omgHeight.prototype, fx.Layout.prototype), {	
	nominalHeight: 20,
	increase: function() {
		this.el.style.height = this.now + "px";
	},

	toggle: function() {
		if (this.el.offsetHeight > this.nominalHeight) this.custom(this.el.offsetHeight, this.nominalHeight);
		else this.custom(this.nominalHeight, this.el.scrollHeight);
	}
});
/*
moo.fx pack, effects extensions for moo.fx.
by Valerio Proietti (http://mad4milk.net) MIT-style LICENSE
for more info visit (http://moofx.mad4milk.net).
Friday, April 14, 2006
v 1.2.4
*/
 
moo_fx_pack_js_version = "$Revision: 1.1 $";

//smooth scroll
fx.Scroll = Class.create();
fx.Scroll.prototype = Object.extend(new fx.Base(), {
	initialize: function(options) {
		this.setOptions(options);
	},

	scrollTo: function(el){
		var dest = Position.cumulativeOffset($(el))[1];
		var client = window.innerHeight || document.documentElement.clientHeight;
		var full = document.documentElement.scrollHeight;
		var top = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
		if (dest+client > full) this.custom(top, dest - client + (full-dest));
		else this.custom(top, dest);
	},

	increase: function(){
		window.scrollTo(0, this.now);
	}
});

//text size modify, now works with pixels too.
fx.Text = Class.create();
fx.Text.prototype = Object.extend(new fx.Base(), {
	initialize: function(el, options) {
		this.el = $(el);
		this.setOptions(options);
		if (!this.options.unit) this.options.unit = "em";
	},

	increase: function() {
		this.el.style.fontSize = this.now + this.options.unit;
	}
});

//composition effect: widht/height/opacity
fx.Combo = Class.create();
fx.Combo.prototype = {
	setOptions: function(options) {
		this.options = {
			opacity: true,
			height: true,
			width: false
		}
		Object.extend(this.options, options || {});
	},

	initialize: function(el, options) {
		this.el = $(el);
		this.setOptions(options);
		if (this.options.opacity) {
			this.o = new fx.Opacity(el, options);
			options.onComplete = null;
		}
		if (this.options.height) {
			this.h = new fx.Height(el, options);
			options.onComplete = null;
		}
		if (this.options.width) this.w = new fx.Width(el, options);
	},
	
	toggle: function() { this.checkExec('toggle'); },

	hide: function(){ this.checkExec('hide'); },
	
	clearTimer: function(){ this.checkExec('clearTimer'); },
	
	checkExec: function(func){
		if (this.o) this.o[func]();
		if (this.h) this.h[func]();
		if (this.w) this.w[func]();
	},
	
	//only if width+height
	resizeTo: function(hto, wto) {
		if (this.h && this.w) {
			this.h.custom(this.el.offsetHeight, this.el.offsetHeight + hto);
			this.w.custom(this.el.offsetWidth, this.el.offsetWidth + wto);
		}
	},

	customSize: function(hto, wto) {
		if (this.h && this.w) {
			this.h.custom(this.el.offsetHeight, hto);
			this.w.custom(this.el.offsetWidth, wto);
		}
	}
}

fx.Accordion = Class.create();
fx.Accordion.prototype = {
	setOptions: function(options) {
		this.options = {
			delay: 100,
			opacity: false
		}
		Object.extend(this.options, options || {});
	},

	initialize: function(togglers, elements, options) {
		this.elements = elements;
		this.setOptions(options);
		var options = options || '';
		this.fxa = [];
		if (options && options.onComplete) options.onFinish = options.onComplete;
		elements.each(function(el, i){
			options.onComplete = function(){
				if (el.offsetHeight > 0) el.style.height = '1%';
				if (options.onFinish) options.onFinish(el);
			}
			this.fxa[i] = new fx.Combo(el, options);
			this.fxa[i].hide();
		}.bind(this));

		togglers.each(function(tog, i){
			if (typeof tog.onclick == 'function') var exClick = tog.onclick;
			tog.onclick = function(){
				if (exClick) exClick();
				this.showThisHideOpen(elements[i]);
			}.bind(this);
		}.bind(this));
	},

	showThisHideOpen: function(toShow){
		this.elements.each(function(el, j){
			if (el.offsetHeight > 0 && el != toShow) this.clearAndToggle(el, j);
			if (el == toShow && toShow.offsetHeight == 0) setTimeout(function(){this.clearAndToggle(toShow, j);}.bind(this), this.options.delay);
		}.bind(this));
	},

	clearAndToggle: function(el, i){
		this.fxa[i].clearTimer();
		this.fxa[i].toggle();
	}
}

var Remember = new Object();
Remember = function(){};
Remember.prototype = {
	initialize: function(el, options){
		this.el = $(el);
		this.days = 365;
		this.options = options;
		this.effect();
		var cookie = this.readCookie();
		if (cookie) {
			this.fx.now = cookie;
			this.fx.increase();
		}
	},

	//cookie functions based on code by Peter-Paul Koch
	setCookie: function(value) {
		var date = new Date();
		date.setTime(date.getTime()+(this.days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
		document.cookie = this.el+this.el.id+this.prefix+"="+value+expires+"; path=/";
	},

	readCookie: function() {
		var nameEQ = this.el+this.el.id+this.prefix + "=";
		var ca = document.cookie.split(';');
		for(var i=0;c=ca[i];i++) {
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return false;
	},

	custom: function(from, to){
		if (this.fx.now != to) {
			this.setCookie(to);
			this.fx.custom(from, to);
		}
	}
}

fx.RememberHeight = Class.create();
fx.RememberHeight.prototype = Object.extend(new Remember(), {
	effect: function(){
		this.fx = new fx.Height(this.el, this.options);
		this.prefix = 'height';
	},
	
	toggle: function(){
		if (this.el.offsetHeight == 0) this.setCookie(this.el.scrollHeight);
		else this.setCookie(0);
		this.fx.toggle();
	},
	
	resize: function(to){
		this.setCookie(this.el.offsetHeight+to);
		this.fx.custom(this.el.offsetHeight,this.el.offsetHeight+to);
	},

	hide: function(){
		if (!this.readCookie()) {
			this.fx.hide();
		}
	}
});

fx.RememberText = Class.create();
fx.RememberText.prototype = Object.extend(new Remember(), {
	effect: function(){
		this.fx = new fx.Text(this.el, this.options);
		this.prefix = 'text';
	}
});

//useful for-replacement
Array.prototype.iterate = function(func){
	for(var i=0;i<this.length;i++) func(this[i], i);
}
if (!Array.prototype.each) Array.prototype.each = Array.prototype.iterate;

//Easing Equations (c) 2003 Robert Penner, all rights reserved.
//This work is subject to the terms in http://www.robertpenner.com/easing_terms_of_use.html.

//expo
fx.expoIn = function(pos){
	return Math.pow(2, 10 * (pos - 1));
}
fx.expoOut = function(pos){
	return (-Math.pow(2, -10 * pos) + 1);
}

//quad
fx.quadIn = function(pos){
	return Math.pow(pos, 2);
}
fx.quadOut = function(pos){
	return -(pos)*(pos-2);
}

//circ
fx.circOut = function(pos){
	return Math.sqrt(1 - Math.pow(pos-1,2));
}
fx.circIn = function(pos){
	return -(Math.sqrt(1 - Math.pow(pos, 2)) - 1);
}

//back
fx.backIn = function(pos){
	return (pos)*pos*((2.7)*pos - 1.7);
}
fx.backOut = function(pos){
	return ((pos-1)*(pos-1)*((2.7)*(pos-1) + 1.7) + 1);
}

//sine
fx.sineOut = function(pos){
	return Math.sin(pos * (Math.PI/2));
}
fx.sineIn = function(pos){
	return -Math.cos(pos * (Math.PI/2)) + 1;
}
fx.sineInOut = function(pos){
	return -(Math.cos(Math.PI*pos) - 1)/2;
}// Ideally all "Petromedia" code should live in the PM "namespace", eventually it will...
var PM = {};

// prototype.lite uses $c instead of $A, save madness by aliasing it.
$c = $A;

function KB912945_fix(slot) {
	var elem = document.getElementById("to_be_rewritten_"+slot);
	if(elem) document.write(elem.innerHTML);
}

function $v(el) {
	el = $(el);
	if(el && el.value) {
		return el.value;
	}
	if(el && el.firstChild && el.firstChild.nodeValue) {
		return el.firstChild.nodeValue;
	}
	return ''; // who really wants 'undefined' anyway?
}

// create an element, optionally set an ID & add a child.
function $pn(type, id, child) {
	el = document.createElement(type);
	if(id)
		el.id = id;
	if(child)
		el.appendChild(child);
	return el;
}

// create a new text node. useful to dump things in to $pn.
function $pt(v) { return document.createTextNode(v); };


/* z = element to start with, x = tags (* for all), y = class (false for none), w = the behaviour, v = type of event (default is click) */
function applyBehaviour(z,x,y,w,v){
	if(!v) v = "click";
	if(!z) z = document;
	b = $(z).getElementsByTagName(x);
	$c(b).each(function(a){
		if((y == false) || Element.hasClassName(a,y)) {
			addEvent(a, v, w);
		}
	});
}

// function applyBehaviour2(node,selector,action,event) {
// 	$A($(node ? node : document).getElementsBySelector(selector)).each(function(a) {
// 		$(a).observe((event ? event : 'click'), action)
// 	});
// }

function addEvent(obj, evType, fn){
	return Event.observe(obj, evType, fn);
}

function removeEvent(obj, evType, fn, useCapture){
  if (obj.removeEventListener){
    obj.removeEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.detachEvent){
    var r = obj.detachEvent("on"+evType, fn.bind(obj));
    return r;
  } else {
    alert("Handler could not be removed");
  }
}

function addToggler(toggle, toggleable, doNow, widthMode, nominalHeight) {
	r = function() {
		if($(toggle) && $(toggleable)) {
			if(widthMode) {
				$(toggleable).fx = new fx.Width($(toggleable), {duration: 500});
			} else {
				$(toggleable).fx = new fx.omgHeight($(toggleable), {duration: 500});
			}
			if(nominalHeight || nominalHeight === 0) $(toggleable).fx.nominalHeight = nominalHeight;
			$(toggleable).toggle = function() {
				this.fx.toggle();
				if($(toggle).doToggleStyle) $(toggle).doToggleStyle();
			};
			$(toggle).onclick = function() {
				this.blur();
				$(toggleable).toggle();
				return false;
			};
		}
	};
	
	if(doNow)	r();
	else      	addEvent(window, 'load', r);
}

Element.addMethods({
	getByTagName: function(el, tagName) {
		childNode = new Array();
		childNode[0] = null;
		i = 0;
		$c(el.childNodes).each(function(child){
			if(child.nodeName == tagName) {
				childNode[i] = child;
				i++;
			}
		});
		return childNode;
	},
	
	toggleStyle: function(workingOn, styleA) {
		workingOn = $(workingOn);
		if(Element.hasClassName(workingOn, styleA)) {
			Element.removeClassName(workingOn, styleA);
		} else {
			Element.addClassName(workingOn, styleA);
		}
	},
	
	toggleStyles: function(el, styleA, styleB) {
		el = $(el);
		if(Element.hasClassName(el, styleA)) {
			Element.removeClassName(el, styleA);
			Element.addClassName(el, styleB);
		} else {
			Element.removeClassName(el, styleB);
			Element.addClassName(el, styleA);		
		}
	},

	tabToMe: function(el) {
		inactiveTab = $(el).parentNode.firstChild;
		while(inactiveTab) {
			if(inactiveTab.nodeName.toLowerCase() == el.nodeName.toLowerCase() && inactiveTab.tabInactiveClass) {
				if(Element.hasClassName(inactiveTab, inactiveTab.tabActiveClass) && inactiveTab.onclosetab) {
					inactiveTab.onclosetab();
				}
				b = inactiveTab.className;
				Element.removeClassName(inactiveTab, inactiveTab.tabActiveClass);
				Element.addClassName(inactiveTab, inactiveTab.tabInactiveClass);
			}
			inactiveTab = inactiveTab.nextSibling;
		}		
		Element.removeClassName(el, el.tabInactiveClass);
		Element.addClassName(el, el.tabActiveClass);
	},

	makeSwitchingTab: function(el, onClickHandler, offClickHandler, inactiveClass, activeClass) {
		el.tabInactiveClass = inactiveClass;
		el.tabActiveClass = activeClass;
		el.onclick = function() { this.tabToMe(); onClickHandler(); };

		if(offClickHandler) el.onclosetab = offClickHandler;

		childer = el.firstChild;
		while(childer) {
			if(childer.nodeName.toLowerCase() == 'a') {
				childer.onclick = function() { this.blur(); return false; };
			}
			childer = childer.nextSibling;
		}
	},

	makeElementAjaxy: function (element) {
		element.href = element.href + "&ajax=1";
		element.onclick = function() {
			parent_node = this.parentNode;
			new Ajax.Updater(parent_node, this.href, {method: 'get', evalScripts: true, onComplete: function(){ 
				$(parent_node.getElementsByTagName('a')[0]).makeElementAjaxy(); 
			}});
			Element.update(parent_node, OMGLib.loadingImageSmall);
			return false;
		};
	},
	
	// These two functions let play with the position of relatively positioned elements
	// without destroying the relativeness.

	relativeOffsetTop: function(el) {
		el.absolutize();
		output = el.offsetTop;
		el.relativize();
		return output;
	},	
	setRelativeOffsetTop: function(el, to) {
		el.absolutize();
		el.style.top = to + "px";
		el.relativize();
	},
	
	relativeOffsetTopAndLeft: function(el) {
		el.absolutize();
		outputTop = el.offsetTop;
		outputLeft = el.offsetLeft;
		el.relativize();
		return { top: outputTop, left: outputLeft };
	}
	
});


function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

function eraseCookie(name) {
	createCookie(name,"",-1);
}

function toggleAutoSignin() {
	document.location.replace("/toggleAutoSignIn?sourcePage="+document.URL);
}

function printit(){}

function nav2(a, c) {
	if(a) {
		a = $(a);
		b = a.getElementsByTagName('select')[0];
		if(b) {
			c = $v(b);
		}
	}
	if(c) {
		document.location = c;
	}
	return false;
}

function nav(formname) {
	opt=document.forms[formname].menu.options[document.forms[formname].menu.selectedIndex].value;
	if (opt!='') {		
		document.location=opt;
	}
} 

function popUp(url, name,x,y,tb,loc,st,mn,sc,rs) {	
	popWin=window.open(url,name,'toolbar='+tb+',location='+loc+',directories=0,status='+st+',menubar='+mn+',scrollbars='+sc+',resizable='+rs+',width='+x+',height='+y);
	self.name = "mainWin"; 
	popWin.opener = self;
	return popWin;
}

function isIE(ver) {
	browserVer = parseInt(navigator.appVersion);
	if (Prototype.Browser.IE && (browserVer >= ver)) {
		return true;
	}
	return false;
}
function URLencode(sStr) {
    return escape(sStr).replace(/\+/g, '%2B').replace(/\"/g,'%22').replace(/\'/g, '%27').replace(/\=/g, '%3D').replace(/\@/g, '%40');
}

function resizeIEFix() {
	var t_body = document.getElementsByTagName('body');
	body = t_body[0];
	var appVersion = navigator.appVersion;
	if ((appVersion.indexOf("MSIE") != -1) && (appVersion.indexOf("Macintosh") == -1))
	{
		if (document.getElementsByTagName)
		{
			var html = document.getElementsByTagName('html');
			if (html[0].offsetWidth) { width = html[0].offsetWidth; }
			if (html[0].offsetHeight) { height = html[0].offsetHeight; }
		}
		if (width < 1020) {	
			body.style.width = width - 4;
			if(Element.hasClassName(body, 'feauxframe')) {
				body.style.height = height - 53;
			} else {
				body.style.height = height - 4;				
			}
			body.hasFixedWidth = true;
		} else if(body.hasFixedWidth) {
			body.style.width = '';
			body.style.height = '';
			body.hasFixedWidth = false;
		}
	}
}

function makeJumpToNext(a, b, limit) {
	$(a).onkeyup = function(e) { 
		if((e.keyCode != 0 && e.keyCode != 9) && this.value.length == limit) {
			$(b).focus();
		}
	};
}

function findPos(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft;
		curtop = obj.offsetTop;
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft;
			curtop += obj.offsetTop;
		}
	}
	return [curleft,curtop];
}

// Date ISO8601 helpers, useful for sending this to/from JSON.

Date.prototype.setISO8601 = function(dString){

   var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/;

   if (dString.toString().match(new RegExp(regexp))) {
      var d = dString.match(new RegExp(regexp));
      var offset = 0;

      this.setUTCFullYear(parseInt(d[1]));
      this.setUTCMonth(d[3] - 1);
      this.setUTCDate(d[5]);
      this.setUTCHours(d[7]);
      this.setUTCMinutes(d[9]);
      this.setUTCSeconds(d[11]);
      if (d[12])
         this.setUTCMilliseconds(parseFloat(d[12]) * 1000);
      else
         this.setUTCMilliseconds(0);
      if (d[13] != 'Z') {
         offset = (d[15] * 60) + parseInt(d[17]);
         offset *= ((d[14] == '-') ? -1 : 1);
         this.setTime(this.getTime() - offset * 60 * 1000);
      }
   }
   else {
      this.setTime(Date.parse(dString));
   }
   return this;
};


Date.prototype.iso8601 = function() {
	return this.UTCstrftime('%Y-%m-%dT%H:%M:%SZ');
};

Date.dateWithISO8601 = function(isoDate) {
	new_date = new Date();
	new_date.setISO8601(isoDate);
	return new_date;
};

/* Class.create2: A add-on to prototype.js Class.create that makes for neater syntax
 * 2007 Patrick Quinn-Graham
 *
 * This software is hereby released into the public domain. Do with it as
 * you please, but with the understanding that it is provided "AS IS" and 
 * without any warranty of any kind.
 *
 * @DEPRECATED: This has now been rolled in to Prototype as of v1.6, you can
 * now just call Class.create({...});. You should only use this if you require
 * multiple mix-ins at creation time.
 *  
 **/

Class.create2 = function() { // create a new class with as many mix-ins as we want.
	var klass = Class.create();
	$A(arguments).each(function(a){
		Object.extend(klass.prototype, a);
	});
	return klass;
};

/* /Class.create2 */

function clickAndReplaceParent(el, loadingMess, theParentNode) {
	if(!theParentNode) theParentNode = el.parentNode;
	if(loadingMess) Element.update(theParentNode, "<img src=\"" + window.ASSET_BASE + "/global/aqua2spinner12.v09.01.12.gif\" alt=\"Loading...\" height=\"12\" width=\"12\"/>");
	new Ajax.Updater(theParentNode, el.href, {method: 'get', evalScripts: true});
}

PM.global = {
	verify: function(ident_num, page_log) {
		Event.observe(window, 'load', function(){
			verify_x = new Image(1,1);
			verify_x.src = '/verify?i='+ident_num+"&p="+page_log;
		});
	},
	log: function(page_url) {
	//	log_x = new Image(1,1);
	//	log_x.src = '/_do_log?i='+ident_num+'&p='+
	},
	
	newExtendedGraphWithWait: function(container, url, options, wait) {
				
		if($(wait).value == 0) {
			
			$(wait).value = 1;
		
			if(options) {
				if(options.createGraphDIV) {
					newDivOptions = Object.extend({'style': 'width:'+(options.width || '100px')+';height:'+(options.height || '100px')+';font-size:0.9em;'}, options.createDivOptions || {});
				
					parentContainer = container;
					container = new Element('div', newDivOptions);
					parentContainer.appendChild(container);
				}
			}
			
			Element.update(container, "<img style=\"background-color: #f2f2f2; padding: 10px;\" src=\"" + window.ASSET_BASE + "/global/aqua2spinner12.v09.01.12.gif\" alt=\"Loading...\" height=\"12\" width=\"12\"/>");
	
			new Ajax.Request(url, {method: 'get', onSuccess: function(transport) {
				 Element.update(container, "");
				//x = transport.responseJSON;
				x = transport.responseText.evalJSON();
	
				if(x.fileFormat == "com.tankerworld.graph.json") {
					//alert('drawing...');
					using = {};
					if(options.overrides) {
						using = Object.extend(x, options.overrides);
					} else {
						using = x;
					}
					
					f = FlotrExtended.draw(container,x.data,using);
					if(options && options.callback) {
						options.callback(f);
					}
				} else if(x.fileFormat == "com.tankerworld_nograph1.graph.json") {
				
					Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph1_nodata.png\">");
				
				} else if(x.fileFormat == "com.tankerworld_nograph2.graph.json") {
				
					Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph2_nodata.png\">");
				
				} else {
					alert("There was an error loading the graph, please try again. (-10)");
				}
			},
			
			onComplete: function() { $(wait).value = 0; }
			
			
			});
			
		}
		
	},
	
	newExtendedGraph: function(container, url, options) {
		
		if(options) {
			if(options.createGraphDIV) {
				newDivOptions = Object.extend({'style': 'width:'+(options.width || '100px')+';height:'+(options.height || '100px')+';font-size:0.9em;'}, options.createDivOptions || {});
			
				parentContainer = container;
				container = new Element('div', newDivOptions);
				parentContainer.appendChild(container);
			}
		}
		
		Element.update(container, "<img style=\"background-color: #f2f2f2; padding: 10px;\" src=\"" + window.ASSET_BASE + "/global/aqua2spinner12.v09.01.12.gif\" alt=\"Loading...\" height=\"12\" width=\"12\"/>");

		new Ajax.Request(url, {method: 'get', onSuccess: function(transport) {
			 Element.update(container, "");
			//x = transport.responseJSON;
			x = transport.responseText.evalJSON();

			if(x.fileFormat == "com.tankerworld.graph.json") {
				//alert('drawing...');
				using = {};
				if(options.overrides) {
					using = Object.extend(x, options.overrides);
				} else {
					using = x;
				}
				
				f = FlotrExtended.draw(container,x.data,using);
				if(options && options.callback) {
					options.callback(f);
				}
			} else if(x.fileFormat == "com.tankerworld_nograph1.graph.json") {
			
				Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph1_nodata.png\">");
			
			} else if(x.fileFormat == "com.tankerworld_nograph2.graph.json") {
			
				Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph2_nodata.png\">");
			
			} else {
				alert("There was an error loading the graph, please try again. (-10)");
			}
		}});
	},
		
	newGraph: function(container, url, options) {
		
		if(options) {
			if(options.createGraphDIV) {
				newDivOptions = Object.extend({'style': 'width:'+(options.width || '100px')+';height:'+(options.height || '100px')+';font-size:0.9em;'}, options.createDivOptions || {});
			
				parentContainer = container;
				container = new Element('div', newDivOptions);
				parentContainer.appendChild(container);
			}
		}
		
		 Element.update(container, "<img style=\"background-color: #f2f2f2; padding: 10px;\" src=\"" + window.ASSET_BASE + "/global/aqua2spinner12.v09.01.12.gif\" alt=\"Loading...\" height=\"12\" width=\"12\"/>");

		new Ajax.Request(url, {method: 'get', onSuccess: function(transport) {
			 Element.update(container, "");
			//x = transport.responseJSON;
			x = transport.responseText.evalJSON();

			if(x.fileFormat == "com.tankerworld.graph.json") {
				//alert('drawing...');
				using = {};
				if(options.overrides) {
					using = Object.extend(x, options.overrides);
				} else {
					using = x;
				}
				
				f = Flotr.draw(container,x.data,using);
				if(options && options.callback) {
					options.callback(f);
				}
			} else if(x.fileFormat == "com.tankerworld_nograph1.graph.json") {
			
				Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph1_nodata.png>");
			
			} else if(x.fileFormat == "com.tankerworld_nograph2.graph.json") {
			
				Element.update(container, "<img src=\"" + window.ASSET_BASE + "/bw/prices/graph2_nodata.png>");
			
			} else {
				alert("There was an error loading the graph, please try again. (-10)");
			}
		}});
	},
	
	reportForumPost: function(report_script, thread_id, post_seq, report_button) {
		$(report_button).update('Reporting...');
		new Ajax.Request(report_script, {
			method: 'post',
			parameters: { 'thread': thread_id, 'post': post_seq },
			onComplete: function(t) {
				r = t.responseJSON;
				if(r.error) {
					alert("An error occurred trying to report this content. Please reload the page and try again.");
				} else {
					$(report_button).update('Thank you for bringing this to our attention. A member of our team will review this post.');	
				}
			}.bind(this)
		});
	},
	
	reportClassifiedAd: function(report_script, ad_id, report_button) {
		$(report_button).update('Reporting...');
		new Ajax.Request(report_script, {
			method: 'post',
			parameters: { 'ad': ad_id },
			onComplete: function(t) {
				r = t.responseJSON;
				if(r.error) {
					alert("An error occurred trying to report this content. Please reload the page and try again.");
				} else {
					$(report_button).update('Thank you for bringing this to our attention. A member of our team will review this ad.');	
				}
			}.bind(this)
		});
	},
	
	radioHasChosen: function(form_radio_name) {
		anyChecked=false;
		$$('input[name="' + form_radio_name + '"]').each(function(a){
			if($(a).checked){
				anyChecked=true;
			}
		});
		return anyChecked;
	},
	
	countdownTimer: function(countdown_id, targetdate, starttext) {
		
		// Get date objects for the current (user) date and the target date.
		this.localtime = new Date();
		this.targetdate = new Date(targetdate);
		
		// Adjust the local date for the difference between time zones. 
		this.localtime.setSeconds(this.localtime.getSeconds() + (this.localtime.getTimezoneOffset() * 60));
		
		// Check to make sure that the target date has not already passed. 
		var remaining = (this.targetdate-this.localtime)/1000;
		if (remaining > 0){
			setTimeout(function(){PM.global.updateCountdown(countdown_id, starttext); }, 1);
		} 
	},
	
	updateCountdown: function(countdown_id, starttext) {
		
		//Check time difference so we don't go past the target date.
		var remaining = (this.targetdate-this.localtime)/1000;		

		// Increment time by one second
		this.localtime.setSeconds(this.localtime.getSeconds()+1);
		
		if (remaining > 0) {
			
			// Get the time remaining in days/hours/minutes/seconds.
			var days = Math.floor(remaining/(60*60*24));//Days until target date
			var hours = Math.floor((remaining-(days*60*60*24))/(60*60));//Hours until target date
			var minutes = Math.floor((remaining-(days*60*60*24)-(hours*60*60))/60);//Minutes until target date
			var seconds = Math.floor(remaining-(days*60*60*24)-(hours*60*60)-(minutes*60));//Seconds until target date
		
			// Left pad numbers with zeros where needed.
			if (hours.toString().length==1) hours = '0' + hours;
			if (minutes.toString().length==1) minutes = '0' + minutes;
			if (seconds.toString().length==1) seconds = '0' + seconds;
			
			// Format the countdown clock, and call the update function again 1 second later.
			$(countdown_id).update(starttext + days + ' days, ' + hours + ':' + minutes + ':' + seconds);
			setTimeout(function(){PM.global.updateCountdown(countdown_id, starttext);}, 1000);
		}
	}
		
		
}; 

PM.global.BasicTabController = Class.create({
	initialize: function() {
		this.basicTabs = Array();
		this.currentTab = 1;
	},
	registerTab : function(tab) {
		this.basicTabs[this.basicTabs.length] = tab;
	},
	setupTabs : function() {
		this.basicTabs.each(function(t){
			t.setup();
		});
	}
});

PM.global.BasicTab = Class.create({
	initialize: function(aTab, aGrip, aIndex, options) {
		this.tab = aTab;
		this.grip = aGrip;
		this.tabIndex = aIndex;
		Object.extend(this, options || {});
		this.btc.registerTab(this);
	},
	setup: function() {
		$(this.tab).makeSwitchingTab(function() { this.onClick(); }.bind(this), 
	 							     function() { this.offClick(); }.bind(this), 'inactiveTab', 'activeTab');
	},
	hide: function() {
		this.offClick();
		$(this.tab).style.display = 'none';
	},
	show: function() {
		$(this.tab).style.display = 'block';
	},
	onClick: function() {
		this.btc.currentTab = this.tabIndex;
		$(this.grip).style.display = 'block';
		this.onActivate();
	},
	offClick: function() {
		//info("Should hide myself " + this.tab);
		$(this.grip).style.display = 'none';
	},
	clear: function() {
		$(this.grip).innerHTML = "&nbsp;";
	},
	activate: function() {
		$(this.tab).onclick();
	},
	onActivate: function() {
		// by default do nothing
	}
});

/*
 dateSwatch is a "helper" for using the Yahoo! UI calendar widget
 Make sure you have, at least, the yahoo, dom, event, and calendar modules from Y!UI.
*/
dateSwatch = Class.create({
	initialize: function(container, lnk, input, defaultDate, title, inputAsCallBack, align) {
		y = YAHOO;
		if(!(y && y.widget && y.widget.Calendar && y.util && y.util.Event)) {
			alert("This control requires several Yahoo! UI javascript components");
		}
		container = $(container);
		this.container = container;
		this.lnk = $(lnk);
		this.alignLeft = (align == "left");
		if(inputAsCallBack) {
			this.inputField = input;
			this.setAsValue = false;
			//info("inputAsRef: TRUE");
		} else {
			this.inputField = $(input);
			this.setAsValue = true;
		}
		this.calendar = new y.widget.Calendar(container.id + "-calendar", container.id, {title: title, close: true});
		this.setDate(defaultDate);
		this.calendar.selectEvent.subscribe(this.dateChange.bind(this), this.calendar.call, true);
		y.util.Event.addListener(lnk, "click", this.showCal.bind(this), this.calendar, true);	
		
		if (navigator.appName == "Microsoft Internet Explorer") {
			this.scrollCheckInterval = setInterval(this.scrollCheck.bind(this), 10);
			this.oldScrollPos = 0;
		}

	},
	scrollCheck: function() {
		if(this.oldScrollPos != document.body.scrollTop) {
			this.oldScrollPos = document.body.scrollTop;
			if(this.onScrollChange) this.onScrollChange();
		}
	},
	setDate: function(defaultDate) {
		if(defaultDate && defaultDate != "") {
			v = defaultDate.split("-");
			fdate = v[1] + "/" + v[2] + "/" + v[0];
			this.calendar.select(fdate);
			var firstDate = this.calendar.getSelectedDates()[0]; 
			this.calendar.cfg.setProperty("pagedate", v[1] + "/" + v[0]);
		}	
		this.calendar.render();
	},
	dateChange: function(t, args) {
		val = args[0][0];
		val[1] = (val[1] < 10) ? 0 + '' + val[1] : val[1];
		val[2] = (val[2] < 10) ? 0 + '' + val[2] : val[2];
		
		if(this.setAsValue) {
			this.inputField.value = val[0] + "-" + val[1] + "-" + val[2];
		} else {	
			this.inputField(val[0] + "-" + val[1] + "-" + val[2]);
		}			
		this.lnk.firstChild.nodeValue = val[0] + "-" + val[1] + "-" + val[2];
		this.calendar.hide();
		this.onScrollChange = null;
	},
	showCal: function() {
		this.calendar.show();
		lnky = findPos(this.lnk);
		this.container.style.top = (lnky[1] + this.lnk.offsetHeight) + "px";
		
		this.onScrollChange = function () {
			lnky = findPos(this.lnk);
			this.container.style.top = (lnky[1] + this.lnk.offsetHeight - this.oldScrollPos) + "px";
		};
		
		if(this.alignLeft) {
			this.container.style.left = (lnky[0]) + "px";
		} else {
			this.container.style.left = ((lnky[0] + this.lnk.offsetWidth) - this.container.offsetWidth) + "px";	
		}		
		return false;
	}
});

var bwSite = {};
bwSite.verify = PM.global.verify;

if(Prototype.Browser.IE) {
	Event.observe(window, 'load', resizeIEFix);
	Event.observe(window, 'resize', resizeIEFix);
}

bwSite.setupFlipper = function() {
	if(!$('mainMenuBar_grip') || !$('mainMenuBar')) return false;
	addToggler('mainMenuBar_grip', 'mainMenuBar', true, false);
	$('mainMenuBar').fx.setOptions({duration: 500, onComplete:function(){
		// work out if we've collapsed
		if($('mainMenuBar').offsetHeight == 20) {
			Element.addClassName(document.getElementsByTagName('body')[0], 'smallMenu');
			createCookie('smallMenu', 'y', 365);
		} else {
			Element.removeClassName(document.getElementsByTagName('body')[0], 'smallMenu');
			createCookie('smallMenu', 'n', 365);
		}
	}});	
};

document.observe('dom:loaded', bwSite.setupFlipper);


PM.emissionsCalculator = {
	
	calc: function(typeHolder, volHolder, sulphurHolder, resultsHolder) {
		
		carb = 82;
		if ($(typeHolder).value=="IFO") carb = 86.5;

		co2 = Math.round($(volHolder).value*(carb/100)*3.667,1);

		sox = Math.round($(volHolder).value*($(sulphurHolder).value/100)*2.05,1);					

		$(resultsHolder).innerHTML="<div style=\'font-size: 1.0em; font-weight: bold;\'>Your Emissions Are:</div><div style=\'\'>CO<sub>2</sub>&nbsp;&nbsp;<strong>"+co2+"&nbsp;mt</strong><br/>SO<sub>x</sub>&nbsp;&nbsp;<strong>"+sox+"&nbsp;mt</strong></div>";
		
	}
	
};var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
jsVersion = 1.1;
// JavaScript helper required to detect Flash Player PlugIn version information
function JSGetSwfVer(i){
	// NS/Opera version >= 3 check for Flash plugin in plugin array
	if (navigator.plugins != null && navigator.plugins.length > 0) {
		if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
			var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
      		var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
			descArray = flashDescription.split(" ");
			tempArrayMajor = descArray[2].split(".");
			versionMajor = tempArrayMajor[0];
			versionMinor = tempArrayMajor[1];
			if ( descArray[3] != "" ) {
				tempArrayMinor = descArray[3].split("r");
			} else {
				tempArrayMinor = descArray[4].split("r");
			}
      		versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
            flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
      	} else {
			flashVer = -1;
		}
	}
	// MSN/WebTV 2.6 supports Flash 4
	else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
	// WebTV 2.5 supports Flash 3
	else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
	// older WebTV supports Flash 2
	else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
	// Can't detect in all other cases
	else {
		
		flashVer = -1;
	}
	return flashVer;
} 
// If called with no parameters this function returns a floating point value 
// which should be the version of the Flash Player or 0.0 
// ex: Flash Player 7r14 returns 7.14
// If called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) 
{
 	reqVer = parseFloat(reqMajorVer + "." + reqRevision);
   	// loop backwards through the versions until we find the newest version	
	for (i=25;i>0;i--) {	
		if (isIE && isWin && !isOpera) {
			versionStr = VBGetSwfVer(i);
		} else {
			versionStr = JSGetSwfVer(i);		
		}
		if (versionStr == -1 ) { 
			return false;
		} else if (versionStr != 0) {
			if(isIE && isWin && !isOpera) {
				tempArray         = versionStr.split(" ");
				tempString        = tempArray[1];
				versionArray      = tempString .split(",");				
			} else {
				versionArray      = versionStr.split(".");
			}
			versionMajor      = versionArray[0];
			versionMinor      = versionArray[1];
			versionRevision   = versionArray[2];
			
			versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
			versionNum        = parseFloat(versionString);
        	// is the major.revision >= requested major.revision AND the minor version >= requested minor
			if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
				return true;
			} else {
				return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );	
			}
		}
	}	
	return (reqVer ? false : 0.0);
}



function writeFlash(data, width, height, bgcolor, returnHTML) {
	res = '<div style="margin: 0 auto; padding: 0 !important; background-color: '+ bgcolor+ '; width: '+width+'px; height: '+height+'px; overflow: hidden;">'
	res = res + '<object style="margin:0;padding:0;" type="application/x-shockwave-flash" data="' + data + '" width="'+ width +'" height="' + height + '">';
	res = res + '<param name="quality" value="high" />';
	res = res + '<param name="bgcolor" value="' + bgcolor + '" />';
	res = res + '<param name="allowScriptAccess" value="sameDomain" />';
	res = res + '<param name="menu" value="false"/>';
	res = res + '<param name="wmode" value="transparent"/>';
	res = res + '<param name="movie" value="' + data + '" />';
	res = res + '</object>';
	res = res + '</div>';
	if(returnHTML) return res;
	
	document.write(res);
}
/*
Copyright (c) 2005 JSON.org
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 Software shall be used for Good, not Evil.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.
*/
/*    The global object JSON contains two methods.
    JSON.stringify(value) takes a JavaScript value and produces a JSON text.    The value must not be cyclical.
    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will    throw a 'JSONError' exception if there is an error.*/var JSON = {    copyright: '(c)2005 JSON.org',    license: 'http://www.crockford.com/JSON/license.html',/*    Stringify a JavaScript value, producing a JSON text.*/    stringify: function (v) {        var a = [];/*    Emit a string.*/        function e(s) {            a[a.length] = s;        }/*    Convert a value.*/        function g(x) {            var c, i, l, v;            switch (typeof x) {            case 'object':                if (x) {                    if (x instanceof Array) {                        e('[');                        l = a.length;                        for (i = 0; i < x.length; i += 1) {                            v = x[i];                            if (typeof v != 'undefined' &&                                    typeof v != 'function') {                                if (l < a.length) {                                    e(',');                                }                                g(v);                            }                        }                        e(']');                        return;                    } else if (typeof x.valueOf == 'function') {                        e('{');                        l = a.length;                        for (i in x) {                            v = x[i];                            if (typeof v != 'undefined' &&                                    typeof v != 'function' &&                                    (!v || typeof v != 'object' ||                                        typeof v.valueOf == 'function')) {                                if (l < a.length) {                                    e(',');                                }                                g(i);                                e(':');                                g(v);                            }                        }                        return e('}');                    }                }                e('null');                return;            case 'number':                e(isFinite(x) ? +x : 'null');                return;            case 'string':                l = x.length;                e('"');                for (i = 0; i < l; i += 1) {                    c = x.charAt(i);                    if (c >= ' ') {                        if (c == '\\' || c == '"') {                            e('\\');                        }                        e(c);                    } else {                        switch (c) {                        case '\b':                            e('\\b');                            break;                        case '\f':                            e('\\f');                            break;                        case '\n':                            e('\\n');                            break;                        case '\r':                            e('\\r');                            break;                        case '\t':                            e('\\t');                            break;                        default:                            c = c.charCodeAt();                            e('\\u00' + Math.floor(c / 16).toString(16) +                                (c % 16).toString(16));                        }                    }                }                e('"');                return;            case 'boolean':                e(String(x));                return;            default:                e('null');                return;            }        }        g(v);        return a.join('');    },/*    Parse a JSON text, producing a JavaScript value.*/    parse: function (text) {        return (/^(\s+|[,:{}\[\]]|"(\\["\\\/bfnrtu]|[^\x00-\x1f"\\]+)*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)+$/.test(text)) &&            eval('(' + text + ')');    }};//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.
var Flotr={version:"0.2.0-alpha",author:"Bas Wenneker",website:"http://www.solutoire.com",_registeredTypes:{lines:"drawSeriesLines",points:"drawSeriesPoints",bars:"drawSeriesBars",candles:"drawSeriesCandles",pie:"drawSeriesPie"},register:function(A,B){Flotr._registeredTypes[A]=B+""},draw:function(B,D,A,C){C=C||Flotr.Graph;return new C(B,D,A)},getSeries:function(A){return A.collect(function(C){var B,C=(C.data)?Object.clone(C):{data:C};for(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(D,B){var A=B||{};for(var C in D){A[C]=(D[C]!=null&&typeof (D[C])=="object"&&!(D[C].constructor==Array||D[C].constructor==RegExp)&&!Object.isElement(D[C]))?Flotr.merge(D[C],B[C]):A[C]=D[C]}return A},getTickSize:function(E,D,A,B){var H=(A-D)/E;var G=Flotr.getMagnitude(H);var C=H/G;var F=10;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+")"},defaultPieLabelFormatter:function(A){return(A.fraction*100).toFixed(2)+"%"},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)},parseColor:function(D){if(D instanceof Flotr.Color){return D}var A,C=Flotr.Color;if((A=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]))}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(D))){return new C(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(D))){return new C(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(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55,parseFloat(A[4]))}if((A=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(D))){return new C(parseInt(A[1],16),parseInt(A[2],16),parseInt(A[3],16))}if((A=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(D))){return new C(parseInt(A[1]+A[1],16),parseInt(A[2]+A[2],16),parseInt(A[3]+A[3],16))}var B=D.strip().toLowerCase();if(B=="transparent"){return new C(255,255,255,0)}return((A=C.lookupColors[B]))?new C(A[0],A[1],A[2]):false},extractColor:function(B){var A;do{A=B.getStyle("background-color").toLowerCase();if(!(A==""||A=="transparent")){break}B=B.up(0)}while(!B.nodeName.match(/^body$/i));return(A=="rgba(0, 0, 0, 0)")?"transparent":A}};Flotr.Graph=Class.create({initialize:function(B,C,A){this.el=$(B);if(!this.el){throw"The target container doesn't exist"}this.data=C;this.series=Flotr.getSeries(C);this.setOptions(A);this.lastMousePos={pageX:null,pageY:null};this.selection={first:{x:-1,y:-1},second:{x:-1,y:-1}};this.prevSelection=null;this.selectionInterval=null;this.ignoreClick=false;this.prevHit=null;this.constructCanvas();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.draw();this.insertLegend();if(this.options.spreadsheet.show){this.constructTabs()}},setOptions:function(B){var P={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],title:null,subtitle:null,legend:{show:true,noColumns:1,labelFormatter:Prototype.K,labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:0,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},x2axis:{},yaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:90,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},y2axis:{titleAngle:270},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#FFFFFF",fillOpacity:0.4},lines:{show:false,lineWidth:2,fill:false,fillColor:null,fillOpacity:0.4},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,fillOpacity:0.4,horizontal:false,stacked:false},candles:{show:false,lineWidth:1,wickLineWidth:1,candleWidth:0.6,fill:true,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:0.5,barcharts:false},pie:{show:false,lineWidth:1,fill:true,fillColor:null,fillOpacity:0.6,explode:6,sizeRatio:0.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:false,pie3DviewAngle:(Math.PI/2*0.8),pie3DspliceThickness:20},grid:{color:"#545454",backgroundColor:null,tickColor:"#DDDDDD",labelMargin:3,verticalLines:true,horizontalLines:true,outlineWidth:2},selection:{mode:null,color:"#B6D9FF",fps:20},mouse:{track:false,position:"se",relative:false,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,radius:3},shadowSize:4,defaultType:"lines",HtmlText:true,fontSize:7.5,spreadsheet:{show:false,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all"}};P.x2axis=Object.extend(Object.clone(P.xaxis),P.x2axis);P.y2axis=Object.extend(Object.clone(P.yaxis),P.y2axis);this.options=Flotr.merge((B||{}),P);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}};var H=[],C=[],K=this.series.length,N=this.series.length,D=this.options.colors,A=[],G=0,M,J,I,O,E;for(J=N-1;J>-1;--J){M=this.series[J].color;if(M!=null){--N;if(Object.isNumber(M)){H.push(M)}else{A.push(Flotr.parseColor(M))}}}for(J=H.length-1;J>-1;--J){N=Math.max(N,H[J]+1)}for(J=0;C.length<N;){M=(D.length==J)?new Flotr.Color(100,100,100):Flotr.parseColor(D[J]);var F=G%2==1?-1:1;var L=1+F*Math.ceil(G/2)*0.2;M.scale(L,L,L);C.push(M);if(++J>=D.length){J=0;++G}}for(J=0,I=0;J<K;++J){O=this.series[J];if(O.color==null){O.color=C[I++].toString()}else{if(Object.isNumber(O.color)){O.color=C[O.color].toString()}}if(!O.xaxis){O.xaxis=this.axes.x}if(O.xaxis==1){O.xaxis=this.axes.x}else{if(O.xaxis==2){O.xaxis=this.axes.x2}}if(!O.yaxis){O.yaxis=this.axes.y}if(O.yaxis==1){O.yaxis=this.axes.y}else{if(O.yaxis==2){O.yaxis=this.axes.y2}}O.lines=Object.extend(Object.clone(this.options.lines),O.lines);O.points=Object.extend(Object.clone(this.options.points),O.points);O.bars=Object.extend(Object.clone(this.options.bars),O.bars);O.candles=Object.extend(Object.clone(this.options.candles),O.candles);O.pie=Object.extend(Object.clone(this.options.pie),O.pie);O.mouse=Object.extend(Object.clone(this.options.mouse),O.mouse);if(O.shadowSize==null){O.shadowSize=this.options.shadowSize}}},constructCanvas:function(){var C=this.el,B,D,A;this.canvas=C.select(".flotr-canvas")[0];this.overlay=C.select(".flotr-overlay")[0];C.childElements().invoke("remove");C.setStyle({position:"relative",cursor:"default"});this.canvasWidth=C.getWidth();this.canvasHeight=C.getHeight();B={width:this.canvasWidth,height:this.canvasHeight};if(this.canvasWidth<=0||this.canvasHeight<=0){throw"Invalid dimensions for plot, width = "+this.canvasWidth+", height = "+this.canvasHeight}if(!this.canvas){D=this.canvas=new Element("canvas",B);D.className="flotr-canvas";D=D.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{D=this.canvas.writeAttribute(B)}C.insert(D);if(Prototype.Browser.IE){D=window.G_vmlCanvasManager.initElement(D)}this.ctx=D.getContext("2d");if(!this.overlay){A=this.overlay=new Element("canvas",B);A.className="flotr-overlay";A=A.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{A=this.overlay.writeAttribute(B)}C.insert(A);if(Prototype.Browser.IE){A=window.G_vmlCanvasManager.initElement(A)}this.octx=A.getContext("2d");if(window.CanvasText){CanvasText.enable(this.ctx);CanvasText.enable(this.octx);this.textEnabled=true}},getTextDimensions:function(F,C,B,D){if(!F){return{width:0,height:0}}if(!this.options.HtmlText&&this.textEnabled){var E=this.ctx.getTextBounds(F,C);return{width:E.width+2,height:E.height+6}}else{var A=this.el.insert('<div style="position:absolute;top:-10000px;'+B+'" class="'+D+' flotr-dummy-div">'+F+"</div>").select(".flotr-dummy-div")[0];dim=A.getDimensions();A.remove();return dim}},loadDataGrid:function(){if(this.seriesData){return this.seriesData}var A=this.series;var B=[];for(i=0;i<A.length;++i){A[i].data.each(function(D){var C=D[0],F=D[1];if(r=B.find(function(G){return G[0]==C})){r[i+1]=F}else{var E=[];E[0]=C;E[i+1]=F;B.push(E)}})}B=B.sortBy(function(C){return C[0]});return this.seriesData=B},showTab:function(B,C){var A="canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle";switch(B){case"graph":this.datagrid.up().hide();this.el.select(A).invoke("show");this.tabs.data.removeClassName("selected");this.tabs.graph.addClassName("selected");break;case"data":this.constructDataGrid();this.datagrid.up().show();this.el.select(A).invoke("hide");this.tabs.data.addClassName("selected");this.tabs.graph.removeClassName("selected");break}},constructTabs:function(){var A=new Element("div",{className:"flotr-tabs-group",style:"position:absolute;left:0px;top:"+this.canvasHeight+"px;width:"+this.canvasWidth+"px;"});this.el.insert({bottom:A});this.tabs={graph:new Element("div",{className:"flotr-tab selected",style:"float:left;"}).update(this.options.spreadsheet.tabGraphLabel),data:new Element("div",{className:"flotr-tab",style:"float:left;"}).update(this.options.spreadsheet.tabDataLabel)};A.insert(this.tabs.graph).insert(this.tabs.data);this.el.setStyle({height:this.canvasHeight+this.tabs.data.getHeight()+2+"px"});this.tabs.graph.observe("click",(function(){this.showTab("graph")}).bind(this));this.tabs.data.observe("click",(function(){this.showTab("data")}).bind(this))},constructDataGrid:function(){if(this.datagrid){return this.datagrid}var D,B,L=this.series,J=this.loadDataGrid();var K=this.datagrid=new Element("table",{className:"flotr-datagrid",style:"height:100px;"});var C=["<colgroup><col />"];var F=['<tr class="first-row">'];F.push("<th>&nbsp;</th>");for(D=0;D<L.length;++D){F.push('<th scope="col">'+(L[D].label||String.fromCharCode(65+D))+"</th>");C.push("<col />")}F.push("</tr>");for(B=0;B<J.length;++B){F.push("<tr>");for(D=0;D<L.length+1;++D){var M="td";var G=(J[B][D]!=null?Math.round(J[B][D]*100000)/100000:"");if(D==0){M="th";var I;if(this.options.xaxis.ticks){var E=this.options.xaxis.ticks.find(function(N){return N[0]==J[B][D]});if(E){I=E[1]}}else{I=this.options.xaxis.tickFormatter(G)}if(I){G=I}}F.push("<"+M+(M=="th"?' scope="row"':"")+">"+G+"</"+M+">")}F.push("</tr>")}C.push("</colgroup>");K.update(C.join("")+F.join(""));if(!Prototype.Browser.IE){K.select("td").each(function(N){N.observe("mouseover",function(O){N=O.element();var P=N.previousSiblings();K.select("th[scope=col]")[P.length-1].addClassName("hover");K.select("colgroup col")[P.length].addClassName("hover")});N.observe("mouseout",function(){K.select("colgroup col.hover, th.hover").each(function(O){O.removeClassName("hover")})})})}var H=new Element("div",{className:"flotr-datagrid-toolbar"}).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarDownload).observe("click",this.downloadCSV.bind(this))).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarSelectAll).observe("click",this.selectAllData.bind(this)));var A=new Element("div",{className:"flotr-datagrid-container",style:"left:0px;top:0px;width:"+this.canvasWidth+"px;height:"+this.canvasHeight+"px;overflow:auto;"});A.insert(H);K.wrap(A.hide());this.el.insert(A);return K},selectAllData:function(){if(this.tabs){var B,A,E,D,C=this.constructDataGrid();this.showTab("data");(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()}}}).defer();return true}else{return false}},downloadCSV:function(){var D,A='"x"',C=this.series,E=this.loadDataGrid();for(D=0;D<C.length;++D){A+='%09"'+(C[D].label||String.fromCharCode(65+D))+'"'}A+="%0D%0A";for(D=0;D<E.length;++D){if(this.options.xaxis.ticks){var B=this.options.xaxis.ticks.find(function(F){return F[0]==E[D][0]});if(B){E[D][0]=B[1]}}else{E[D][0]=this.options.xaxis.tickFormatter(E[D][0])}A+=E[D].join("%09")+"%0D%0A"}if(Prototype.Browser.IE){A=A.gsub("%09","\t").gsub("%0A","\n").gsub("%0D","\r");window.open().document.write(A)}else{window.open("data:text/csv,"+A)}},initEvents:function(){this.overlay.stopObserving();this.overlay.observe("mousedown",this.mouseDownHandler.bind(this));this.overlay.observe("mousemove",this.mouseMoveHandler.bind(this));this.overlay.observe("click",this.clickHandler.bind(this))},findDataRanges:function(){var J=this.series,G=this.axes;G.x.datamin=0;G.x.datamax=0;G.x2.datamin=0;G.x2.datamax=0;G.y.datamin=0;G.y.datamax=0;G.y2.datamin=0;G.y2.datamax=0;if(J.length>0){var C,A,D,H,F,B,I,E;for(C=0;C<J.length;++C){B=J[C].data,I=J[C].xaxis,E=J[C].yaxis;if(B.length>0&&!J[C].hide){if(!I.used){I.datamin=I.datamax=B[0][0]}if(!E.used){E.datamin=E.datamax=B[0][1]}I.used=true;E.used=true;for(D=B.length-1;D>-1;--D){H=B[D][0];if(H<I.datamin){I.datamin=H}else{if(H>I.datamax){I.datamax=H}}for(A=1;A<B[D].length;A++){F=B[D][A];if(F<E.datamin){E.datamin=F}else{if(F>E.datamax){E.datamax=F}}}}}}}this.findXAxesValues();this.calculateRange(G.x);this.extendXRangeIfNeededByBar(G.x);if(G.x2.used){this.calculateRange(G.x2);this.extendXRangeIfNeededByBar(G.x2)}this.calculateRange(G.y);this.extendYRangeIfNeededByBar(G.y);if(G.y2.used){this.calculateRange(G.y2);this.extendYRangeIfNeededByBar(G.y2)}},calculateRange:function(D){var F=D.options,C=F.min!=null?F.min:D.datamin,A=F.max!=null?F.max:D.datamax,E;if(A-C==0){var B=(A==0)?1:0.01;C-=B;A+=B}D.tickSize=Flotr.getTickSize(F.noTicks,C,A,F.tickDecimals);if(F.min==null){E=F.autoscaleMargin;if(E!=0){C-=D.tickSize*E;if(C<0&&D.datamin>=0){C=0}C=D.tickSize*Math.floor(C/D.tickSize)}}if(F.max==null){E=F.autoscaleMargin;if(E!=0){A+=D.tickSize*E;if(A>0&&D.datamax<=0){A=0}A=D.tickSize*Math.ceil(A/D.tickSize)}}D.min=C;D.max=A},extendXRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.axis==A&&(F.show||E.show)){if(!F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+I.bars.barWidth}if(F.stacked&&F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},extendYRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.yaxis==A&&F.show&&!I.hide){if(F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+F.barWidth}if(F.stacked&&!F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},findXAxesValues:function(){for(i=this.series.length-1;i>-1;--i){s=this.series[i];s.xaxis.values=s.xaxis.values||[];for(j=s.data.length-1;j>-1;--j){s.xaxis.values[s.data[j][0]]={}}}},calculateTicks:function(D){var B=D.options,E,H;D.ticks=[];if(B.ticks){var G=B.ticks,I,F;if(Object.isFunction(G)){G=G({min:D.min,max:D.max})}for(E=0;E<G.length;++E){I=G[E];if(typeof (I)=="object"){H=I[0];F=(I.length>1)?I[1]:B.tickFormatter(H)}else{H=I;F=B.tickFormatter(H)}D.ticks[E]={v:H,label:F}}}else{var A=D.tickSize*Math.ceil(D.min/D.tickSize),C;for(E=0;A+E*D.tickSize<=D.max;++E){H=A+E*D.tickSize;C=B.tickDecimals;if(C==null){C=1-Math.floor(Math.log(D.tickSize)/Math.LN10)}if(C<0){C=0}H=H.toFixed(C);D.ticks.push({v:H,label:B.tickFormatter(H)})}}},calculateSpacing:function(){var L=this.axes,N=this.options,H=this.series,D=N.grid.labelMargin,M=L.x,A=L.x2,J=L.y,K=L.y2,F=2,G,E,C,I;[M,A,J,K].each(function(P){var O="";if(P.options.showLabels){for(G=0;G<P.ticks.length;++G){C=P.ticks[G].label.length;if(C>O.length){O=P.ticks[G].label}}}P.maxLabel=this.getTextDimensions(O,{size:N.fontSize,angle:Flotr.toRad(P.options.labelsAngle)},"font-size:smaller;","flotr-grid-label");P.titleSize=this.getTextDimensions(P.options.title,{size:N.fontSize*1.2,angle:Flotr.toRad(P.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},this);I=this.getTextDimensions(N.title,{size:N.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title");this.titleHeight=I.height;I=this.getTextDimensions(N.subtitle,{size:N.fontSize},"font-size:smaller;","flotr-subtitle");this.subtitleHeight=I.height;if(N.show){F=Math.max(F,N.points.radius+N.points.lineWidth/2)}for(E=0;E<N.length;++E){if(H[E].points.show){F=Math.max(F,H[E].points.radius+H[E].points.lineWidth/2)}}var B=this.plotOffset={left:0,right:0,top:0,bottom:0};B.left=B.right=B.top=B.bottom=F;B.bottom+=(M.options.showLabels?(M.maxLabel.height+D):0)+(M.options.title?(M.titleSize.height+D):0);B.top+=(A.options.showLabels?(A.maxLabel.height+D):0)+(A.options.title?(A.titleSize.height+D):0)+this.subtitleHeight+this.titleHeight;B.left+=(J.options.showLabels?(J.maxLabel.width+D):0)+(J.options.title?(J.titleSize.width+D):0);B.right+=(K.options.showLabels?(K.maxLabel.width+D):0)+(K.options.title?(K.titleSize.width+D):0);B.top=Math.floor(B.top);this.plotWidth=this.canvasWidth-B.left-B.right;this.plotHeight=this.canvasHeight-B.bottom-B.top;M.scale=this.plotWidth/(M.max-M.min);A.scale=this.plotWidth/(A.max-A.min);J.scale=this.plotHeight/(J.max-J.min);K.scale=this.plotHeight/(K.max-K.min)},draw:function(){this.drawGrid();this.drawLabels();this.drawTitles();if(this.series.length){this.el.fire("flotr:beforedraw",[this.series,this]);for(var A=0;A<this.series.length;A++){if(!this.series[A].hide){this.drawSeries(this.series[A])}}}this.el.fire("flotr:afterdraw",[this.series,this])},tHoz:function(A,B){B=B||this.axes.x;return(A-B.min)*B.scale},tVert:function(B,A){A=A||this.axes.y;return this.plotHeight-(B-A.min)*A.scale},drawGrid:function(){var B,E=this.options,A=this.ctx;if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:beforegrid",[this.axes.x,this.axes.y,E,this])}A.save();A.translate(this.plotOffset.left,this.plotOffset.top);if(E.grid.backgroundColor!=null){A.fillStyle=E.grid.backgroundColor;A.fillRect(0,0,this.plotWidth,this.plotHeight)}A.lineWidth=1;A.strokeStyle=E.grid.tickColor;A.beginPath();if(E.grid.verticalLines){for(var D=0;D<this.axes.x.ticks.length;++D){B=this.axes.x.ticks[D].v;if((B==this.axes.x.min||B==this.axes.x.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(Math.floor(this.tHoz(B))+A.lineWidth/2,0);A.lineTo(Math.floor(this.tHoz(B))+A.lineWidth/2,this.plotHeight)}}if(E.grid.horizontalLines){for(var C=0;C<this.axes.y.ticks.length;++C){B=this.axes.y.ticks[C].v;if((B==this.axes.y.min||B==this.axes.y.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(0,Math.floor(this.tVert(B))+A.lineWidth/2);A.lineTo(this.plotWidth,Math.floor(this.tVert(B))+A.lineWidth/2)}}A.stroke();if(E.grid.outlineWidth!=0){A.lineWidth=E.grid.outlineWidth;A.strokeStyle=E.grid.color;A.lineJoin="round";A.strokeRect(0,0,this.plotWidth,this.plotHeight)}A.restore();if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:aftergrid",[this.axes.x,this.axes.y,E,this])}},drawLabels:function(){var C=0,D,B,E,F,G,J=this.options,I=this.ctx,H=this.axes;for(E=0;E<H.x.ticks.length;++E){if(H.x.ticks[E].label){++C}}B=this.plotWidth/C;if(!J.HtmlText&&this.textEnabled){var A={size:J.fontSize,adjustAlign:true};D=H.x;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="t";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+this.plotHeight+J.grid.labelMargin,A)}D=H.x2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="b";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+J.grid.labelMargin,A)}D=H.y;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="r";A.valign="m";I.drawText(G.label,this.plotOffset.left-J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A)}D=H.y2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="l";A.valign="m";I.drawText(G.label,this.plotOffset.left+this.plotWidth+J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A);I.save();I.strokeStyle=A.color;I.beginPath();I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D));I.stroke();I.restore()}}else{if(H.x.options.showLabels||H.x2.options.showLabels||H.y.options.showLabels||H.y2.options.showLabels){F=['<div style="font-size:smaller;color:'+J.grid.color+';" class="flotr-labels">'];D=H.x;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+J.grid.labelMargin)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.x2;if(D.options.showLabels&&D.used){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top-J.grid.labelMargin-D.maxLabel.height)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;left:0;width:"+(this.plotOffset.left-J.grid.labelMargin)+"px;text-align:right;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y2;if(D.options.showLabels&&D.used){I.save();I.strokeStyle=D.options.color||J.grid.color;I.beginPath();for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;right:0;width:"+(this.plotOffset.right-J.grid.labelMargin)+"px;text-align:left;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>");I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D))}I.stroke();I.restore()}F.push("</div>");this.el.insert(F.join(""))}}},drawTitles:function(){var D,C=this.options,F=C.grid.labelMargin,B=this.ctx,A=this.axes;if(!C.HtmlText&&this.textEnabled){var E={size:C.fontSize,color:C.grid.color,halign:"c"};if(C.subtitle){B.drawText(C.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,E)}E.weight=1.5;E.size*=1.5;if(C.title){B.drawText(C.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,E)}E.weight=1.8;E.size*=0.8;E.adjustAlign=true;if(A.x.options.title&&A.x.used){E.halign="c";E.valign="t";E.angle=Flotr.toRad(A.x.options.titleAngle);B.drawText(A.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+A.x.maxLabel.height+this.plotHeight+2*F,E)}if(A.x2.options.title&&A.x2.used){E.halign="c";E.valign="b";E.angle=Flotr.toRad(A.x2.options.titleAngle);B.drawText(A.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-A.x2.maxLabel.height-2*F,E)}if(A.y.options.title&&A.y.used){E.halign="r";E.valign="m";E.angle=Flotr.toRad(A.y.options.titleAngle);B.drawText(A.y.options.title,this.plotOffset.left-A.y.maxLabel.width-2*F,this.plotOffset.top+this.plotHeight/2,E)}if(A.y2.options.title&&A.y2.used){E.halign="l";E.valign="m";E.angle=Flotr.toRad(A.y2.options.titleAngle);B.drawText(A.y2.options.title,this.plotOffset.left+this.plotWidth+A.y2.maxLabel.width+2*F,this.plotOffset.top+this.plotHeight/2,E)}}else{D=['<div style="color:'+C.grid.color+';" class="flotr-titles">'];if(C.title){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;font-size:1em;font-weight:bold;text-align:center;width:"+this.plotWidth+'px;" class="flotr-title">'+C.title+"</div>")}if(C.subtitle){D.push('<div style="position:absolute;top:'+this.titleHeight+"px;left:"+this.plotOffset.left+"px;font-size:smaller;text-align:center;width:"+this.plotWidth+'px;" class="flotr-subtitle">'+C.subtitle+"</div>")}D.push("</div>");D.push('<div class="flotr-axis-title" style="font-weight:bold;">');if(A.x.options.title&&A.x.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+C.grid.labelMargin+A.x.titleSize.height)+"px;left:"+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x.options.title+"</div>")}if(A.x2.options.title&&A.x2.used){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x2.options.title+"</div>")}if(A.y.options.title&&A.y.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;left:0;text-align:right;" class="flotr-axis-title">'+A.y.options.title+"</div>")}if(A.y2.options.title&&A.y2.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;right:0;text-align:right;" class="flotr-axis-title">'+A.y2.options.title+"</div>")}D.push("</div>");this.el.insert(D.join(""))}},drawSeries:function(A){A=A||this.series;var C=false;for(var B in Flotr._registeredTypes){if(A[B]&&A[B].show){this[Flotr._registeredTypes[B]](A);C=true}}if(!C){this[Flotr._registeredTypes[this.options.defaultType]](A)}},plotLine:function(I,F){var O=this.ctx,A=I.xaxis,K=I.yaxis,J=this.tHoz.bind(this),M=this.tVert.bind(this),H=I.data;if(H.length<2){return }var E=J(H[0][0],A),D=M(H[0][1],K)+F;O.beginPath();O.moveTo(E,D);for(var G=0;G<H.length-1;++G){var C=H[G][0],N=H[G][1],B=H[G+1][0],L=H[G+1][1];if(N===null||L===null){continue}if(N<=L&&N<K.min){if(L<K.min){continue}C=(K.min-N)/(L-N)*(B-C)+C;N=K.min}else{if(L<=N&&L<K.min){if(N<K.min){continue}B=(K.min-N)/(L-N)*(B-C)+C;L=K.min}}if(N>=L&&N>K.max){if(L>K.max){continue}C=(K.max-N)/(L-N)*(B-C)+C;N=K.max}else{if(L>=N&&L>K.max){if(N>K.max){continue}B=(K.max-N)/(L-N)*(B-C)+C;L=K.max}}if(C<=B&&C<A.min){if(B<A.min){continue}N=(A.min-C)/(B-C)*(L-N)+N;C=A.min}else{if(B<=C&&B<A.min){if(C<A.min){continue}L=(A.min-C)/(B-C)*(L-N)+N;B=A.min}}if(C>=B&&C>A.max){if(B>A.max){continue}N=(A.max-C)/(B-C)*(L-N)+N;C=A.max}else{if(B>=C&&B>A.max){if(C>A.max){continue}L=(A.max-C)/(B-C)*(L-N)+N;B=A.max}}if(E!=J(C,A)||D!=M(N,K)+F){O.moveTo(J(C,A),M(N,K)+F)}E=J(B,A);D=M(L,K)+F;O.lineTo(E,D)}O.stroke()},plotLineArea:function(J,D){var S=J.data;if(S.length<2){return }var L,G=0,N=this.ctx,Q=J.xaxis,B=J.yaxis,E=this.tHoz.bind(this),M=this.tVert.bind(this),H=Math.min(Math.max(0,B.min),B.max),F=true;N.beginPath();for(var O=0;O<S.length-1;++O){var R=S[O][0],C=S[O][1],P=S[O+1][0],A=S[O+1][1];if(R<=P&&R<Q.min){if(P<Q.min){continue}C=(Q.min-R)/(P-R)*(A-C)+C;R=Q.min}else{if(P<=R&&P<Q.min){if(R<Q.min){continue}A=(Q.min-R)/(P-R)*(A-C)+C;P=Q.min}}if(R>=P&&R>Q.max){if(P>Q.max){continue}C=(Q.max-R)/(P-R)*(A-C)+C;R=Q.max}else{if(P>=R&&P>Q.max){if(R>Q.max){continue}A=(Q.max-R)/(P-R)*(A-C)+C;P=Q.max}}if(F){N.moveTo(E(R,Q),M(H,B)+D);F=false}if(C>=B.max&&A>=B.max){N.lineTo(E(R,Q),M(B.max,B)+D);N.lineTo(E(P,Q),M(B.max,B)+D);continue}else{if(C<=B.min&&A<=B.min){N.lineTo(E(R,Q),M(B.min,B)+D);N.lineTo(E(P,Q),M(B.min,B)+D);continue}}var I=R,K=P;if(C<=A&&C<B.min&&A>=B.min){R=(B.min-C)/(A-C)*(P-R)+R;C=B.min}else{if(A<=C&&A<B.min&&C>=B.min){P=(B.min-C)/(A-C)*(P-R)+R;A=B.min}}if(C>=A&&C>B.max&&A<=B.max){R=(B.max-C)/(A-C)*(P-R)+R;C=B.max}else{if(A>=C&&A>B.max&&C<=B.max){P=(B.max-C)/(A-C)*(P-R)+R;A=B.max}}if(R!=I){L=(C<=B.min)?L=B.min:B.max;N.lineTo(E(I,Q),M(L,B)+D);N.lineTo(E(R,Q),M(L,B)+D)}N.lineTo(E(R,Q),M(C,B)+D);N.lineTo(E(P,Q),M(A,B)+D);if(P!=K){L=(A<=B.min)?B.min:B.max;N.lineTo(E(K,Q),M(L,B)+D);N.lineTo(E(P,Q),M(L,B)+D)}G=Math.max(P,K)}N.lineTo(E(G,Q),M(H,B)+D);N.closePath();N.fill()},drawSeriesLines:function(C){C=C||this.series;var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);B.lineJoin="round";var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;var E=D/2+B.lineWidth/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotLine(C,E+A/2);B.strokeStyle="rgba(0,0,0,0.2)";this.plotLine(C,E);if(C.lines.fill){B.fillStyle="rgba(0,0,0,0.05)";this.plotLineArea(C,E+A/2)}}B.lineWidth=D;B.strokeStyle=C.color;if(C.lines.fill){B.fillStyle=C.lines.fillColor!=null?C.lines.fillColor:Flotr.parseColor(C.color).scale(null,null,null,C.lines.fillOpacity).toString();this.plotLineArea(C,0)}this.plotLine(C,0);B.restore()},drawSeriesPoints:function(C){var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotPointShadows(C,A/2+B.lineWidth/2,C.points.radius);B.strokeStyle="rgba(0,0,0,0.2)";this.plotPointShadows(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.plotPoints(C,C.points.radius,C.points.fill);B.restore()},plotPoints:function(C,E,I){var A=C.xaxis,F=C.yaxis,J=this.ctx,D,B=C.data;for(D=B.length-1;D>-1;--D){var H=B[D][0],G=B[D][1];if(H<A.min||H>A.max||G<F.min||G>F.max){continue}J.beginPath();J.arc(this.tHoz(H,A),this.tVert(G,F),E,0,2*Math.PI,true);if(I){J.fill()}J.stroke()}},plotPointShadows:function(D,B,F){var A=D.xaxis,G=D.yaxis,J=this.ctx,E,C=D.data;for(E=C.length-1;E>-1;--E){var I=C[E][0],H=C[E][1];if(I<A.min||I>A.max||H<G.min||H>G.max){continue}J.beginPath();J.arc(this.tHoz(I,A),this.tVert(H,G)+B,F,0,Math.PI,false);J.stroke()}},drawSeriesBars:function(B){var A=this.ctx,D=B.bars.barWidth,C=Math.min(B.bars.lineWidth,D);A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=C;A.strokeStyle=B.color;this.plotBarsShadows(B,D,0,B.bars.fill);if(B.bars.fill){A.fillStyle=B.bars.fillColor!=null?B.bars.fillColor:Flotr.parseColor(B.color).scale(null,null,null,B.bars.fillOpacity).toString()}this.plotBars(B,D,0,B.bars.fill);A.restore()},plotBars:function(K,N,D,Q){var U=K.data;if(U.length<1){return }var S=K.xaxis,B=K.yaxis,P=this.ctx,F=this.tHoz.bind(this),O=this.tVert.bind(this);for(var R=0;R<U.length;R++){var J=U[R][0],I=U[R][1];var E=true,L=true,A=true;var H=0;if(K.bars.stacked){S.values.each(function(W,V){if(V==J){H=W.stack||0;W.stack=H+I}})}if(K.bars.horizontal){var C=H,T=J+H,G=I,M=I+N}else{var C=J,T=J+N,G=H,M=I+H}if(T<S.min||C>S.max||M<B.min||G>B.max){continue}if(C<S.min){C=S.min;E=false}if(T>S.max){T=S.max;if(S.lastSerie!=K&&K.bars.horizontal){L=false}}if(G<B.min){G=B.min}if(M>B.max){M=B.max;if(B.lastSerie!=K&&!K.bars.horizontal){L=false}}if(Q){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P.lineTo(F(C,S),O(M,B)+D);P.lineTo(F(T,S),O(M,B)+D);P.lineTo(F(T,S),O(G,B)+D);P.fill()}if(K.bars.lineWidth!=0&&(E||A||L)){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P[E?"lineTo":"moveTo"](F(C,S),O(M,B)+D);P[L?"lineTo":"moveTo"](F(T,S),O(M,B)+D);P[A?"lineTo":"moveTo"](F(T,S),O(G,B)+D);P.stroke()}}},plotBarsShadows:function(I,K,C){var T=I.data;if(T.length<1){return }var R=I.xaxis,A=I.yaxis,P=this.ctx,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var Q=0;Q<T.length;Q++){var H=T[Q][0],G=T[Q][1];var E=0;if(I.bars.stacked){R.values.each(function(V,U){if(U==H){E=V.stackShadow||0;V.stackShadow=E+G}})}if(I.bars.horizontal){var B=E,S=H+E,F=G,J=G+K}else{var B=H,S=H+K,F=E,J=G+E}if(S<R.min||B>R.max||J<A.min||F>A.max){continue}if(B<R.min){B=R.min}if(S>R.max){S=R.max}if(F<A.min){F=A.min}if(J>A.max){J=A.max}var O=D(S,R)-D(B,R)-((D(S,R)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(F,A)-M(J,A)-((M(F,A)+N<=this.plotHeight)?0:N));P.fillStyle="rgba(0,0,0,0.05)";P.fillRect(Math.min(D(B,R)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesCandles:function(B){var A=this.ctx,C=B.candles.candleWidth;A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=B.candles.lineWidth;this.plotCandlesShadows(B,C/2);this.plotCandles(B,C/2);A.restore()},plotCandles:function(K,D){var W=K.data;if(W.length<1){return }var T=K.xaxis,B=K.yaxis,P=this.ctx,E=this.tHoz.bind(this),O=this.tVert.bind(this);for(var S=0;S<W.length;S++){var U=W[S],J=U[0],L=U[1],I=U[2],X=U[3],N=U[4];var C=J,V=J+K.candles.candleWidth,G=Math.max(B.min,X),M=Math.min(B.max,I),A=Math.max(B.min,Math.min(L,N)),R=Math.min(B.max,Math.max(L,N));if(V<T.min||C>T.max||M<B.min||G>B.max){continue}var Q=K.candles[L>N?"downFillColor":"upFillColor"];if(K.candles.fill&&!K.candles.barcharts){P.fillStyle=Flotr.parseColor(Q).scale(null,null,null,K.candles.fillOpacity).toString();P.fillRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B))}if(K.candles.lineWidth||K.candles.wickLineWidth){var J,H,F=(K.candles.wickLineWidth%2)/2;J=Math.floor(E((C+V)/2),T)+F;P.save();P.strokeStyle=Q;P.lineWidth=K.candles.wickLineWidth;P.lineCap="butt";if(K.candles.barcharts){P.beginPath();P.moveTo(J,Math.floor(O(M,B)+D));P.lineTo(J,Math.floor(O(G,B)+D));H=Math.floor(O(L,B)+D)+0.5;P.moveTo(Math.floor(E(C,T))+F,H);P.lineTo(J,H);H=Math.floor(O(N,B)+D)+0.5;P.moveTo(Math.floor(E(V,T))+F,H);P.lineTo(J,H)}else{P.strokeRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B));P.beginPath();P.moveTo(J,Math.floor(O(R,B)+D));P.lineTo(J,Math.floor(O(M,B)+D));P.moveTo(J,Math.floor(O(A,B)+D));P.lineTo(J,Math.floor(O(G,B)+D))}P.stroke();P.restore()}}},plotCandlesShadows:function(H,C){var T=H.data;if(T.length<1||H.candles.barcharts){return }var Q=H.xaxis,A=H.yaxis,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var P=0;P<T.length;P++){var R=T[P],G=R[0],I=R[1],F=R[2],U=R[3],K=R[4];var B=G,S=G+H.candles.candleWidth,E=Math.max(A.min,Math.min(I,K)),J=Math.min(A.max,Math.max(I,K));if(S<Q.min||B>Q.max||J<A.min||E>A.max){continue}var O=D(S,Q)-D(B,Q)-((D(S,Q)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(E,A)-M(J,A)-((M(E,A)+N<=this.plotHeight)?0:N));this.ctx.fillStyle="rgba(0,0,0,0.05)";this.ctx.fillRect(Math.min(D(B,Q)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesPie:function(G){if(!this.options.pie.drawn){var K=this.ctx,C=this.options,E=G.pie.lineWidth,I=G.shadowSize,R=G.data,D=(Math.min(this.canvasWidth,this.canvasHeight)*G.pie.sizeRatio)/2,H=[];var L=1;var P=Math.sin(G.pie.viewAngle)*G.pie.spliceThickness/L;var M={size:C.fontSize*1.2,color:C.grid.color,weight:1.5};var Q={x:(this.canvasWidth+this.plotOffset.left)/2,y:(this.canvasHeight-this.plotOffset.bottom)/2};var O=this.series.collect(function(T,S){if(T.pie.show){return{name:(T.label||T.data[0][1]),value:[S,T.data[0][1]],explode:T.pie.explode}}});var B=O.pluck("value").pluck(1).inject(0,function(S,T){return S+T});var F=0,N=G.pie.startAngle,J=0;var A=O.collect(function(S){N+=F;J=parseFloat(S.value[1]);F=J/B;return{name:S.name,fraction:F,x:S.value[0],y:J,explode:S.explode,startAngle:2*N*Math.PI,endAngle:2*(N+F)*Math.PI}});K.save();if(I>0){A.each(function(V){var S=(V.startAngle+V.endAngle)/2;var T=Q.x+Math.cos(S)*V.explode+I;var U=Q.y+Math.sin(S)*V.explode+I;this.plotSlice(T,U,D,V.startAngle,V.endAngle,false,L);K.fillStyle="rgba(0,0,0,0.1)";K.fill()},this)}if(C.HtmlText){H=['<div style="color:'+this.options.grid.color+'" class="flotr-labels">']}A.each(function(c,X){var W=(c.startAngle+c.endAngle)/2;var V=C.colors[X];var Y=Q.x+Math.cos(W)*c.explode;var U=Q.y+Math.sin(W)*c.explode;this.plotSlice(Y,U,D,c.startAngle,c.endAngle,false,L);if(G.pie.fill){K.fillStyle=Flotr.parseColor(V).scale(null,null,null,G.pie.fillOpacity).toString();K.fill()}K.lineWidth=E;K.strokeStyle=V;K.stroke();var b=C.pie.labelFormatter(c);var S=(Math.cos(W)<0);var a=Y+Math.cos(W)*(G.pie.explode+D);var Z=U+Math.sin(W)*(G.pie.explode+D);if(c.fraction&&b){if(C.HtmlText){var T="position:absolute;top:"+(Z-5)+"px;";if(S){T+="right:"+(this.canvasWidth-a)+"px;text-align:right;"}else{T+="left:"+a+"px;text-align:left;"}H.push('<div style="'+T+'" class="flotr-grid-label">'+b+"</div>")}else{M.halign=S?"r":"l";K.drawText(b,a,Z+M.size/2,M)}}},this);if(C.HtmlText){H.push("</div>");this.el.insert(H.join(""))}K.restore();C.pie.drawn=true}},plotSlice:function(B,H,A,E,D,F,G){var C=this.ctx;G=G||1;C.save();C.scale(1,G);C.beginPath();C.moveTo(B,H);C.arc(B,H,A,E,D,F);C.lineTo(B,H);C.closePath();C.restore()},plotPie:function(){},insertLegend:function(){if(!this.options.legend.show){return }var H=this.series,I=this.plotOffset,B=this.options,b=[],A=false,O=this.ctx,R;var Q=H.findAll(function(c){return(c.label&&!c.hide)}).size();if(Q){if(!B.HtmlText&&this.textEnabled){var T={size:B.fontSize*1.1,color:B.grid.color};var M=B.legend.position,N=B.legend.margin,L=B.legend.labelBoxWidth,Z=B.legend.labelBoxHeight,S=B.legend.labelBoxMargin,W=I.left+N,U=I.top+N;var a=0;for(R=H.length-1;R>-1;--R){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);a=Math.max(a,O.measureText(E,T))}var K=Math.round(L+S*3+a),C=Math.round(Q*(S+Z)+S);if(M.charAt(0)=="s"){U=I.top+this.plotHeight-(N+C)}if(M.charAt(1)=="e"){W=I.left+this.plotWidth-(N+K)}var P=Flotr.parseColor(B.legend.backgroundColor||"rgb(240,240,240)").scale(null,null,null,B.legend.backgroundOpacity||0.1).toString();O.fillStyle=P;O.fillRect(W,U,K,C);O.strokeStyle=B.legend.labelBoxBorderColor;O.strokeRect(Flotr.toPixel(W),Flotr.toPixel(U),K,C);var G=W+S;var F=U+S;for(R=0;R<H.length;R++){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);O.fillStyle=H[R].color;O.fillRect(G,F,L-1,Z-1);O.strokeStyle=B.legend.labelBoxBorderColor;O.lineWidth=1;O.strokeRect(Math.ceil(G)-1.5,Math.ceil(F)-1.5,L+2,Z+2);O.drawText(E,G+L+S,F+(Z+T.size-O.fontDescent(T))/2,T);F+=Z+S}}else{for(R=0;R<H.length;++R){if(!H[R].label||H[R].hide){continue}if(R%B.legend.noColumns==0){b.push(A?"</tr><tr>":"<tr>");A=true}var E=B.legend.labelFormatter(H[R].label);b.push('<td class="flotr-legend-color-box"><div style="border:1px solid '+B.legend.labelBoxBorderColor+';padding:1px"><div style="width:'+B.legend.labelBoxWidth+"px;height:"+B.legend.labelBoxHeight+"px;background-color:"+H[R].color+'"></div></div></td><td class="flotr-legend-label">'+E+"</td>")}if(A){b.push("</tr>")}if(b.length>0){var V='<table style="font-size:smaller;color:'+B.grid.color+'">'+b.join("")+"</table>";if(B.legend.container!=null){$(B.legend.container).update(V)}else{var D="";var M=B.legend.position,N=B.legend.margin;if(M.charAt(0)=="n"){D+="top:"+(N+I.top)+"px;"}else{if(M.charAt(0)=="s"){D+="bottom:"+(N+I.bottom)+"px;"}}if(M.charAt(1)=="e"){D+="right:"+(N+I.right)+"px;"}else{if(M.charAt(1)=="w"){D+="left:"+(N+I.left)+"px;"}}var J=this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;'+D+'">'+V+"</div>").select("div.flotr-legend").first();if(B.legend.backgroundOpacity!=0){var Y=B.legend.backgroundColor;if(Y==null){var X=(B.grid.backgroundColor!=null)?B.grid.backgroundColor:Flotr.extractColor(J);Y=Flotr.parseColor(X).adjust(null,null,null,1).toString()}this.el.insert('<div class="flotr-legend-bg" style="position:absolute;width:'+J.getWidth()+"px;height:"+J.getHeight()+"px;"+D+"background-color:"+Y+';"> </div>').select("div.flotr-legend-bg").first().setStyle({opacity:B.legend.backgroundOpacity})}}}}}},getEventPosition:function(C){var G=this.overlay.cumulativeOffset(),F=(C.pageX-G.left-this.plotOffset.left),E=(C.pageY-G.top-this.plotOffset.top),D=0,B=0;if(C.pageX==null&&C.clientX!=null){var H=document.documentElement,A=document.body;D=C.clientX+(H&&H.scrollLeft||A.scrollLeft||0);B=C.clientY+(H&&H.scrollTop||A.scrollTop||0)}else{D=C.pageX;B=C.pageY}return{x:this.axes.x.min+F/this.axes.x.scale,x2:this.axes.x2.min+F/this.axes.x2.scale,y:this.axes.y.max-E/this.axes.y.scale,y2:this.axes.y2.max-E/this.axes.y2.scale,relX:F,relY:E,absX:D,absY:B}},clickHandler:function(A){if(this.ignoreClick){this.ignoreClick=false;return }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.selectionInterval==null&&(this.options.mouse.track||this.series.any(function(C){return C.mouse&&C.mouse.track}))){this.hit(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.bind(this),1000/this.options.selection.fps);this.mouseUpHandler=this.mouseUpHandler.bind(this);$(document).observe("mouseup",this.mouseUpHandler)},fireSelectEvent:function(){var A=this.axes,F=this.selection,C=(F.first.x<=F.second.x)?F.first.x:F.second.x,B=(F.first.x<=F.second.x)?F.second.x:F.first.x,E=(F.first.y>=F.second.y)?F.first.y:F.second.y,D=(F.first.y>=F.second.y)?F.second.y:F.first.y;C=A.x.min+C/A.x.scale;B=A.x.min+B/A.x.scale;E=A.y.max-E/A.y.scale;D=A.y.max-D/A.y.scale;this.el.fire("flotr:select",[{x1:C,y1:E,x2:B,y2:D},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,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);E.clearRect(A+C.left-E.lineWidth,F+C.top-E.lineWidth,B+E.lineWidth*2,D+E.lineWidth*2);this.prevSelection=null},setSelection:function(G){var B=this.options,H=this.axes.x,A=this.axes.y,F=yaxis.scale,D=xaxis.scale,E=B.selection.mode.indexOf("x")!=-1,C=B.selection.mode.indexOf("y")!=-1;this.clearSelection();this.selection.first.y=E?0:(A.max-G.y1)*F;this.selection.second.y=E?this.plotHeight:(A.max-G.y2)*F;this.selection.first.x=C?0:(G.x1-H.min)*D;this.selection.second.x=C?this.plotWidth:(G.x2-H.min)*D;this.drawSelection();this.fireSelectEvent()},drawSelection:function(){var C=this.prevSelection,F=this.selection,H=this.octx,I=this.options,A=this.plotOffset;if(C!=null&&F.first.x==C.first.x&&F.first.y==C.first.y&&F.second.x==C.second.x&&F.second.y==C.second.y){return }H.strokeStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.8).toString();H.lineWidth=1;H.lineJoin="round";H.fillStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.4).toString();this.prevSelection={first:{x:F.first.x,y:F.first.y},second:{x:F.second.x,y:F.second.y}};var E=Math.min(F.first.x,F.second.x),D=Math.min(F.first.y,F.second.y),G=Math.abs(F.second.x-F.first.x),B=Math.abs(F.second.y-F.first.y);H.fillRect(E+A.left,D+A.top,G,B);H.strokeRect(E+A.left,D+A.top,G,B)},selectionIsSane:function(){var A=this.selection;return Math.abs(A.second.x-A.first.x)>=5&&Math.abs(A.second.y-A.first.y)>=5},clearHit:function(){if(this.prevHit){var B=this.options,A=this.plotOffset,C=this.prevHit;this.octx.clearRect(this.tHoz(C.x)+A.left-B.points.radius*2,this.tVert(C.y)+A.top-B.points.radius*2,B.points.radius*3+B.points.lineWidth*3,B.points.radius*3+B.points.lineWidth*3);this.prevHit=null}},hit:function(I){var G=this.series,C=this.options,R=this.prevHit,H=this.plotOffset,D=this.octx,S,A,M,Q,L={dist:Number.MAX_VALUE,x:null,y:null,relX:I.relX,relY:I.relY,absX:I.absX,absY:I.absY,mouse:null};for(Q=0;Q<G.length;Q++){s=G[Q];if(!s.mouse.track){continue}S=s.data;A=(s.xaxis.scale*s.mouse.sensibility);M=(s.yaxis.scale*s.mouse.sensibility);for(var P=0,B,E;P<S.length;P++){if(S[P][1]===null){continue}B=Math.pow(s.xaxis.scale*(S[P][0]-I.x),2);E=Math.pow(s.yaxis.scale*(S[P][1]-I.y),2);if(B<A&&E<M&&Math.sqrt(B+E)<L.dist){L.dist=Math.sqrt(B+E);L.x=S[P][0];L.y=S[P][1];L.mouse=s.mouse}}}if(L.mouse&&L.mouse.track&&!R||(R&&(L.x!=R.x||L.y!=R.y))){var K=this.mouseTrack||this.el.select(".flotr-mouse-value")[0],F="",J=C.mouse.position,N=C.mouse.margin,O="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(!C.mouse.relative){if(J.charAt(0)=="n"){F+="top:"+(N+H.top)+"px;"}else{if(J.charAt(0)=="s"){F+="bottom:"+(N+H.bottom)+"px;"}}if(J.charAt(1)=="e"){F+="right:"+(N+H.right)+"px;"}else{if(J.charAt(1)=="w"){F+="left:"+(N+H.left)+"px;"}}}else{if(J.charAt(0)=="n"){F+="bottom:"+(N-H.top-this.tVert(L.y)+this.canvasHeight)+"px;"}else{if(J.charAt(0)=="s"){F+="top:"+(N+H.top+this.tVert(L.y))+"px;"}}if(J.charAt(1)=="e"){F+="left:"+(N+H.left+this.tHoz(L.x))+"px;"}else{if(J.charAt(1)=="w"){F+="right:"+(N-H.left-this.tHoz(L.x)+this.canvasWidth)+"px;"}}}O+=F;if(!K){this.el.insert('<div class="flotr-mouse-value" style="'+O+'"></div>');K=this.mouseTrack=this.el.select(".flotr-mouse-value").first()}else{this.mouseTrack=K.setStyle(O)}if(L.x!==null&&L.y!==null){K.show();this.clearHit();if(L.mouse.lineColor!=null){D.save();D.translate(H.left,H.top);D.lineWidth=C.points.lineWidth;D.strokeStyle=L.mouse.lineColor;D.fillStyle="#ffffff";D.beginPath();D.arc(this.tHoz(L.x),this.tVert(L.y),C.mouse.radius,0,2*Math.PI,true);D.fill();D.stroke();D.restore()}this.prevHit=L;var T=L.mouse.trackDecimals;if(T==null||T<0){T=0}K.innerHTML=L.mouse.trackFormatter({x:L.x.toFixed(T),y:L.y.toFixed(T)});K.fire("flotr:hit",[L,this])}else{if(R){K.hide();this.clearHit()}}}},saveImage:function(D,C,A,B){var E=null;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(E,D,B,C){this.rgba=["r","g","b","a"];var A=4;while(-1<--A){this[this.rgba[A]]=arguments[A]||((A==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()},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},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()},distance:function(B){if(!B){return }B=new Flotr.parseColor(B);var C=0;var 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(",")+")"}});Flotr.Color.lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};Flotr.Date={format:function(F,E){if(!F){return }var A=function(H){H=H.toString();return H.length==1?"0"+H:H};var D=[];var C=false;for(var B=0;B<E.length;++B){var G=E.charAt(B);if(C){switch(G){case"h":G=F.getUTCHours().toString();break;case"H":G=A(F.getUTCHours());break;case"M":G=A(F.getUTCMinutes());break;case"S":G=A(F.getUTCSeconds());break;case"d":G=F.getUTCDate().toString();break;case"m":G=(F.getUTCMonth()+1).toString();break;case"y":G=F.getUTCFullYear().toString();break;case"b":G=Flotr.Date.monthNames[F.getUTCMonth()];break}D.push(G);C=false}else{if(G=="%"){C=true}else{D.push(G)}}}return D.join("")},timeUnits:{second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000},spec:[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]};/* $Id: flotr_extended.js,v 1.19 2010/05/31 19:05:12 jen Exp $ */

/** 
 * @author Bas Wenneker
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 * @version 0.2.0
 */
var FlotrExtended = {
	version: '%version%',
	author: 'Bas Wenneker',
	website: 'http://www.solutoire.com',
	isIphone: /iphone/i.test(navigator.userAgent),
	
	/**
	 * An object of the registered graph types. Use FlotrExtended.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){
		FlotrExtended.graphTypes[name] = graphType;
		FlotrExtended.defaultOptions[name] = graphType.options || {};
		FlotrExtended.defaultOptions.defaultType = FlotrExtended.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){
		FlotrExtended.plugins[name] = plugin;
		FlotrExtended.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 FlotrExtended.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 FlotrExtended.Graph
	 * @return {Object} returns a new graph object and of course draws the graph.
	 */
	draw: function(el, data, options, GraphKlass){	
		GraphKlass = GraphKlass || FlotrExtended.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)) ? FlotrExtended.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)) ? FlotrExtended.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 = FlotrExtended.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','u','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 || FlotrExtended.isIphone) {
			ctx.drawText(text, x, y, style);
			return;
		}
		
		style = Object.extend({
			size: '10px',
			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 || FlotrExtended.isIphone) {
			return {width: ctx.measure(text, style)};
		}
		
		style = Object.extend({
			size: '10px',
			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 += FlotrExtended.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 FlotrExtended.alignTable[style.textAlign+' '+style.textBaseline] || 0;
  }
};

FlotrExtended.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]
		showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
		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
		tickFormatter: FlotrExtended.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
		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
	},
	x2axis: {},
	yaxis: {
		ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
		showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
		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
		tickFormatter: FlotrExtended.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
		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,
        labelWidth: null,		// Do labels have a default width?
        labelWidthBothSides: 0   // Do we apply the label width to both sides?
	},
	y2axis: {
		titleAngle: 270
	},
	grid: {
		color: '#545454',      // => primary color used for outline and labels
		backgroundColor: null, // => null for transparent, else color
		tickColor: '#DDDDDD',  // => color used for the ticks
		labelMargin: 3,        // => margin in pixels
		verticalLines: true,   // => whether to show gridlines in vertical direction
		horizontalLines: true, // => whether to show gridlines in horizontal direction
		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
        xaxisOutline: 0,       // How thick should the outline be on the xaxis?
        yaxisOutline: 0        // How thick should the outline be on the yaxis?
	},
	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: FlotrExtended.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
		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,
        showLine: false,		// Are we showing a line down the whole graph on hit?
        mouseBoxSize: null 		// How wide is the mousebox?
		
	}
};

/**
 * Flotr Graph class that plots a graph on creation.
 */
FlotrExtended.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 = FlotrExtended.getSeries(data);
		this.setOptions(options);
    
		var type, p;
		for (type in FlotrExtended.graphTypes) {
			this[type] = Object.clone(FlotrExtended.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();
		this.insertLegend();
    
		this.el.fire('flotr:afterinit', [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 = FlotrExtended.clone(FlotrExtended.defaultOptions);
		options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis);
		options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis);
		this.options = FlotrExtended.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}
		};
		
		// 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(FlotrExtended.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 FlotrExtended.Color(100, 100, 100) : FlotrExtended.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 FlotrExtended.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;
		}

		this.axes.x.d2p = this.axes.x2.d2p = function(x){
			return (d2p(x, this.options) - this.min) * this.scale;
		};

		this.axes.x.p2d = this.axes.x2.p2d = function(x){
			return (p2d(x, this.options) / this.scale + this.min);
		};

		var ph = this.plotHeight;
		this.axes.y.d2p = this.axes.y2.d2p = function(y){
			return ph - (d2p(y, this.options) - this.min) * this.scale;
		};

		this.axes.y.p2d = this.axes.y2.p2d = function(y){
			return p2d(this.max -y / this.scale, this.options);
		};
	},
	/**
	 * 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(Prototype.Browser.IE){
			window.G_vmlCanvasManager.initElement(c);
			window.G_vmlCanvasManager.initElement(oc);
		}
		this.ctx = c.getContext('2d');
		this.octx = oc.getContext('2d');
    
		if(!Prototype.Browser.IE){
			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 FlotrExtended.Color) return color.adjust(null, null, null, options.opacity).toString();
    if (Object.isString(color)) return FlotrExtended.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 FlotrExtended.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, FlotrExtended.Color.parse(c).scale(null, null, null, options.opacity));
    }
    return gradient;
  },
	registerPlugins: function(){
		var name, plugin, c;
		for (name in FlotrExtended.plugins) {
			plugin = FlotrExtended.plugins[name];
			for (c in plugin.callbacks) {
				this.el.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('<div style="position:absolute;top:-10000px;'+HtmlStyle+'" class="'+className+' flotr-dummy-div">' + text + '</div>').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) {
					if (!xaxis.used) xaxis.datamin = xaxis.datamax = data[0][0];
					if (!yaxis.used) yaxis.datamin = yaxis.datamax = data[0][1];
					xaxis.used = true;
					yaxis.used = true;

					for(h = data.length - 1; h > -1; --h){
						x = data[h][0];
						     if(x < xaxis.datamin) xaxis.datamin = x;
						else if(x > xaxis.datamax) xaxis.datamax = x;

						for(j = 1; j < data[h].length; j++){
							y = data[h][j];
							     if(y < yaxis.datamin) yaxis.datamin = y;
							else if(y > yaxis.datamax) yaxis.datamax = y;
						}
					}
				}
			}
		}
		
		this.findXAxesValues();
		
		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 FlotrExtended.graphTypes) {
			if (this[t][f]) this[t][f](axis);
		}
	},
	/**
	 * Calculates the range of an axis to apply autoscaling.
	 * @param {Object} axis - The axis for what the range will be calculated
	 */
	calculateRange: function(axis, type){
		var o = axis.options,
		    min = o.min != null ? o.min : axis.datamin,
		    max = o.max != null ? o.max : axis.datamax,
		    margin = o.autoscaleMargin;

		if(max - min == 0.0){
			var widen = (max == 0.0) ? 1.0 : 0.01;
			min -= widen;
			max += widen;
		}
		axis.tickSize = FlotrExtended.getTickSize(o.noTicks, min, max, o.tickDecimals);

		// Autoscaling.
		if(o.min == null && margin != 0){
			min -= axis.tickSize * margin;
			// Make sure we don't go below zero if all values are positive.
			if(min < 0 && axis.datamin >= 0) min = 0;
			min = axis.tickSize * Math.floor(min / axis.tickSize);
		}
    
		if(o.max == null && margin != 0){
			max += axis.tickSize * margin;
			if(max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) max = 0;				
			max = axis.tickSize * Math.ceil(max / axis.tickSize);
		}

		if (min == max) max = min + 1;

		axis.min = min;
		axis.max = max;
		
		this.extendRange(axis, type);
	},
	/** 
	 * Find every values of the x axes
	 */
	findXAxesValues: function(){
		var i, j, s;
		for(i = this.series.length-1; i > -1 ; --i){
			s = this.series[i];
			s.xaxis.values = s.xaxis.values || {};
			for (j = s.data.length-1; j > -1 ; --j){
				s.xaxis.values[s.data[j][0]+''] = {};
			}
		}
	},
	/**
	 * 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 = [];	
		if(o.ticks){
			var ticks = o.ticks, t, label;

			if(Object.isFunction(ticks)){
				ticks = ticks({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 };
			}
		}
		else {
			if (o.mode == 'time') {
				var tu = FlotrExtended.Date.timeUnits,
				    spec = FlotrExtended.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 = FlotrExtended.getTickSize(axis.options.noTicks*tu.year, axis.min, axis.max, 0);
				}
				
				axis.tickSize = size;
				axis.tickUnit = unit;
				axis.ticks = FlotrExtended.Date.generator(axis);
			}
			else {
				// Round to nearest multiple of tick size.
				var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize),
				    decimals;
				
				// Then store all possible ticks.
				for(i = 0; start + i * axis.tickSize <= axis.max; ++i){
					v = 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) });
				}
			}
		}
	},
	/**
	 * 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: FlotrExtended.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label');
			
			// Check to see if we have a preset label width.
			if(axis == y && options.yaxis.labelWidth) axis.maxLabel.width = this.options.yaxis.labelWidth;
			
			axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: FlotrExtended.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;
		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;
    
		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;
		
		x.scale  = this.plotWidth / (x.max - x.min);
		x2.scale = this.plotWidth / (x2.max - x2.min);
		y.scale  = this.plotHeight / (y.max - y.min);
		y2.scale = this.plotHeight / (y2.max - y2.min);
	},
	/**
	 * Draws grid, labels, series and outline.
	 */
	draw: 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]);
	},
	/**
	 * Draws a grid for the graph.
	 */
	drawGrid: function(){
		var v, o = this.options,
		    ctx = this.ctx, a;
		    
		if(o.grid.verticalLines || o.grid.horizontalLines){
			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.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);
				}
			}
			
			// 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);
				}
			}
			ctx.stroke();
		}
		
		ctx.restore();
		if(o.grid.verticalLines || o.grid.horizontalLines){
			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 && o.grid.xaxisOutline == 0 && o.grid.yaxisOutline == 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);
      
      // Draw xaxis/yaxis outlines.
      ctx.translate(-this.plotOffset.left, -this.plotOffset.top);
      ctx.beginPath();
      if (o.grid.xaxisOutline != 0) {
    	  ctx.lineWidth = o.grid.xaxisOutline;
    	  ctx.strokeStyle = o.grid.color;
    	  ctx.moveTo(this.plotOffset.left, this.plotOffset.top);
    	  ctx.lineTo(this.plotOffset.left, this.plotHeight + this.plotOffset.top);
      }

      if (o.grid.yaxisOutline != 0) {
    	  ctx.lineWidth = o.grid.yaxisOutline;
    	  ctx.strokeStyle = o.grid.color;
    	  ctx.moveTo(this.plotOffset.left, this.plotHeight + this.plotOffset.top);
    	  ctx.lineTo(this.plotWidth + this.plotOffset.left, this.plotHeight + this.plotOffset.top);
      }

      ctx.stroke();      
      
    }
    
    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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textBaseline = 'middle';
				style.textAlign = (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left'));

				FlotrExtended.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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textBaseline = 'middle';
				style.textAlign = 'left';
				
				FlotrExtended.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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textAlign = 'center';
				style.textBaseline = 'top';
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				
				FlotrExtended.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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textAlign = 'center';
				style.textBaseline = 'bottom';
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				
				FlotrExtended.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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textAlign = 'right';
				style.textBaseline = 'middle';
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				
				FlotrExtended.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 = FlotrExtended.toRad(axis.options.labelsAngle);
				style.textAlign = 'left';
				style.textBaseline = 'middle';
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				
				FlotrExtended.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 = ['<div style="font-size:smaller;color:' + options.grid.color + ';" class="flotr-labels">'];
			
			// 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(
					  '<div style="position:absolute;top:', 
					  (this.plotOffset.top + this.plotHeight + options.grid.labelMargin), 'px;left:', 
					  (this.plotOffset.left +axis.d2p(tick.v) - xBoxWidth/2), 'px;width:', 
					  xBoxWidth, 'px;text-align:center;', (axis.options.color?('color:'+axis.options.color+';'):''), 
					  '" class="flotr-grid-label">', tick.label, '</div>'
					);
				}
			}
			
			// 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(
					  '<div style="position:absolute;top:', 
					  (this.plotOffset.top - options.grid.labelMargin - axis.maxLabel.height), 'px;left:', 
					  (this.plotOffset.left + axis.d2p(tick.v) - xBoxWidth/2), 'px;width:', 
					  xBoxWidth, 'px;text-align:center;', (axis.options.color?('color:'+axis.options.color+';'):''), 
					  '" class="flotr-grid-label">', tick.label, '</div>'
					);
				}
			}
			
			// 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(
					  '<div style="position:absolute;top:', 
					  (this.plotOffset.top + axis.d2p(tick.v) - axis.maxLabel.height/2), 'px;left:0;width:', 
					  (this.plotOffset.left - options.grid.labelMargin), 'px;text-align:right;', 
					  (axis.options.color?('color:'+axis.options.color+';'):''), 
					  '" class="flotr-grid-label flotr-grid-label-y">', tick.label, '</div>'
					);
				}
			}
			
			// 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(
					  '<div style="position:absolute;top:', 
					  (this.plotOffset.top + axis.d2p(tick.v) - axis.maxLabel.height/2), 'px;right:0;width:', 
					  (this.plotOffset.right - options.grid.labelMargin), 'px;text-align:left;', 
					  (axis.options.color?('color:'+axis.options.color+';'):''), 
					  '" class="flotr-grid-label flotr-grid-label-y">', tick.label, '</div>'
					);

					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('</div>');
			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){
				FlotrExtended.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){
				FlotrExtended.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 = 'center';
				style.textBaseline = 'top';
				style.angle = FlotrExtended.toRad(a.x.options.titleAngle);
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				FlotrExtended.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 = 'center';
				style.textBaseline = 'bottom';
				style.angle = FlotrExtended.toRad(a.x2.options.titleAngle);
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				FlotrExtended.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 = 'right';
				style.textBaseline = 'middle';
				style.angle = FlotrExtended.toRad(a.y.options.titleAngle);
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				FlotrExtended.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 = 'left';
				style.textBaseline = 'middle';
				style.angle = FlotrExtended.toRad(a.y2.options.titleAngle);
				style = FlotrExtended.getBestTextAlign(style.angle, style);
				FlotrExtended.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 = ['<div style="color:'+options.grid.color+';" class="flotr-titles">'];
			
			// Add title
			if (options.title)
				html.push(
				  '<div style="position:absolute;top:0;left:', 
				  this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:',
				  this.plotWidth,'px;" class="flotr-title">', options.title, '</div>'
				);
			
			// Add subtitle
			if (options.subtitle)
				html.push(
				  '<div style="position:absolute;top:', this.titleHeight, 'px;left:', 
				  this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:',
				  this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>'
				);

			html.push('</div>');
			
			html.push('<div class="flotr-axis-title" style="font-weight:bold;">');
			
			// Add x axis title
			if (a.x.options.title && a.x.used)
				html.push(
				  '<div style="position:absolute;top:', 
				  (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), 
				  'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, 
				  'px;text-align:center;" class="flotr-axis-title">', a.x.options.title, '</div>'
				);
			
			// Add x2 axis title
			if (a.x2.options.title && a.x2.used)
				html.push(
				  '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', 
				  this.plotWidth, 'px;text-align:center;" class="flotr-axis-title">', a.x2.options.title, '</div>'
				);
			
			// Add y axis title
			if (a.y.options.title && a.y.used)
				html.push(
				  '<div style="position:absolute;top:', 
				  (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
				  'px;left:0;text-align:right;" class="flotr-axis-title">', a.y.options.title, '</div>'
				);
			
			// Add y2 axis title
			if (a.y2.options.title && a.y2.used)
				html.push(
				  '<div style="position:absolute;top:', 
				  (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
				  'px;right:0;text-align:right;" class="flotr-axis-title">', a.y2.options.title, '</div>'
				);
			
			html.push('</div>');
			
			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 FlotrExtended.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, FlotrExtended.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(FlotrExtended.toPixel(offsetX), FlotrExtended.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
					FlotrExtended.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 ? '</tr><tr>' : '<tr>');
		  				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(
						'<td class="flotr-legend-color-box">',
							'<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">',
								'<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border
									'<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', opacity, color, '"></div>', // Background
								'</div>',
							'</div>',
						'</td>',
						'<td class="flotr-legend-label">', label, '</td>'
					);
		  		}
		  		if(rowStarted) fragments.push('</tr>');
		  		
		  		if(fragments.length > 0){
		  			var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>';
		  			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('<div class="flotr-legend" style="position:absolute;z-index:2;' + pos +'">' + table + '</div>').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 : FlotrExtended.Color.extract(div);
		  						c = this.processColor(tmp, null, {opacity: 1});
		  					}
		  					this.el.insert(
		  					  '<div class="flotr-legend-bg" style="position:absolute;width:' + div.getWidth() + 
		  					  'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>'
		  					)
		  					.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,
		    xa = prevHit.xaxis,
		    ya = prevHit.yaxis;
				
		//if(!s.bars.show){
			var r = s.points.radius;
			
			if(s.mouse.showLine) {
				this.octx.clearRect(
						xa.d2p(prevHit.x) + plotOffset.left - r*2,
						0,
						r*3 + s.points.lineWidth*3, 
						this.plotHeight + this.plotOffset.top
				);
			} else {
				this.octx.clearRect(
						xa.d2p(prevHit.x) + plotOffset.left - r*2,
						ya.d2p(prevHit.y) + plotOffset.top - r*2,
						r*3 + s.points.lineWidth*3, 
						r*3 + s.points.lineWidth*3
				);
			}
		/*}

		else {
			
			if(s.mouse.showLine) {
				this.octx.clearRect(
						xa.d2p(prevHit.x) + plotOffset.left - r*2,
						0,
						r*3 + s.points.lineWidth*3, 
						this.plotHeight + this.plotOffset.top
				);
			} else {
				var bw = s.bars.barWidth;		
				this.octx.clearRect(
					xa.d2p(prevHit.x - bw/2) + plotOffset.left - lw, 
					ya.d2p(prevHit.y >= 0 ? prevHit.y : 0) + plotOffset.top - lw, 
					xa.d2p(bw) + lw * 2, 
					ya.d2p(prevHit.y < 0 ? prevHit.y : 0) + lw * 2
				);
			}
		}*/
	},
	/**
	 * 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){
				octx.translate(this.plotOffset.left, this.plotOffset.top);
				octx.beginPath();
				
  				if(s.mouse.showLine) {
  					// Draw a line down the entire graph.
	                octx.moveTo(xa.d2p(n.x), 0);
	                octx.lineTo(xa.d2p(n.x), this.plotHeight);
  				} else {
  	  				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 {
  				octx.save();
  				octx.translate(this.plotOffset.left, this.plotOffset.top);
  				octx.beginPath();
  				
  				if(s.mouse.showLine) {
  					// Draw a line down the entire graph.
	                octx.moveTo(xa.d2p(n.x), 0);
	                octx.lineTo(xa.d2p(n.x), this.plotHeight);
  				} else {
        
	  				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;
	  					
	  					octx.moveTo(xa.d2p(n.x-(bw/2)), ya.d2p(0));
						octx.lineTo(xa.d2p(n.x-(bw/2)), ya.d2p(n.y));
	  					octx.lineTo(xa.d2p(n.x+(bw/2)), ya.d2p(n.y));
	  					octx.lineTo(xa.d2p(n.x+(bw/2)), ya.d2p(0));
	  
	  					if(s.mouse.fillColor) octx.fill();
					}
  				}

  				octx.stroke();
  				octx.closePath();
  				octx.restore();
			}*/
			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 FlotrExtended.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,
			mousepos = 'sw',
			/**
			 * Nearest data element.
			 */
			n = {
				dist:Number.MAX_VALUE,
				x:null,
				y:null,
				x_show:null,
				y_show:null,
				relX:mouse.relX,
				relY:mouse.relY,
				absX:mouse.absX,
				absY:mouse.absY,
				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];
					
					x_show = x;
					y_show = y;
					
					if(s.use_alternate_vals) x_show = data[j][2];
	                if(s.use_alternate_vals) y_show  = data[j][3];
		
					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 are not supported yet. Not sure how it should look with bars
					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.x_show = x_show;
	  						n.y_show = y_show;
							n.xaxis = xa;
							n.yaxis = ya;
							n.mouse = s.mouse;
							n.series = s; 
							n.allSeries = series; // include all series
							n.index = j;
						}
					}
				}
			}
		} else {
		    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;
	  			
	  			if(s.mouse.showLine) ysens = 5000;
	  			
	  			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];
	  				x_show = x;
	  				y_show = y;
	  				
	  				if(s.use_alternate_vals) x_show = data[j][2];
	                if(s.use_alternate_vals) y_show  = data[j][3];
	  				
	  				if (y === null || 
	  				    xa.min > x || xa.max < x || 
	  				    ya.min > y || ya.max < y) continue;
	  				
	  				var 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 && ydiff < ysens) || 
	  					(!s.bars.show && s.mouse.showLine && xdiff < xsens) || 
	  				    (s.bars.show && xdiff < s.bars.barWidth/2 && ((y > 0 && my > 0 && my < y) || (y < 0 && my < 0 && my > y))) ||
	  				    (s.bars.show && s.mouse.showLine && xdiff < s.bars.barWidth/2)
	  				    ){
	  					var distance = Math.sqrt(xdiff*xdiff + ydiff*ydiff);
	  					if(distance < n.dist){
	  						n.dist = distance;
	  						n.x = x;
	  						n.y = y;
	  						n.x_show = x_show;
	  						n.y_show = y_show;
	  						
	  						n.xaxis = xa;
	  						n.yaxis = ya;
	  						n.mouse = s.mouse;
	  						n.series = s;
	  						n.allSeries = series;
	  						n.index = j;
	  						n.seriesIndex = i;
	  						
	  						// Are we on the right hand or left hand side of the graph?  We want to 
	  			            // flip to using the other side where needed.
	  			            if(j < parseInt(data.length/2)) {
	  			            	
	  			            	// Are we on the top or the bottom of the graph?
	  			            	if(ya.d2p(y) < this.plotHeight/2)
	  			            		mousepos = 'se';
	  			            	else
	  			            		mousepos = 'ne';
	  			            } else {
	  			            	// Are we on the top or the bottom of the graph?
	  			            	if(ya.d2p(y) < this.plotHeight/2)
	  			            		mousepos = 'sw';
	  			            	else
	  			            		mousepos = 'nw';
	  			            } 
	  						  						
	  					}
	  				}
	  			}
	  		}
		}
		
		
		if(n.series && (n.mouse && n.mouse.track && !prevHit || (prevHit /*&& (n.x != prevHit.x || n.y != prevHit.y)*/))){
			
			var mouseboxwidth = parseInt(this.plotWidth * 3/5);        	
        	if(n.mouse.mouseBoxSize) mouseboxwidth = n.mouse.mouseBoxSize;
			
			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;';
			
			// Get the mouse position - it is dependent on where we are in the graph, top/bottom, right/left.
			p = mousepos;			
			
			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){
					     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 {
					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;';
				}*/
			}
			elStyle += pos;
				     
			if(!mt){
				this.el.insert('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');
				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;
				
				if(s.use_alternate_vals) {
					
					mt.innerHTML = n.mouse.trackFormatter({
						x: n.x_show, 
						y: n.y_show, 
						series: n.series, 
						index: n.index,
						nearest: n
					});
					
				} else {
					
					mt.innerHTML = n.mouse.trackFormatter({
						x: n.x.toFixed(decimals), 
						y: n.y.toFixed(decimals), 
						series: n.series, 
						index: n.index,
						nearest: n
					});
					
				}
				
				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('<div class="flotr-mouse-value"></div>');
			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) {
			image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>';
			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');
	}
});

FlotrExtended.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 FlotrExtended.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 FlotrExtended.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(FlotrExtended.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 FlotrExtended.Color) return color;

		var result, Color = FlotrExtended.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 FlotrExtended.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]
	}
});

FlotrExtended.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: FlotrExtended.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 = FlotrExtended.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 FlotrExtended.Date.format(d, axis.options.timeFormat);
		
		var span = axis.max - axis.min,
		    t = axis.tickSize * FlotrExtended.Date.timeUnits[axis.tickUnit];
				
		return FlotrExtended.Date.format(d, FlotrExtended.Date.getFormat(t, span));
	},
	generator: function(axis) {
		var ticks = [],
			d = new Date(axis.min),
			tu = FlotrExtended.Date.timeUnits;
		
		var step = axis.tickSize * tu[axis.tickUnit];

		switch (axis.tickUnit) {
			case "millisecond": d.setUTCMilliseconds(FlotrExtended.floorInBase(d.getUTCMilliseconds(), axis.tickSize)); break;
			case "second": d.setUTCSeconds(FlotrExtended.floorInBase(d.getUTCSeconds(), axis.tickSize)); break;
			case "minute": d.setUTCMinutes(FlotrExtended.floorInBase(d.getUTCMinutes(), axis.tickSize)); break;
			case "hour":   d.setUTCHours(FlotrExtended.floorInBase(d.getUTCHours(), axis.tickSize)); break;
			case "month":  d.setUTCMonth(FlotrExtended.floorInBase(d.getUTCMonth(), axis.tickSize)); break;
			case "year":   d.setUTCFullYear(FlotrExtended.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:FlotrExtended.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 **/
FlotrExtended.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
        paired: false,			// Is this line paired for a spread
        pair: 0,				// What's it paired with?
        lineOpacity: 1			// Lines can have opacity!
	},
	/**
	 * 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);

			ctx.strokeStyle = "rgba(0,0,0,0.2)";
			this.lines.plot(series, offset);

			if(series.lines.fill && !series.lines.paired) {
				ctx.fillStyle = "rgba(0,0,0,0.05)";
				this.lines.plotArea(series, offset + sw/2);
			}
		}

		ctx.lineWidth = lw;
		ctx.strokeStyle = series.color;
		
		if(series.lines.fill){
			ctx.fillStyle = this.processColor(series.lines.fillColor || series.color, {opacity: series.lines.fillOpacity});
			
			if(series.lines.paired) {
            	var pair = series.lines.pair;
            	this.lines.plotAreaBetween(this.series[pair],this.series[pair+1]);            	
            } else {
            	this.lines.plotArea(series, 0);
            }
			
		}

		this.lines.plot(series, 0);
		ctx.restore();
	},	
	plot: function(series, offset){
		var ctx = this.ctx,
		    xa = series.xaxis,
		    ya = series.yaxis,
  			data = series.data, 
  			length = data.length - 1, i;
			
		if(data.length < 2) return;

		var prevx = xa.d2p(data[0][0]),
		    prevy = ya.d2p(data[0][1]) + offset;

		ctx.beginPath();
		ctx.moveTo(prevx, prevy);

		for(i = 0; i < length; ++i){
			var x1 = data[i][0],   y1 = data[i][1],
			    x2 = data[i+1][0], y2 = data[i+1][1];

			// To allow empty values
			if (y1 === null || y2 === null) continue;
      
			/**
			 * Clip with ymin.
			 */
			if(y1 <= y2 && y1 < ya.min){
				/**
				 * Line segment is outside the drawing area.
				 */
				if(y2 < ya.min) continue;
				
				/**
				 * Compute new intersection point.
				 */
				x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = ya.min;
			}
			else if(y2 <= y1 && y2 < ya.min){
				if(y1 < ya.min) continue;
				x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = ya.min;
			}

			/**
			 * Clip with ymax.
			 */ 
			if(y1 >= y2 && y1 > ya.max) {
				if(y2 > ya.max) continue;
				x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;
				y1 = ya.max;
			}
			else if(y2 >= y1 && y2 > ya.max){
				if(y1 > ya.max) continue;
				x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;
				y2 = ya.max;
			}

			/**
			 * Clip with xmin.
			 */
			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;
			}

			/**
			 * Clip with xmax.
			 */
			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;
			}

			if(prevx != xa.d2p(x1) || prevy != ya.d2p(y1) + offset)
				ctx.moveTo(xa.d2p(x1), ya.d2p(y1) + offset);
			
			prevx = xa.d2p(x2);
			prevy = ya.d2p(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){
		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;
      
		if(data.length < 2) return;
    
		ctx.beginPath();
    
		for(var i = 0; i < length; ++i){
			var x1 = data[i][0],   y1 = data[i][1],
			    x2 = data[i+1][0], 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;
			}

			if(first){
				ctx.moveTo(xa.d2p(x1), ya.d2p(bottom) + offset);
				first = false;
			}
			
			/**
			 * Now check the case where both is outside.
			 */
			if(y1 >= ya.max && y2 >= ya.max){
				ctx.lineTo(xa.d2p(x1), ya.d2p(ya.max) + offset);
				ctx.lineTo(xa.d2p(x2), ya.d2p(ya.max) + offset);
				continue;
			}
			else if(y1 <= ya.min && y2 <= ya.min){
				ctx.lineTo(xa.d2p(x1), ya.d2p(ya.min) + offset);
				ctx.lineTo(xa.d2p(x2), ya.d2p(ya.min) + 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;
			}

			/**
			 * If the x value was changed we got a rectangle to fill.
			 */
			if(x1 != x1old){
				top = (y1 <= ya.min) ? top = ya.min : ya.max;
				ctx.lineTo(xa.d2p(x1old), ya.d2p(top) + offset);
				ctx.lineTo(xa.d2p(x1), ya.d2p(top) + offset);
			}
		   	
			/**
			 * Fill the triangles.
			 */
			ctx.lineTo(xa.d2p(x1), ya.d2p(y1) + offset);
			ctx.lineTo(xa.d2p(x2), ya.d2p(y2) + offset);

			/**
			 * Fill the other rectangle if it's there.
			 */
			if(x2 != x2old){
				top = (y2 <= ya.min) ? ya.min : ya.max;
				ctx.lineTo(xa.d2p(x2old), ya.d2p(top) + offset);
				ctx.lineTo(xa.d2p(x2), ya.d2p(top) + offset);
			}

			lastX = Math.max(x2, x2old);
		}
		
		ctx.lineTo(xa.d2p(xa.max), ya.d2p(bottom) + offset);
		ctx.closePath();
		ctx.fill();
	},
		
	/**
	 * Function used to fill area between paired data for spreads
	 * @param {Object} series_top - The top series
	 * @param {Object} series_bot - The bottom series
	 * @param {Object} offset
	 */
	plotAreaBetween: function(series_top, series_bot){

		var ctx = this.ctx,
	    data_top = series_top.data,
	    data_bot = series_bot.data;	

	    xa = series_top.xaxis,
	    ya = series_top.yaxis;
		
		if (data_top.length < 2 || data_bot.length < 2) {
            return;
        } 

		// For each set of points, draw two filled in rectangles, all together this will
        // create the spread.
        for (topindex = 1; topindex < data_top.length; topindex++) {
        	
            // Assume positive values - this is DANGEROUS but we'll let it go, for now.        	
        	var x1 = xa.d2p(data_top[topindex-1][0]);
        	var y1 = ya.d2p(data_top[topindex-1][1]);
        	var x2 = xa.d2p(data_top[topindex][0]);
        	var y2 = ya.d2p(data_top[topindex][1]);
        	var x3 = xa.d2p(data_bot[topindex][0]);
        	var y3 = ya.d2p(data_bot[topindex][1]);
        	var x4 = xa.d2p(data_bot[topindex-1][0]);
        	var y4 = ya.d2p(data_bot[topindex-1][1]);
        	
        	ctx.beginPath();
        	ctx.moveTo(x1,y1);
        	ctx.lineTo(x2,y2); 
        	ctx.lineTo(x3,y3); 
        	ctx.lineTo(x4,y4); 
        	ctx.lineTo(x1,y1); 
        	ctx.closePath();
        	ctx.fill();            
            
        }
	
	}	
	
});

/** Bars **/
FlotrExtended.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 stackOffset = 0;
			if(series.bars.stacked) {
				$H(xa.values).each(function(pair) {
					if (pair.key == x) {
						stackOffset = pair.value.stack || 0;
						pair.value.stack = stackOffset + y;
					}
				});
			}

			// @todo: fix horizontal bars support
			// Horizontal bars
			if(series.bars.horizontal)
				var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;
			else 
				var left = x - (series.bars.centered ? barWidth/2 : 0), right = x + barWidth - (series.bars.centered ? barWidth/2 : 0), bottom = stackOffset, top = y + stackOffset;

			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;
			}
      
			/**
			 * 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.bars.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, 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 stackOffset = 0;
			if(series.bars.stacked) {
				$H(xa.values).each(function(pair) {
					if (pair.key == x) {
						stackOffset = pair.value.stackShadow || 0;
						pair.value.stackShadow = stackOffset + y;
					}
				});
			}
			
			// Horizontal bars
			if(series.bars.horizontal) 
				var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;
			else 
				var left = x - (series.bars.centered ? barWidth/2 : 0), right = x + barWidth - (series.bars.centered ? barWidth/2 : 0), bottom = stackOffset, top = y + stackOffset;
			
			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 = Math.max(0, 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.plotWidth), width, height);
		}
	},
	extendXRange: function(axis) {
		if(axis.options.max == null){
			var newmin = axis.min,
			    newmax = axis.max,
			    i, j, x, s, b,
			    stackedSums = [], 
			    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) {
						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) {
								x = s.data[j][0]+'';
								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];
								lastSerie = s;
							}
						}
				    
						for (j in stackedSums) {
							newmax = Math.max(stackedSums[j], newmax);
						}
					}
				}
			}
			axis.lastSerie = lastSerie;
			axis.max = newmax;
			axis.min = newmin;
		}
	},
	extendYRange: function(axis){
		if(axis.options.max == null){
			var newmax = axis.max,
				  x, i, j, s, b,
				  stackedSums = {},
				  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) {
					// 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]+'';
								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];
								lastSerie = s;
							}
						}
						
						for (j in stackedSums) {
							newmax = Math.max(stackedSums[j], newmax);
						}
					}
				}
			}
			axis.lastSerie = lastSerie;
			axis.max = newmax;
		}
	}
});

/** Points **/
FlotrExtended.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
 */
FlotrExtended.defaultPieLabelFormatter = function(slice) {
	return (slice.fraction*100).toFixed(2)+'%';
};

FlotrExtended.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: FlotrExtended.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 = ['<div style="color:' + this.options.grid.color + '" class="flotr-labels">'];
		
		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 divStyle = 'position:absolute;top:' + (distY - 5) + 'px;'; //@todo: change
					if (textAlignRight)
						divStyle += 'right:'+(this.canvasWidth - distX)+'px;text-align:right;';
					else 
						divStyle += 'left:'+distX+'px;text-align:left;';
					html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>');
				}
				else {
					style.textAlign = textAlignRight ? 'right' : 'left';
					style.textBaseline = textAlignTop ? 'top' : 'bottom';
					FlotrExtended.drawText(ctx, label, distX, distY, style);
				}
			}
		}, this);
		
		if (options.HtmlText || !this.textEnabled) {
			html.push('</div>');    
			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();
	}
});


/** Candles **/
FlotrExtended.addType('candles', {
	options: {
		show: false,           // => 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
 */
FlotrExtended.defaultMarkerFormatter = function(obj){
	return (Math.round(obj.y*100)/100)+'';
};

FlotrExtended.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: FlotrExtended.defaultMarkerFormatter
	},
	/**
	 * 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], xPos = xa.d2p(x),
			    y = data[i][1], 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);
    
		FlotrExtended.drawText(ctx, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left'});
	}
});

FlotrExtended.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();
	}
});

FlotrExtended.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();
		}
	}/*,
	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;
		}
	}*/
});

FlotrExtended.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 (FlotrExtended.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 = ['<colgroup><col />'];
		
		// First row : series' labels
		var html = ['<tr class="first-row">'];
		html.push('<th>&nbsp;</th>');
		for (i = 0; i < s.length; ++i) {
			html.push('<th scope="col">'+(s[i].label || String.fromCharCode(65+i))+'</th>');
			colgroup.push('<col />');
		}
		html.push('</tr>');
		
		// Data rows
		for (j = 0; j < datagrid.length; ++j) {
			html.push('<tr>');
			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+'</'+tag+'>');
			}
			html.push('</tr>');
		}
		colgroup.push('</colgroup>');
		t.update(colgroup.join('')+html.join(''));
		
		if (!Prototype.Browser.IE) {
			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 (FlotrExtended.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) {
			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);
	}
});