diff options
| -rw-r--r-- | app/controllers/public_body_controller.rb | 20 | ||||
| -rw-r--r-- | app/views/public_body/statistics.html.erb | 6 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.axislabels.js | 6 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.axislabels.min.js | 1 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.errorbars.js | 353 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.errorbars.min.js | 64 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.js | 3078 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.min.js | 30 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.tickrotor.js | 205 | ||||
| -rw-r--r-- | public/javascripts/jquery.flot.tickrotor.min.js | 1 | ||||
| -rw-r--r-- | public/javascripts/stats-graphs.js | 52 | 
11 files changed, 3711 insertions, 105 deletions
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 1f7032eed..505325df3 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -201,7 +201,7 @@ class PublicBodyController < ApplicationController              raise ActiveRecord::RecordNotFound.new("Page not enabled")          end -        per_graph = 8 +        per_graph = 10          minimum_requests = AlaveteliConfiguration::minimum_requests_for_statistics          # Make sure minimum_requests is > 0 to avoid division-by-zero          minimum_requests = [minimum_requests, 1].max @@ -251,17 +251,27 @@ class PublicBodyController < ApplicationController                                                           minimum_requests)                  end -                data_to_draw = { +                # We just need the URL and name of each public body: +                data['public_bodies'].map! { |pb| +                    {'name' => pb.name, 'url' => public_body_path(pb)} +                } + +                data_to_draw = Hash.new { |h, k| h[k] = [] } +                data_to_draw.update({                      'id' => "#{column}-#{highest ? 'highest' : 'lowest'}",                      'x_axis' => _('Public Bodies'),                      'y_axis' => graph_properties[:y_axis],                      'errorbars' => percentages, -                    'title' => graph_properties[:title]} +                    'title' => graph_properties[:title] +                })                  if data                      data_to_draw.update(data) -                    data_to_draw['x_values'] = data['public_bodies'].each_with_index.map { |pb, i| i } -                    data_to_draw['x_ticks'] = data['public_bodies'].each_with_index.map { |pb, i| [i, pb.name] } +                    data['public_bodies'].each_with_index { |pb, i| +                        data_to_draw['x_values'].push i +                        data_to_draw['x_ticks'].push [i, pb['name']] +                        data_to_draw['tooltips'].push pb['name'] +                    }                  end                  @graph_list.push data_to_draw diff --git a/app/views/public_body/statistics.html.erb b/app/views/public_body/statistics.html.erb index 840af0c10..6ea253260 100644 --- a/app/views/public_body/statistics.html.erb +++ b/app/views/public_body/statistics.html.erb @@ -52,9 +52,9 @@ are due to him.") %></p>              </tr>            </thead>            <tbody> -            <% graph_data['x_ticks'].each_with_index do |pb_and_index, i| %> +            <% graph_data['public_bodies'].each_with_index do |pb, i| %>                <tr> -                <td><%= pb_and_index[1] %></td> +                <td><%= link_to pb['name'], pb['url'] %></td>                  <td class="statistic"><%= graph_data['y_values'][i].round %></td>                </tr>              <% end %> @@ -70,6 +70,6 @@ are due to him.") %></p>    var graphs_data = <%= @graph_list.to_json.html_safe %>;  </script>  <!--[if lte IE 8]><%= javascript_include_tag 'excanvas.min.js' %><![endif]--> -<%= javascript_include_tag 'jquery.flot.min.js', 'jquery.flot.errorbars.min.js', 'jquery.flot.axislabels.js', 'stats-graphs.js' %> +<%= javascript_include_tag 'jquery.flot.min.js', 'jquery.flot.errorbars.min.js', 'jquery.flot.tickrotor.min.js', 'jquery.flot.axislabels.min.js', 'stats-graphs.js' %>  </div> diff --git a/public/javascripts/jquery.flot.axislabels.js b/public/javascripts/jquery.flot.axislabels.js index d75b03ba9..c6f77aad4 100644 --- a/public/javascripts/jquery.flot.axislabels.js +++ b/public/javascripts/jquery.flot.axislabels.js @@ -61,7 +61,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.          this.height = 0;      } -    AxisLabel.prototype.delete = function() { +    AxisLabel.prototype['delete'] = function() {      }; @@ -144,7 +144,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.          }      }; -    HtmlAxisLabel.prototype.delete = function() { +    HtmlAxisLabel.prototype['delete'] = function() {          if (this.elem) {              this.elem.remove();          } @@ -357,7 +357,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                              axisLabels[axisName].width;                          opts.labelHeight = axis.labelHeight;                          opts.labelWidth = axis.labelWidth; -                        axisLabels[axisName].delete(); +                        axisLabels[axisName]['delete']();                          delete axisLabels[axisName];                      } diff --git a/public/javascripts/jquery.flot.axislabels.min.js b/public/javascripts/jquery.flot.axislabels.min.js new file mode 100644 index 000000000..684d6173a --- /dev/null +++ b/public/javascripts/jquery.flot.axislabels.min.js @@ -0,0 +1 @@ +(function($){var options={};function canvasSupported(){return !!document.createElement("canvas").getContext}function canvasTextSupported(){if(!canvasSupported()){return false}var dummy_canvas=document.createElement("canvas");var context=dummy_canvas.getContext("2d");return typeof context.fillText=="function"}function css3TransitionSupported(){var div=document.createElement("div");return typeof div.style.MozTransition!="undefined"||typeof div.style.OTransition!="undefined"||typeof div.style.webkitTransition!="undefined"||typeof div.style.transition!="undefined"}function AxisLabel(axisName,position,padding,plot,opts){this.axisName=axisName;this.position=position;this.padding=padding;this.plot=plot;this.opts=opts;this.width=0;this.height=0}AxisLabel.prototype["delete"]=function(){};CanvasAxisLabel.prototype=new AxisLabel();CanvasAxisLabel.prototype.constructor=CanvasAxisLabel;function CanvasAxisLabel(axisName,position,padding,plot,opts){AxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts)}CanvasAxisLabel.prototype.calculateSize=function(){if(!this.opts.axisLabelFontSizePixels){this.opts.axisLabelFontSizePixels=14}if(!this.opts.axisLabelFontFamily){this.opts.axisLabelFontFamily="sans-serif"}var textWidth=this.opts.axisLabelFontSizePixels+this.padding;var textHeight=this.opts.axisLabelFontSizePixels+this.padding;if(this.position=="left"||this.position=="right"){this.width=this.opts.axisLabelFontSizePixels+this.padding;this.height=0}else{this.width=0;this.height=this.opts.axisLabelFontSizePixels+this.padding}};CanvasAxisLabel.prototype.draw=function(box){var ctx=this.plot.getCanvas().getContext("2d");ctx.save();ctx.font=this.opts.axisLabelFontSizePixels+"px "+this.opts.axisLabelFontFamily;var width=ctx.measureText(this.opts.axisLabel).width;var height=this.opts.axisLabelFontSizePixels;var x,y,angle=0;if(this.position=="top"){x=box.left+box.width/2-width/2;y=box.top+height*0.72}else{if(this.position=="bottom"){x=box.left+box.width/2-width/2;y=box.top+box.height-height*0.72}else{if(this.position=="left"){x=box.left+height*0.72;y=box.height/2+box.top+width/2;angle=-Math.PI/2}else{if(this.position=="right"){x=box.left+box.width-height*0.72;y=box.height/2+box.top-width/2;angle=Math.PI/2}}}}ctx.translate(x,y);ctx.rotate(angle);ctx.fillText(this.opts.axisLabel,0,0);ctx.restore()};HtmlAxisLabel.prototype=new AxisLabel();HtmlAxisLabel.prototype.constructor=HtmlAxisLabel;function HtmlAxisLabel(axisName,position,padding,plot,opts){AxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts);this.elem=null}HtmlAxisLabel.prototype.calculateSize=function(){var elem=$('<div class="axisLabels" style="position:absolute;">'+this.opts.axisLabel+"</div>");this.plot.getPlaceholder().append(elem);this.labelWidth=elem.outerWidth(true);this.labelHeight=elem.outerHeight(true);elem.remove();this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelWidth+this.padding}else{this.height=this.labelHeight+this.padding}};HtmlAxisLabel.prototype["delete"]=function(){if(this.elem){this.elem.remove()}};HtmlAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("#"+this.axisName+"Label").remove();this.elem=$('<div id="'+this.axisName+'Label" " class="axisLabels" style="position:absolute;">'+this.opts.axisLabel+"</div>");this.plot.getPlaceholder().append(this.elem);if(this.position=="top"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+"px")}else{if(this.position=="bottom"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+box.height-this.labelHeight+"px")}else{if(this.position=="left"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+"px")}else{if(this.position=="right"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+box.width-this.labelWidth+"px")}}}}};CssTransformAxisLabel.prototype=new HtmlAxisLabel();CssTransformAxisLabel.prototype.constructor=CssTransformAxisLabel;function CssTransformAxisLabel(axisName,position,padding,plot,opts){HtmlAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts)}CssTransformAxisLabel.prototype.calculateSize=function(){HtmlAxisLabel.prototype.calculateSize.call(this);this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelHeight+this.padding}else{this.height=this.labelHeight+this.padding}};CssTransformAxisLabel.prototype.transforms=function(degrees,x,y){var stransforms={"-moz-transform":"","-webkit-transform":"","-o-transform":"","-ms-transform":""};if(x!=0||y!=0){var stdTranslate=" translate("+x+"px, "+y+"px)";stransforms["-moz-transform"]+=stdTranslate;stransforms["-webkit-transform"]+=stdTranslate;stransforms["-o-transform"]+=stdTranslate;stransforms["-ms-transform"]+=stdTranslate}if(degrees!=0){var rotation=degrees/90;var stdRotate=" rotate("+degrees+"deg)";stransforms["-moz-transform"]+=stdRotate;stransforms["-webkit-transform"]+=stdRotate;stransforms["-o-transform"]+=stdRotate;stransforms["-ms-transform"]+=stdRotate}var s="top: 0; left: 0; ";for(var prop in stransforms){if(stransforms[prop]){s+=prop+":"+stransforms[prop]+";"}}s+=";";return s};CssTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets={x:0,y:0,degrees:0};if(this.position=="bottom"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top+box.height-this.labelHeight}else{if(this.position=="top"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top}else{if(this.position=="left"){offsets.degrees=-90;offsets.x=box.left-this.labelWidth/2+this.labelHeight/2;offsets.y=box.height/2+box.top}else{if(this.position=="right"){offsets.degrees=90;offsets.x=box.left+box.width-this.labelWidth/2-this.labelHeight/2;offsets.y=box.height/2+box.top}}}}return offsets};CssTransformAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("."+this.axisName+"Label").remove();var offsets=this.calculateOffsets(box);this.elem=$('<div class="axisLabels '+this.axisName+'Label" style="position:absolute; color: '+this.opts.color+"; "+this.transforms(offsets.degrees,offsets.x,offsets.y)+'">'+this.opts.axisLabel+"</div>");this.plot.getPlaceholder().append(this.elem)};IeTransformAxisLabel.prototype=new CssTransformAxisLabel();IeTransformAxisLabel.prototype.constructor=IeTransformAxisLabel;function IeTransformAxisLabel(axisName,position,padding,plot,opts){CssTransformAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts);this.requiresResize=false}IeTransformAxisLabel.prototype.transforms=function(degrees,x,y){var s="";if(degrees!=0){var rotation=degrees/90;while(rotation<0){rotation+=4}s+=" filter: progid:DXImageTransform.Microsoft.BasicImage(rotation="+rotation+"); ";this.requiresResize=(this.position=="right")}if(x!=0){s+="left: "+x+"px; "}if(y!=0){s+="top: "+y+"px; "}return s};IeTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets=CssTransformAxisLabel.prototype.calculateOffsets.call(this,box);if(this.position=="top"){offsets.y=box.top+1}else{if(this.position=="left"){offsets.x=box.left;offsets.y=box.height/2+box.top-this.labelWidth/2}else{if(this.position=="right"){offsets.x=box.left+box.width-this.labelHeight;offsets.y=box.height/2+box.top-this.labelWidth/2}}}return offsets};IeTransformAxisLabel.prototype.draw=function(box){CssTransformAxisLabel.prototype.draw.call(this,box);if(this.requiresResize){this.elem=this.plot.getPlaceholder().find("."+this.axisName+"Label");this.elem.css("width",this.labelWidth);this.elem.css("height",this.labelHeight)}};function init(plot){var secondPass=false;var axisLabels={};var axisOffsetCounts={left:0,right:0,top:0,bottom:0};var defaultPadding=2;plot.hooks.draw.push(function(plot,ctx){var hasAxisLabels=false;if(!secondPass){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(axisName in axisLabels){axis.labelHeight=axis.labelHeight-axisLabels[axisName].height;axis.labelWidth=axis.labelWidth-axisLabels[axisName].width;opts.labelHeight=axis.labelHeight;opts.labelWidth=axis.labelWidth;axisLabels[axisName]["delete"]();delete axisLabels[axisName]}if(!opts||!opts.axisLabel||!axis.show){return}hasAxisLabels=true;var renderer=null;if(!opts.axisLabelUseHtml&&navigator.appName=="Microsoft Internet Explorer"){var ua=navigator.userAgent;var re=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");if(re.exec(ua)!=null){rv=parseFloat(RegExp.$1)}if(rv>=9&&!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=CssTransformAxisLabel}else{if(!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=IeTransformAxisLabel}else{if(opts.axisLabelUseCanvas){renderer=CanvasAxisLabel}else{renderer=HtmlAxisLabel}}}}else{if(opts.axisLabelUseHtml||(!css3TransitionSupported()&&!canvasTextSupported())&&!opts.axisLabelUseCanvas){renderer=HtmlAxisLabel}else{if(opts.axisLabelUseCanvas||!css3TransitionSupported()){renderer=CanvasAxisLabel}else{renderer=CssTransformAxisLabel}}}var padding=opts.axisLabelPadding===undefined?defaultPadding:opts.axisLabelPadding;axisLabels[axisName]=new renderer(axisName,axis.position,padding,plot,opts);axisLabels[axisName].calculateSize();opts.labelHeight=axis.labelHeight+axisLabels[axisName].height;opts.labelWidth=axis.labelWidth+axisLabels[axisName].width});if(hasAxisLabels){secondPass=true;plot.setupGrid();plot.draw()}}else{secondPass=false;$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(!opts||!opts.axisLabel||!axis.show){return}axisLabels[axisName].draw(axis.box)})}})}$.plot.plugins.push({init:init,options:options,name:"axisLabels",version:"2.0b0"})})(jQuery);
\ No newline at end of file diff --git a/public/javascripts/jquery.flot.errorbars.js b/public/javascripts/jquery.flot.errorbars.js new file mode 100644 index 000000000..729843678 --- /dev/null +++ b/public/javascripts/jquery.flot.errorbars.js @@ -0,0 +1,353 @@ +/* Flot plugin for plotting error bars. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +Error bars are used to show standard deviation and other statistical +properties in a plot. + +* Created by Rui Pereira  -  rui (dot) pereira (at) gmail (dot) com + +This plugin allows you to plot error-bars over points. Set "errorbars" inside +the points series to the axis name over which there will be error values in +your data array (*even* if you do not intend to plot them later, by setting +"show: null" on xerr/yerr). + +The plugin supports these options: + +	series: { +		points: { +			errorbars: "x" or "y" or "xy", +			xerr: { +				show: null/false or true, +				asymmetric: null/false or true, +				upperCap: null or "-" or function, +				lowerCap: null or "-" or function, +				color: null or color, +				radius: null or number +			}, +			yerr: { same options as xerr } +		} +	} + +Each data point array is expected to be of the type: + +	"x"  [ x, y, xerr ] +	"y"  [ x, y, yerr ] +	"xy" [ x, y, xerr, yerr ] + +Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and +equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric +error-bars on X and asymmetric on Y would be: + +	[ x, y, xerr, yerr_lower, yerr_upper ] + +By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will +draw a small cap perpendicular to the error bar. They can also be set to a +user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. + +	function drawSemiCircle( ctx, x, y, radius ) { +		ctx.beginPath(); +		ctx.arc( x, y, radius, 0, Math.PI, false ); +		ctx.moveTo( x - radius, y ); +		ctx.lineTo( x + radius, y ); +		ctx.stroke(); +	} + +Color and radius both default to the same ones of the points series if not +set. The independent radius parameter on xerr/yerr is useful for the case when +we may want to add error-bars to a line, without showing the interconnecting +points (with radius: 0), and still showing end caps on the error-bars. +shadowSize and lineWidth are derived as well from the points series. + +*/ + +(function ($) { +    var options = { +        series: { +            points: { +                errorbars: null, //should be 'x', 'y' or 'xy' +                xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, +                yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} +            } +        } +    }; + +    function processRawData(plot, series, data, datapoints){ +        if (!series.points.errorbars) +            return; + +        // x,y values +        var format = [ +            { x: true, number: true, required: true }, +            { y: true, number: true, required: true } +        ]; + +        var errors = series.points.errorbars; +        // error bars - first X then Y +        if (errors == 'x' || errors == 'xy') { +            // lower / upper error +            if (series.points.xerr.asymmetric) { +                format.push({ x: true, number: true, required: true }); +                format.push({ x: true, number: true, required: true }); +            } else +                format.push({ x: true, number: true, required: true }); +        } +        if (errors == 'y' || errors == 'xy') { +            // lower / upper error +            if (series.points.yerr.asymmetric) { +                format.push({ y: true, number: true, required: true }); +                format.push({ y: true, number: true, required: true }); +            } else +                format.push({ y: true, number: true, required: true }); +        } +        datapoints.format = format; +    } + +    function parseErrors(series, i){ + +        var points = series.datapoints.points; + +        // read errors from points array +        var exl = null, +                exu = null, +                eyl = null, +                eyu = null; +        var xerr = series.points.xerr, +                yerr = series.points.yerr; + +        var eb = series.points.errorbars; +        // error bars - first X +        if (eb == 'x' || eb == 'xy') { +            if (xerr.asymmetric) { +                exl = points[i + 2]; +                exu = points[i + 3]; +                if (eb == 'xy') +                    if (yerr.asymmetric){ +                        eyl = points[i + 4]; +                        eyu = points[i + 5]; +                    } else eyl = points[i + 4]; +            } else { +                exl = points[i + 2]; +                if (eb == 'xy') +                    if (yerr.asymmetric) { +                        eyl = points[i + 3]; +                        eyu = points[i + 4]; +                    } else eyl = points[i + 3]; +            } +        // only Y +        } else if (eb == 'y') +            if (yerr.asymmetric) { +                eyl = points[i + 2]; +                eyu = points[i + 3]; +            } else eyl = points[i + 2]; + +        // symmetric errors? +        if (exu == null) exu = exl; +        if (eyu == null) eyu = eyl; + +        var errRanges = [exl, exu, eyl, eyu]; +        // nullify if not showing +        if (!xerr.show){ +            errRanges[0] = null; +            errRanges[1] = null; +        } +        if (!yerr.show){ +            errRanges[2] = null; +            errRanges[3] = null; +        } +        return errRanges; +    } + +    function drawSeriesErrors(plot, ctx, s){ + +        var points = s.datapoints.points, +                ps = s.datapoints.pointsize, +                ax = [s.xaxis, s.yaxis], +                radius = s.points.radius, +                err = [s.points.xerr, s.points.yerr]; + +        //sanity check, in case some inverted axis hack is applied to flot +        var invertX = false; +        if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { +            invertX = true; +            var tmp = err[0].lowerCap; +            err[0].lowerCap = err[0].upperCap; +            err[0].upperCap = tmp; +        } + +        var invertY = false; +        if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { +            invertY = true; +            var tmp = err[1].lowerCap; +            err[1].lowerCap = err[1].upperCap; +            err[1].upperCap = tmp; +        } + +        for (var i = 0; i < s.datapoints.points.length; i += ps) { + +            //parse +            var errRanges = parseErrors(s, i); + +            //cycle xerr & yerr +            for (var e = 0; e < err.length; e++){ + +                var minmax = [ax[e].min, ax[e].max]; + +                //draw this error? +                if (errRanges[e * err.length]){ + +                    //data coordinates +                    var x = points[i], +                        y = points[i + 1]; + +                    //errorbar ranges +                    var upper = [x, y][e] + errRanges[e * err.length + 1], +                        lower = [x, y][e] - errRanges[e * err.length]; + +                    //points outside of the canvas +                    if (err[e].err == 'x') +                        if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) +                            continue; +                    if (err[e].err == 'y') +                        if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) +                            continue; + +                    // prevent errorbars getting out of the canvas +                    var drawUpper = true, +                        drawLower = true; + +                    if (upper > minmax[1]) { +                        drawUpper = false; +                        upper = minmax[1]; +                    } +                    if (lower < minmax[0]) { +                        drawLower = false; +                        lower = minmax[0]; +                    } + +                    //sanity check, in case some inverted axis hack is applied to flot +                    if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { +                        //swap coordinates +                        var tmp = lower; +                        lower = upper; +                        upper = tmp; +                        tmp = drawLower; +                        drawLower = drawUpper; +                        drawUpper = tmp; +                        tmp = minmax[0]; +                        minmax[0] = minmax[1]; +                        minmax[1] = tmp; +                    } + +                    // convert to pixels +                    x = ax[0].p2c(x), +                        y = ax[1].p2c(y), +                        upper = ax[e].p2c(upper); +                    lower = ax[e].p2c(lower); +                    minmax[0] = ax[e].p2c(minmax[0]); +                    minmax[1] = ax[e].p2c(minmax[1]); + +                    //same style as points by default +                    var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, +                        sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; + +                    //shadow as for points +                    if (lw > 0 && sw > 0) { +                        var w = sw / 2; +                        ctx.lineWidth = w; +                        ctx.strokeStyle = "rgba(0,0,0,0.1)"; +                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); + +                        ctx.strokeStyle = "rgba(0,0,0,0.2)"; +                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); +                    } + +                    ctx.strokeStyle = err[e].color? err[e].color: s.color; +                    ctx.lineWidth = lw; +                    //draw it +                    drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); +                } +            } +        } +    } + +    function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ + +        //shadow offset +        y += offset; +        upper += offset; +        lower += offset; + +        // error bar - avoid plotting over circles +        if (err.err == 'x'){ +            if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); +            else drawUpper = false; +            if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); +            else drawLower = false; +        } +        else { +            if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); +            else drawUpper = false; +            if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); +            else drawLower = false; +        } + +        //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps +        //this is a way to get errorbars on lines without visible connecting dots +        radius = err.radius != null? err.radius: radius; + +        // upper cap +        if (drawUpper) { +            if (err.upperCap == '-'){ +                if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); +                else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); +            } else if ($.isFunction(err.upperCap)){ +                if (err.err=='x') err.upperCap(ctx, upper, y, radius); +                else err.upperCap(ctx, x, upper, radius); +            } +        } +        // lower cap +        if (drawLower) { +            if (err.lowerCap == '-'){ +                if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); +                else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); +            } else if ($.isFunction(err.lowerCap)){ +                if (err.err=='x') err.lowerCap(ctx, lower, y, radius); +                else err.lowerCap(ctx, x, lower, radius); +            } +        } +    } + +    function drawPath(ctx, pts){ +        ctx.beginPath(); +        ctx.moveTo(pts[0][0], pts[0][1]); +        for (var p=1; p < pts.length; p++) +            ctx.lineTo(pts[p][0], pts[p][1]); +        ctx.stroke(); +    } + +    function draw(plot, ctx){ +        var plotOffset = plot.getPlotOffset(); + +        ctx.save(); +        ctx.translate(plotOffset.left, plotOffset.top); +        $.each(plot.getData(), function (i, s) { +            if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) +                drawSeriesErrors(plot, ctx, s); +        }); +        ctx.restore(); +    } + +    function init(plot) { +        plot.hooks.processRawData.push(processRawData); +        plot.hooks.draw.push(draw); +    } + +    $.plot.plugins.push({ +                init: init, +                options: options, +                name: 'errorbars', +                version: '1.0' +            }); +})(jQuery); diff --git a/public/javascripts/jquery.flot.errorbars.min.js b/public/javascripts/jquery.flot.errorbars.min.js index 72d7e3dc7..84a514b07 100644 --- a/public/javascripts/jquery.flot.errorbars.min.js +++ b/public/javascripts/jquery.flot.errorbars.min.js @@ -1,63 +1 @@ -/* Flot plugin for plotting error bars. - -Copyright (c) 2007-2013 IOLA and Ole Laursen. -Licensed under the MIT license. - -Error bars are used to show standard deviation and other statistical -properties in a plot. - -* Created by Rui Pereira  -  rui (dot) pereira (at) gmail (dot) com - -This plugin allows you to plot error-bars over points. Set "errorbars" inside -the points series to the axis name over which there will be error values in -your data array (*even* if you do not intend to plot them later, by setting -"show: null" on xerr/yerr). - -The plugin supports these options: - -	series: { -		points: { -			errorbars: "x" or "y" or "xy", -			xerr: { -				show: null/false or true, -				asymmetric: null/false or true, -				upperCap: null or "-" or function, -				lowerCap: null or "-" or function, -				color: null or color, -				radius: null or number -			}, -			yerr: { same options as xerr } -		} -	} - -Each data point array is expected to be of the type: - -	"x"  [ x, y, xerr ] -	"y"  [ x, y, yerr ] -	"xy" [ x, y, xerr, yerr ] - -Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and -equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric -error-bars on X and asymmetric on Y would be: - -	[ x, y, xerr, yerr_lower, yerr_upper ] - -By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will -draw a small cap perpendicular to the error bar. They can also be set to a -user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. - -	function drawSemiCircle( ctx, x, y, radius ) { -		ctx.beginPath(); -		ctx.arc( x, y, radius, 0, Math.PI, false ); -		ctx.moveTo( x - radius, y ); -		ctx.lineTo( x + radius, y ); -		ctx.stroke(); -	} - -Color and radius both default to the same ones of the points series if not -set. The independent radius parameter on xerr/yerr is useful for the case when -we may want to add error-bars to a line, without showing the interconnecting -points (with radius: 0), and still showing end caps on the error-bars. -shadowSize and lineWidth are derived as well from the points series. - -*/(function(e){function n(e,t,n,r){if(!t.points.errorbars)return;var i=[{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0}],s=t.points.errorbars;if(s=="x"||s=="xy")t.points.xerr.asymmetric?(i.push({x:!0,number:!0,required:!0}),i.push({x:!0,number:!0,required:!0})):i.push({x:!0,number:!0,required:!0});if(s=="y"||s=="xy")t.points.yerr.asymmetric?(i.push({y:!0,number:!0,required:!0}),i.push({y:!0,number:!0,required:!0})):i.push({y:!0,number:!0,required:!0});r.format=i}function r(e,t){var n=e.datapoints.points,r=null,i=null,s=null,o=null,u=e.points.xerr,a=e.points.yerr,f=e.points.errorbars;f=="x"||f=="xy"?u.asymmetric?(r=n[t+2],i=n[t+3],f=="xy"&&(a.asymmetric?(s=n[t+4],o=n[t+5]):s=n[t+4])):(r=n[t+2],f=="xy"&&(a.asymmetric?(s=n[t+3],o=n[t+4]):s=n[t+3])):f=="y"&&(a.asymmetric?(s=n[t+2],o=n[t+3]):s=n[t+2]),i==null&&(i=r),o==null&&(o=s);var l=[r,i,s,o];return u.show||(l[0]=null,l[1]=null),a.show||(l[2]=null,l[3]=null),l}function i(e,t,n){var i=n.datapoints.points,o=n.datapoints.pointsize,u=[n.xaxis,n.yaxis],a=n.points.radius,f=[n.points.xerr,n.points.yerr],l=!1;if(u[0].p2c(u[0].max)<u[0].p2c(u[0].min)){l=!0;var c=f[0].lowerCap;f[0].lowerCap=f[0].upperCap,f[0].upperCap=c}var h=!1;if(u[1].p2c(u[1].min)<u[1].p2c(u[1].max)){h=!0;var c=f[1].lowerCap;f[1].lowerCap=f[1].upperCap,f[1].upperCap=c}for(var p=0;p<n.datapoints.points.length;p+=o){var d=r(n,p);for(var v=0;v<f.length;v++){var m=[u[v].min,u[v].max];if(d[v*f.length]){var g=i[p],y=i[p+1],b=[g,y][v]+d[v*f.length+1],w=[g,y][v]-d[v*f.length];if(f[v].err=="x")if(y>u[1].max||y<u[1].min||b<u[0].min||w>u[0].max)continue;if(f[v].err=="y")if(g>u[0].max||g<u[0].min||b<u[1].min||w>u[1].max)continue;var E=!0,S=!0;b>m[1]&&(E=!1,b=m[1]),w<m[0]&&(S=!1,w=m[0]);if(f[v].err=="x"&&l||f[v].err=="y"&&h){var c=w;w=b,b=c,c=S,S=E,E=c,c=m[0],m[0]=m[1],m[1]=c}g=u[0].p2c(g),y=u[1].p2c(y),b=u[v].p2c(b),w=u[v].p2c(w),m[0]=u[v].p2c(m[0]),m[1]=u[v].p2c(m[1]);var x=f[v].lineWidth?f[v].lineWidth:n.points.lineWidth,T=n.points.shadowSize!=null?n.points.shadowSize:n.shadowSize;if(x>0&&T>0){var N=T/2;t.lineWidth=N,t.strokeStyle="rgba(0,0,0,0.1)",s(t,f[v],g,y,b,w,E,S,a,N+N/2,m),t.strokeStyle="rgba(0,0,0,0.2)",s(t,f[v],g,y,b,w,E,S,a,N/2,m)}t.strokeStyle=f[v].color?f[v].color:n.color,t.lineWidth=x,s(t,f[v],g,y,b,w,E,S,a,0,m)}}}}function s(t,n,r,i,s,u,a,f,l,c,h){i+=c,s+=c,u+=c,n.err=="x"?(s>r+l?o(t,[[s,i],[Math.max(r+l,h[0]),i]]):a=!1,u<r-l?o(t,[[Math.min(r-l,h[1]),i],[u,i]]):f=!1):(s<i-l?o(t,[[r,s],[r,Math.min(i-l,h[0])]]):a=!1,u>i+l?o(t,[[r,Math.max(i+l,h[1])],[r,u]]):f=!1),l=n.radius!=null?n.radius:l,a&&(n.upperCap=="-"?n.err=="x"?o(t,[[s,i-l],[s,i+l]]):o(t,[[r-l,s],[r+l,s]]):e.isFunction(n.upperCap)&&(n.err=="x"?n.upperCap(t,s,i,l):n.upperCap(t,r,s,l))),f&&(n.lowerCap=="-"?n.err=="x"?o(t,[[u,i-l],[u,i+l]]):o(t,[[r-l,u],[r+l,u]]):e.isFunction(n.lowerCap)&&(n.err=="x"?n.lowerCap(t,u,i,l):n.lowerCap(t,r,u,l)))}function o(e,t){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(var n=1;n<t.length;n++)e.lineTo(t[n][0],t[n][1]);e.stroke()}function u(t,n){var r=t.getPlotOffset();n.save(),n.translate(r.left,r.top),e.each(t.getData(),function(e,r){r.points.errorbars&&(r.points.xerr.show||r.points.yerr.show)&&i(t,n,r)}),n.restore()}function a(e){e.hooks.processRawData.push(n),e.hooks.draw.push(u)}var t={series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}};e.plot.plugins.push({init:a,options:t,name:"errorbars",version:"1.0"})})(jQuery);
\ No newline at end of file +(function($){var options={series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}};function processRawData(plot,series,data,datapoints){if(!series.points.errorbars){return}var format=[{x:true,number:true,required:true},{y:true,number:true,required:true}];var errors=series.points.errorbars;if(errors=="x"||errors=="xy"){if(series.points.xerr.asymmetric){format.push({x:true,number:true,required:true});format.push({x:true,number:true,required:true})}else{format.push({x:true,number:true,required:true})}}if(errors=="y"||errors=="xy"){if(series.points.yerr.asymmetric){format.push({y:true,number:true,required:true});format.push({y:true,number:true,required:true})}else{format.push({y:true,number:true,required:true})}}datapoints.format=format}function parseErrors(series,i){var points=series.datapoints.points;var exl=null,exu=null,eyl=null,eyu=null;var xerr=series.points.xerr,yerr=series.points.yerr;var eb=series.points.errorbars;if(eb=="x"||eb=="xy"){if(xerr.asymmetric){exl=points[i+2];exu=points[i+3];if(eb=="xy"){if(yerr.asymmetric){eyl=points[i+4];eyu=points[i+5]}else{eyl=points[i+4]}}}else{exl=points[i+2];if(eb=="xy"){if(yerr.asymmetric){eyl=points[i+3];eyu=points[i+4]}else{eyl=points[i+3]}}}}else{if(eb=="y"){if(yerr.asymmetric){eyl=points[i+2];eyu=points[i+3]}else{eyl=points[i+2]}}}if(exu==null){exu=exl}if(eyu==null){eyu=eyl}var errRanges=[exl,exu,eyl,eyu];if(!xerr.show){errRanges[0]=null;errRanges[1]=null}if(!yerr.show){errRanges[2]=null;errRanges[3]=null}return errRanges}function drawSeriesErrors(plot,ctx,s){var points=s.datapoints.points,ps=s.datapoints.pointsize,ax=[s.xaxis,s.yaxis],radius=s.points.radius,err=[s.points.xerr,s.points.yerr];var invertX=false;if(ax[0].p2c(ax[0].max)<ax[0].p2c(ax[0].min)){invertX=true;var tmp=err[0].lowerCap;err[0].lowerCap=err[0].upperCap;err[0].upperCap=tmp}var invertY=false;if(ax[1].p2c(ax[1].min)<ax[1].p2c(ax[1].max)){invertY=true;var tmp=err[1].lowerCap;err[1].lowerCap=err[1].upperCap;err[1].upperCap=tmp}for(var i=0;i<s.datapoints.points.length;i+=ps){var errRanges=parseErrors(s,i);for(var e=0;e<err.length;e++){var minmax=[ax[e].min,ax[e].max];if(errRanges[e*err.length]){var x=points[i],y=points[i+1];var upper=[x,y][e]+errRanges[e*err.length+1],lower=[x,y][e]-errRanges[e*err.length];if(err[e].err=="x"){if(y>ax[1].max||y<ax[1].min||upper<ax[0].min||lower>ax[0].max){continue}}if(err[e].err=="y"){if(x>ax[0].max||x<ax[0].min||upper<ax[1].min||lower>ax[1].max){continue}}var drawUpper=true,drawLower=true;if(upper>minmax[1]){drawUpper=false;upper=minmax[1]}if(lower<minmax[0]){drawLower=false;lower=minmax[0]}if((err[e].err=="x"&&invertX)||(err[e].err=="y"&&invertY)){var tmp=lower;lower=upper;upper=tmp;tmp=drawLower;drawLower=drawUpper;drawUpper=tmp;tmp=minmax[0];minmax[0]=minmax[1];minmax[1]=tmp}x=ax[0].p2c(x),y=ax[1].p2c(y),upper=ax[e].p2c(upper);lower=ax[e].p2c(lower);minmax[0]=ax[e].p2c(minmax[0]);minmax[1]=ax[e].p2c(minmax[1]);var lw=err[e].lineWidth?err[e].lineWidth:s.points.lineWidth,sw=s.points.shadowSize!=null?s.points.shadowSize:s.shadowSize;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w+w/2,minmax);ctx.strokeStyle="rgba(0,0,0,0.2)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w/2,minmax)}ctx.strokeStyle=err[e].color?err[e].color:s.color;ctx.lineWidth=lw;drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,0,minmax)}}}}function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){y+=offset;upper+=offset;lower+=offset;if(err.err=="x"){if(upper>x+radius){drawPath(ctx,[[upper,y],[Math.max(x+radius,minmax[0]),y]])}else{drawUpper=false}if(lower<x-radius){drawPath(ctx,[[Math.min(x-radius,minmax[1]),y],[lower,y]])}else{drawLower=false}}else{if(upper<y-radius){drawPath(ctx,[[x,upper],[x,Math.min(y-radius,minmax[0])]])}else{drawUpper=false}if(lower>y+radius){drawPath(ctx,[[x,Math.max(y+radius,minmax[1])],[x,lower]])}else{drawLower=false}}radius=err.radius!=null?err.radius:radius;if(drawUpper){if(err.upperCap=="-"){if(err.err=="x"){drawPath(ctx,[[upper,y-radius],[upper,y+radius]])}else{drawPath(ctx,[[x-radius,upper],[x+radius,upper]])}}else{if($.isFunction(err.upperCap)){if(err.err=="x"){err.upperCap(ctx,upper,y,radius)}else{err.upperCap(ctx,x,upper,radius)}}}}if(drawLower){if(err.lowerCap=="-"){if(err.err=="x"){drawPath(ctx,[[lower,y-radius],[lower,y+radius]])}else{drawPath(ctx,[[x-radius,lower],[x+radius,lower]])}}else{if($.isFunction(err.lowerCap)){if(err.err=="x"){err.lowerCap(ctx,lower,y,radius)}else{err.lowerCap(ctx,x,lower,radius)}}}}}function drawPath(ctx,pts){ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);for(var p=1;p<pts.length;p++){ctx.lineTo(pts[p][0],pts[p][1])}ctx.stroke()}function draw(plot,ctx){var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);$.each(plot.getData(),function(i,s){if(s.points.errorbars&&(s.points.xerr.show||s.points.yerr.show)){drawSeriesErrors(plot,ctx,s)}});ctx.restore()}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.draw.push(draw)}$.plot.plugins.push({init:init,options:options,name:"errorbars",version:"1.0"})})(jQuery);
\ No newline at end of file diff --git a/public/javascripts/jquery.flot.js b/public/javascripts/jquery.flot.js new file mode 100644 index 000000000..2855d2eb3 --- /dev/null +++ b/public/javascripts/jquery.flot.js @@ -0,0 +1,3078 @@ +/* Javascript plotting library for jQuery, version 0.8.2-alpha. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +*/ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + *   var c = $.color.extract($("#mydiv"), 'background-color'); + *   console.log(c.r, c.g, c.b, c.a); + *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + +	// Cache the prototype hasOwnProperty for faster access + +	var hasOwnProperty = Object.prototype.hasOwnProperty; + +	/////////////////////////////////////////////////////////////////////////// +	// The Canvas object is a wrapper around an HTML5 <canvas> tag. +	// +	// @constructor +	// @param {string} cls List of classes to apply to the canvas. +	// @param {element} container Element onto which to append the canvas. +	// +	// Requiring a container is a little iffy, but unfortunately canvas +	// operations don't work unless the canvas is attached to the DOM. + +	function Canvas(cls, container) { + +		var element = container.children("." + cls)[0]; + +		if (element == null) { + +			element = document.createElement("canvas"); +			element.className = cls; + +			$(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) +				.appendTo(container); + +			// If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas + +			if (!element.getContext) { +				if (window.G_vmlCanvasManager) { +					element = window.G_vmlCanvasManager.initElement(element); +				} else { +					throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); +				} +			} +		} + +		this.element = element; + +		var context = this.context = element.getContext("2d"); + +		// Determine the screen's ratio of physical to device-independent +		// pixels.  This is the ratio between the canvas width that the browser +		// advertises and the number of pixels actually present in that space. + +		// The iPhone 4, for example, has a device-independent width of 320px, +		// but its screen is actually 640px wide.  It therefore has a pixel +		// ratio of 2, while most normal devices have a ratio of 1. + +		var devicePixelRatio = window.devicePixelRatio || 1, +			backingStoreRatio = +				context.webkitBackingStorePixelRatio || +				context.mozBackingStorePixelRatio || +				context.msBackingStorePixelRatio || +				context.oBackingStorePixelRatio || +				context.backingStorePixelRatio || 1; + +		this.pixelRatio = devicePixelRatio / backingStoreRatio; + +		// Size the canvas to match the internal dimensions of its container + +		this.resize(container.width(), container.height()); + +		// Collection of HTML div layers for text overlaid onto the canvas + +		this.textContainer = null; +		this.text = {}; + +		// Cache of text fragments and metrics, so we can avoid expensively +		// re-calculating them when the plot is re-rendered in a loop. + +		this._textCache = {}; +	} + +	// Resizes the canvas to the given dimensions. +	// +	// @param {number} width New width of the canvas, in pixels. +	// @param {number} width New height of the canvas, in pixels. + +	Canvas.prototype.resize = function(width, height) { + +		if (width <= 0 || height <= 0) { +			throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); +		} + +		var element = this.element, +			context = this.context, +			pixelRatio = this.pixelRatio; + +		// Resize the canvas, increasing its density based on the display's +		// pixel ratio; basically giving it more pixels without increasing the +		// size of its element, to take advantage of the fact that retina +		// displays have that many more pixels in the same advertised space. + +		// Resizing should reset the state (excanvas seems to be buggy though) + +		if (this.width != width) { +			element.width = width * pixelRatio; +			element.style.width = width + "px"; +			this.width = width; +		} + +		if (this.height != height) { +			element.height = height * pixelRatio; +			element.style.height = height + "px"; +			this.height = height; +		} + +		// Save the context, so we can reset in case we get replotted.  The +		// restore ensure that we're really back at the initial state, and +		// should be safe even if we haven't saved the initial state yet. + +		context.restore(); +		context.save(); + +		// Scale the coordinate space to match the display density; so even though we +		// may have twice as many pixels, we still want lines and other drawing to +		// appear at the same size; the extra pixels will just make them crisper. + +		context.scale(pixelRatio, pixelRatio); +	}; + +	// Clears the entire canvas area, not including any overlaid HTML text + +	Canvas.prototype.clear = function() { +		this.context.clearRect(0, 0, this.width, this.height); +	}; + +	// Finishes rendering the canvas, including managing the text overlay. + +	Canvas.prototype.render = function() { + +		var cache = this._textCache; + +		// For each text layer, add elements marked as active that haven't +		// already been rendered, and remove those that are no longer active. + +		for (var layerKey in cache) { +			if (hasOwnProperty.call(cache, layerKey)) { + +				var layer = this.getTextLayer(layerKey), +					layerCache = cache[layerKey]; + +				layer.hide(); + +				for (var styleKey in layerCache) { +					if (hasOwnProperty.call(layerCache, styleKey)) { +						var styleCache = layerCache[styleKey]; +						for (var key in styleCache) { +							if (hasOwnProperty.call(styleCache, key)) { + +								var positions = styleCache[key].positions; + +								for (var i = 0, position; position = positions[i]; i++) { +									if (position.active) { +										if (!position.rendered) { +											layer.append(position.element); +											position.rendered = true; +										} +									} else { +										positions.splice(i--, 1); +										if (position.rendered) { +											position.element.detach(); +										} +									} +								} + +								if (positions.length == 0) { +									delete styleCache[key]; +								} +							} +						} +					} +				} + +				layer.show(); +			} +		} +	}; + +	// Creates (if necessary) and returns the text overlay container. +	// +	// @param {string} classes String of space-separated CSS classes used to +	//     uniquely identify the text layer. +	// @return {object} The jQuery-wrapped text-layer div. + +	Canvas.prototype.getTextLayer = function(classes) { + +		var layer = this.text[classes]; + +		// Create the text layer if it doesn't exist + +		if (layer == null) { + +			// Create the text layer container, if it doesn't exist + +			if (this.textContainer == null) { +				this.textContainer = $("<div class='flot-text'></div>") +					.css({ +						position: "absolute", +						top: 0, +						left: 0, +						bottom: 0, +						right: 0, +						'font-size': "smaller", +						color: "#545454" +					}) +					.insertAfter(this.element); +			} + +			layer = this.text[classes] = $("<div></div>") +				.addClass(classes) +				.css({ +					position: "absolute", +					top: 0, +					left: 0, +					bottom: 0, +					right: 0 +				}) +				.appendTo(this.textContainer); +		} + +		return layer; +	}; + +	// Creates (if necessary) and returns a text info object. +	// +	// The object looks like this: +	// +	// { +	//     width: Width of the text's wrapper div. +	//     height: Height of the text's wrapper div. +	//     element: The jQuery-wrapped HTML div containing the text. +	//     positions: Array of positions at which this text is drawn. +	// } +	// +	// The positions array contains objects that look like this: +	// +	// { +	//     active: Flag indicating whether the text should be visible. +	//     rendered: Flag indicating whether the text is currently visible. +	//     element: The jQuery-wrapped HTML div containing the text. +	//     x: X coordinate at which to draw the text. +	//     y: Y coordinate at which to draw the text. +	// } +	// +	// Each position after the first receives a clone of the original element. +	// +	// The idea is that that the width, height, and general 'identity' of the +	// text is constant no matter where it is placed; the placements are a +	// secondary property. +	// +	// Canvas maintains a cache of recently-used text info objects; getTextInfo +	// either returns the cached element or creates a new entry. +	// +	// @param {string} layer A string of space-separated CSS classes uniquely +	//     identifying the layer containing this text. +	// @param {string} text Text string to retrieve info for. +	// @param {(string|object)=} font Either a string of space-separated CSS +	//     classes or a font-spec object, defining the text's font and style. +	// @param {number=} angle Angle at which to rotate the text, in degrees. +	//     Angle is currently unused, it will be implemented in the future. +	// @param {number=} width Maximum width of the text before it wraps. +	// @return {object} a text info object. + +	Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + +		var textStyle, layerCache, styleCache, info; + +		// Cast the value to a string, in case we were given a number or such + +		text = "" + text; + +		// If the font is a font-spec object, generate a CSS font definition + +		if (typeof font === "object") { +			textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; +		} else { +			textStyle = font; +		} + +		// Retrieve (or create) the cache for the text's layer and styles + +		layerCache = this._textCache[layer]; + +		if (layerCache == null) { +			layerCache = this._textCache[layer] = {}; +		} + +		styleCache = layerCache[textStyle]; + +		if (styleCache == null) { +			styleCache = layerCache[textStyle] = {}; +		} + +		info = styleCache[text]; + +		// If we can't find a matching element in our cache, create a new one + +		if (info == null) { + +			var element = $("<div></div>").html(text) +				.css({ +					position: "absolute", +					'max-width': width, +					top: -9999 +				}) +				.appendTo(this.getTextLayer(layer)); + +			if (typeof font === "object") { +				element.css({ +					font: textStyle, +					color: font.color +				}); +			} else if (typeof font === "string") { +				element.addClass(font); +			} + +			info = styleCache[text] = { +				width: element.outerWidth(true), +				height: element.outerHeight(true), +				element: element, +				positions: [] +			}; + +			element.detach(); +		} + +		return info; +	}; + +	// Adds a text string to the canvas text overlay. +	// +	// The text isn't drawn immediately; it is marked as rendering, which will +	// result in its addition to the canvas on the next render pass. +	// +	// @param {string} layer A string of space-separated CSS classes uniquely +	//     identifying the layer containing this text. +	// @param {number} x X coordinate at which to draw the text. +	// @param {number} y Y coordinate at which to draw the text. +	// @param {string} text Text string to draw. +	// @param {(string|object)=} font Either a string of space-separated CSS +	//     classes or a font-spec object, defining the text's font and style. +	// @param {number=} angle Angle at which to rotate the text, in degrees. +	//     Angle is currently unused, it will be implemented in the future. +	// @param {number=} width Maximum width of the text before it wraps. +	// @param {string=} halign Horizontal alignment of the text; either "left", +	//     "center" or "right". +	// @param {string=} valign Vertical alignment of the text; either "top", +	//     "middle" or "bottom". + +	Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + +		var info = this.getTextInfo(layer, text, font, angle, width), +			positions = info.positions; + +		// Tweak the div's position to match the text's alignment + +		if (halign == "center") { +			x -= info.width / 2; +		} else if (halign == "right") { +			x -= info.width; +		} + +		if (valign == "middle") { +			y -= info.height / 2; +		} else if (valign == "bottom") { +			y -= info.height; +		} + +		// Determine whether this text already exists at this position. +		// If so, mark it for inclusion in the next render pass. + +		for (var i = 0, position; position = positions[i]; i++) { +			if (position.x == x && position.y == y) { +				position.active = true; +				return; +			} +		} + +		// If the text doesn't exist at this position, create a new entry + +		// For the very first position we'll re-use the original element, +		// while for subsequent ones we'll clone it. + +		position = { +			active: true, +			rendered: false, +			element: positions.length ? info.element.clone() : info.element, +			x: x, +			y: y +		}; + +		positions.push(position); + +		// Move the element to its final position within the container + +		position.element.css({ +			top: Math.round(y), +			left: Math.round(x), +			'text-align': halign	// In case the text wraps +		}); +	}; + +	// Removes one or more text strings from the canvas text overlay. +	// +	// If no parameters are given, all text within the layer is removed. +	// +	// Note that the text is not immediately removed; it is simply marked as +	// inactive, which will result in its removal on the next render pass. +	// This avoids the performance penalty for 'clear and redraw' behavior, +	// where we potentially get rid of all text on a layer, but will likely +	// add back most or all of it later, as when redrawing axes, for example. +	// +	// @param {string} layer A string of space-separated CSS classes uniquely +	//     identifying the layer containing this text. +	// @param {number=} x X coordinate of the text. +	// @param {number=} y Y coordinate of the text. +	// @param {string=} text Text string to remove. +	// @param {(string|object)=} font Either a string of space-separated CSS +	//     classes or a font-spec object, defining the text's font and style. +	// @param {number=} angle Angle at which the text is rotated, in degrees. +	//     Angle is currently unused, it will be implemented in the future. + +	Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { +		if (text == null) { +			var layerCache = this._textCache[layer]; +			if (layerCache != null) { +				for (var styleKey in layerCache) { +					if (hasOwnProperty.call(layerCache, styleKey)) { +						var styleCache = layerCache[styleKey]; +						for (var key in styleCache) { +							if (hasOwnProperty.call(styleCache, key)) { +								var positions = styleCache[key].positions; +								for (var i = 0, position; position = positions[i]; i++) { +									position.active = false; +								} +							} +						} +					} +				} +			} +		} else { +			var positions = this.getTextInfo(layer, text, font, angle).positions; +			for (var i = 0, position; position = positions[i]; i++) { +				if (position.x == x && position.y == y) { +					position.active = false; +				} +			} +		} +	}; + +	/////////////////////////////////////////////////////////////////////////// +	// The top-level container for the entire plot. + +    function Plot(placeholder, data_, options_, plugins) { +        // data is on the form: +        //   [ series1, series2 ... ] +        // where series is either just the data as [ [x1, y1], [x2, y2], ... ] +        // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + +        var series = [], +            options = { +                // the color theme used for graphs +                colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], +                legend: { +                    show: true, +                    noColumns: 1, // number of colums in legend table +                    labelFormatter: null, // fn: string -> string +                    labelBoxBorderColor: "#ccc", // border color for the little label boxes +                    container: null, // container (as jQuery object) to put legend in, null means default on top of graph +                    position: "ne", // position of default legend container within plot +                    margin: 5, // distance from grid edge to default legend container within plot +                    backgroundColor: null, // null means auto-detect +                    backgroundOpacity: 0.85, // set to 0 to avoid background +                    sorted: null    // default to no legend sorting +                }, +                xaxis: { +                    show: null, // null = auto-detect, true = always, false = never +                    position: "bottom", // or "top" +                    mode: null, // null or "time" +                    font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } +                    color: null, // base color, labels, ticks +                    tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" +                    transform: null, // null or f: number -> number to transform axis +                    inverseTransform: null, // if transform is set, this should be the inverse function +                    min: null, // min. value to show, null means set automatically +                    max: null, // max. value to show, null means set automatically +                    autoscaleMargin: null, // margin in % to add if auto-setting min/max +                    ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks +                    tickFormatter: null, // fn: number -> string +                    labelWidth: null, // size of tick labels in pixels +                    labelHeight: null, +                    reserveSpace: null, // whether to reserve space even if axis isn't shown +                    tickLength: null, // size in pixels of ticks, or "full" for whole line +                    alignTicksWithAxis: null, // axis number or null for no sync +                    tickDecimals: null, // no. of decimals, null means auto +                    tickSize: null, // number or [number, "unit"] +                    minTickSize: null // number or [number, "unit"] +                }, +                yaxis: { +                    autoscaleMargin: 0.02, +                    position: "left" // or "right" +                }, +                xaxes: [], +                yaxes: [], +                series: { +                    points: { +                        show: false, +                        radius: 3, +                        lineWidth: 2, // in pixels +                        fill: true, +                        fillColor: "#ffffff", +                        symbol: "circle" // or callback +                    }, +                    lines: { +                        // we don't put in show: false so we can see +                        // whether lines were actively disabled +                        lineWidth: 2, // in pixels +                        fill: false, +                        fillColor: null, +                        steps: false +                        // Omit 'zero', so we can later default its value to +                        // match that of the 'fill' option. +                    }, +                    bars: { +                        show: false, +                        lineWidth: 2, // in pixels +                        barWidth: 1, // in units of the x axis +                        fill: true, +                        fillColor: null, +                        align: "left", // "left", "right", or "center" +                        horizontal: false, +                        zero: true +                    }, +                    shadowSize: 3, +                    highlightColor: null +                }, +                grid: { +                    show: true, +                    aboveData: false, +                    color: "#545454", // primary color used for outline and labels +                    backgroundColor: null, // null for transparent, else color +                    borderColor: null, // set if different from the grid color +                    tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" +                    margin: 0, // distance from the canvas edge to the grid +                    labelMargin: 5, // in pixels +                    axisMargin: 8, // in pixels +                    borderWidth: 2, // in pixels +                    minBorderMargin: null, // in pixels, null means taken from points radius +                    markings: null, // array of ranges or fn: axes -> array of ranges +                    markingsColor: "#f4f4f4", +                    markingsLineWidth: 2, +                    // interactive stuff +                    clickable: false, +                    hoverable: false, +                    autoHighlight: true, // highlight in case mouse is near +                    mouseActiveRadius: 10 // how far the mouse can be away to activate an item +                }, +                interaction: { +                    redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow +                }, +                hooks: {} +            }, +        surface = null,     // the canvas for the plot itself +        overlay = null,     // canvas for interactive stuff on top of plot +        eventHolder = null, // jQuery object that events should be bound to +        ctx = null, octx = null, +        xaxes = [], yaxes = [], +        plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, +        plotWidth = 0, plotHeight = 0, +        hooks = { +            processOptions: [], +            processRawData: [], +            processDatapoints: [], +            processOffset: [], +            drawBackground: [], +            drawSeries: [], +            draw: [], +            bindEvents: [], +            drawOverlay: [], +            shutdown: [] +        }, +        plot = this; + +        // public functions +        plot.setData = setData; +        plot.setupGrid = setupGrid; +        plot.draw = draw; +        plot.getPlaceholder = function() { return placeholder; }; +        plot.getCanvas = function() { return surface.element; }; +        plot.getPlotOffset = function() { return plotOffset; }; +        plot.width = function () { return plotWidth; }; +        plot.height = function () { return plotHeight; }; +        plot.offset = function () { +            var o = eventHolder.offset(); +            o.left += plotOffset.left; +            o.top += plotOffset.top; +            return o; +        }; +        plot.getData = function () { return series; }; +        plot.getAxes = function () { +            var res = {}, i; +            $.each(xaxes.concat(yaxes), function (_, axis) { +                if (axis) +                    res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; +            }); +            return res; +        }; +        plot.getXAxes = function () { return xaxes; }; +        plot.getYAxes = function () { return yaxes; }; +        plot.c2p = canvasToAxisCoords; +        plot.p2c = axisToCanvasCoords; +        plot.getOptions = function () { return options; }; +        plot.highlight = highlight; +        plot.unhighlight = unhighlight; +        plot.triggerRedrawOverlay = triggerRedrawOverlay; +        plot.pointOffset = function(point) { +            return { +                left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), +                top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) +            }; +        }; +        plot.shutdown = shutdown; +        plot.resize = function () { +        	var width = placeholder.width(), +        		height = placeholder.height(); +            surface.resize(width, height); +            overlay.resize(width, height); +        }; + +        // public attributes +        plot.hooks = hooks; + +        // initialize +        initPlugins(plot); +        parseOptions(options_); +        setupCanvases(); +        setData(data_); +        setupGrid(); +        draw(); +        bindEvents(); + + +        function executeHooks(hook, args) { +            args = [plot].concat(args); +            for (var i = 0; i < hook.length; ++i) +                hook[i].apply(this, args); +        } + +        function initPlugins() { + +            // References to key classes, allowing plugins to modify them + +            var classes = { +                Canvas: Canvas +            }; + +            for (var i = 0; i < plugins.length; ++i) { +                var p = plugins[i]; +                p.init(plot, classes); +                if (p.options) +                    $.extend(true, options, p.options); +            } +        } + +        function parseOptions(opts) { + +            $.extend(true, options, opts); + +            // $.extend merges arrays, rather than replacing them.  When less +            // colors are provided than the size of the default palette, we +            // end up with those colors plus the remaining defaults, which is +            // not expected behavior; avoid it by replacing them here. + +            if (opts && opts.colors) { +            	options.colors = opts.colors; +            } + +            if (options.xaxis.color == null) +                options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); +            if (options.yaxis.color == null) +                options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + +            if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility +                options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; +            if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility +                options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; + +            if (options.grid.borderColor == null) +                options.grid.borderColor = options.grid.color; +            if (options.grid.tickColor == null) +                options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + +            // Fill in defaults for axis options, including any unspecified +            // font-spec fields, if a font-spec was provided. + +            // If no x/y axis options were provided, create one of each anyway, +            // since the rest of the code assumes that they exist. + +            var i, axisOptions, axisCount, +                fontDefaults = { +                    style: placeholder.css("font-style"), +                    size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), +                    variant: placeholder.css("font-variant"), +                    weight: placeholder.css("font-weight"), +                    family: placeholder.css("font-family") +                }; + +            fontDefaults.lineHeight = fontDefaults.size * 1.15; + +            axisCount = options.xaxes.length || 1; +            for (i = 0; i < axisCount; ++i) { + +                axisOptions = options.xaxes[i]; +                if (axisOptions && !axisOptions.tickColor) { +                    axisOptions.tickColor = axisOptions.color; +                } + +                axisOptions = $.extend(true, {}, options.xaxis, axisOptions); +                options.xaxes[i] = axisOptions; + +                if (axisOptions.font) { +                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); +                    if (!axisOptions.font.color) { +                        axisOptions.font.color = axisOptions.color; +                    } +                } +            } + +            axisCount = options.yaxes.length || 1; +            for (i = 0; i < axisCount; ++i) { + +                axisOptions = options.yaxes[i]; +                if (axisOptions && !axisOptions.tickColor) { +                    axisOptions.tickColor = axisOptions.color; +                } + +                axisOptions = $.extend(true, {}, options.yaxis, axisOptions); +                options.yaxes[i] = axisOptions; + +                if (axisOptions.font) { +                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); +                    if (!axisOptions.font.color) { +                        axisOptions.font.color = axisOptions.color; +                    } +                } +            } + +            // backwards compatibility, to be removed in future +            if (options.xaxis.noTicks && options.xaxis.ticks == null) +                options.xaxis.ticks = options.xaxis.noTicks; +            if (options.yaxis.noTicks && options.yaxis.ticks == null) +                options.yaxis.ticks = options.yaxis.noTicks; +            if (options.x2axis) { +                options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); +                options.xaxes[1].position = "top"; +            } +            if (options.y2axis) { +                options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); +                options.yaxes[1].position = "right"; +            } +            if (options.grid.coloredAreas) +                options.grid.markings = options.grid.coloredAreas; +            if (options.grid.coloredAreasColor) +                options.grid.markingsColor = options.grid.coloredAreasColor; +            if (options.lines) +                $.extend(true, options.series.lines, options.lines); +            if (options.points) +                $.extend(true, options.series.points, options.points); +            if (options.bars) +                $.extend(true, options.series.bars, options.bars); +            if (options.shadowSize != null) +                options.series.shadowSize = options.shadowSize; +            if (options.highlightColor != null) +                options.series.highlightColor = options.highlightColor; + +            // save options on axes for future reference +            for (i = 0; i < options.xaxes.length; ++i) +                getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; +            for (i = 0; i < options.yaxes.length; ++i) +                getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + +            // add hooks from options +            for (var n in hooks) +                if (options.hooks[n] && options.hooks[n].length) +                    hooks[n] = hooks[n].concat(options.hooks[n]); + +            executeHooks(hooks.processOptions, [options]); +        } + +        function setData(d) { +            series = parseData(d); +            fillInSeriesOptions(); +            processData(); +        } + +        function parseData(d) { +            var res = []; +            for (var i = 0; i < d.length; ++i) { +                var s = $.extend(true, {}, options.series); + +                if (d[i].data != null) { +                    s.data = d[i].data; // move the data instead of deep-copy +                    delete d[i].data; + +                    $.extend(true, s, d[i]); + +                    d[i].data = s.data; +                } +                else +                    s.data = d[i]; +                res.push(s); +            } + +            return res; +        } + +        function axisNumber(obj, coord) { +            var a = obj[coord + "axis"]; +            if (typeof a == "object") // if we got a real axis, extract number +                a = a.n; +            if (typeof a != "number") +                a = 1; // default to first axis +            return a; +        } + +        function allAxes() { +            // return flat array without annoying null entries +            return $.grep(xaxes.concat(yaxes), function (a) { return a; }); +        } + +        function canvasToAxisCoords(pos) { +            // return an object with x/y corresponding to all used axes +            var res = {}, i, axis; +            for (i = 0; i < xaxes.length; ++i) { +                axis = xaxes[i]; +                if (axis && axis.used) +                    res["x" + axis.n] = axis.c2p(pos.left); +            } + +            for (i = 0; i < yaxes.length; ++i) { +                axis = yaxes[i]; +                if (axis && axis.used) +                    res["y" + axis.n] = axis.c2p(pos.top); +            } + +            if (res.x1 !== undefined) +                res.x = res.x1; +            if (res.y1 !== undefined) +                res.y = res.y1; + +            return res; +        } + +        function axisToCanvasCoords(pos) { +            // get canvas coords from the first pair of x/y found in pos +            var res = {}, i, axis, key; + +            for (i = 0; i < xaxes.length; ++i) { +                axis = xaxes[i]; +                if (axis && axis.used) { +                    key = "x" + axis.n; +                    if (pos[key] == null && axis.n == 1) +                        key = "x"; + +                    if (pos[key] != null) { +                        res.left = axis.p2c(pos[key]); +                        break; +                    } +                } +            } + +            for (i = 0; i < yaxes.length; ++i) { +                axis = yaxes[i]; +                if (axis && axis.used) { +                    key = "y" + axis.n; +                    if (pos[key] == null && axis.n == 1) +                        key = "y"; + +                    if (pos[key] != null) { +                        res.top = axis.p2c(pos[key]); +                        break; +                    } +                } +            } + +            return res; +        } + +        function getOrCreateAxis(axes, number) { +            if (!axes[number - 1]) +                axes[number - 1] = { +                    n: number, // save the number for future reference +                    direction: axes == xaxes ? "x" : "y", +                    options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) +                }; + +            return axes[number - 1]; +        } + +        function fillInSeriesOptions() { + +            var neededColors = series.length, maxIndex = -1, i; + +            // Subtract the number of series that already have fixed colors or +            // color indexes from the number that we still need to generate. + +            for (i = 0; i < series.length; ++i) { +                var sc = series[i].color; +                if (sc != null) { +                    neededColors--; +                    if (typeof sc == "number" && sc > maxIndex) { +                        maxIndex = sc; +                    } +                } +            } + +            // If any of the series have fixed color indexes, then we need to +            // generate at least as many colors as the highest index. + +            if (neededColors <= maxIndex) { +                neededColors = maxIndex + 1; +            } + +            // Generate all the colors, using first the option colors and then +            // variations on those colors once they're exhausted. + +            var c, colors = [], colorPool = options.colors, +                colorPoolSize = colorPool.length, variation = 0; + +            for (i = 0; i < neededColors; i++) { + +                c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); + +                // Each time we exhaust the colors in the pool we adjust +                // a scaling factor used to produce more variations on +                // those colors. The factor alternates negative/positive +                // to produce lighter/darker colors. + +                // Reset the variation after every few cycles, or else +                // it will end up producing only white or black colors. + +                if (i % colorPoolSize == 0 && i) { +                    if (variation >= 0) { +                        if (variation < 0.5) { +                            variation = -variation - 0.2; +                        } else variation = 0; +                    } else variation = -variation; +                } + +                colors[i] = c.scale('rgb', 1 + variation); +            } + +            // Finalize the series options, filling in their colors + +            var colori = 0, s; +            for (i = 0; i < series.length; ++i) { +                s = series[i]; + +                // assign colors +                if (s.color == null) { +                    s.color = colors[colori].toString(); +                    ++colori; +                } +                else if (typeof s.color == "number") +                    s.color = colors[s.color].toString(); + +                // turn on lines automatically in case nothing is set +                if (s.lines.show == null) { +                    var v, show = true; +                    for (v in s) +                        if (s[v] && s[v].show) { +                            show = false; +                            break; +                        } +                    if (show) +                        s.lines.show = true; +                } + +                // If nothing was provided for lines.zero, default it to match +                // lines.fill, since areas by default should extend to zero. + +                if (s.lines.zero == null) { +                    s.lines.zero = !!s.lines.fill; +                } + +                // setup axes +                s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); +                s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); +            } +        } + +        function processData() { +            var topSentry = Number.POSITIVE_INFINITY, +                bottomSentry = Number.NEGATIVE_INFINITY, +                fakeInfinity = Number.MAX_VALUE, +                i, j, k, m, length, +                s, points, ps, x, y, axis, val, f, p, +                data, format; + +            function updateAxis(axis, min, max) { +                if (min < axis.datamin && min != -fakeInfinity) +                    axis.datamin = min; +                if (max > axis.datamax && max != fakeInfinity) +                    axis.datamax = max; +            } + +            $.each(allAxes(), function (_, axis) { +                // init axis +                axis.datamin = topSentry; +                axis.datamax = bottomSentry; +                axis.used = false; +            }); + +            for (i = 0; i < series.length; ++i) { +                s = series[i]; +                s.datapoints = { points: [] }; + +                executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); +            } + +            // first pass: clean and copy data +            for (i = 0; i < series.length; ++i) { +                s = series[i]; + +                data = s.data; +                format = s.datapoints.format; + +                if (!format) { +                    format = []; +                    // find out how to copy +                    format.push({ x: true, number: true, required: true }); +                    format.push({ y: true, number: true, required: true }); + +                    if (s.bars.show || (s.lines.show && s.lines.fill)) { +                        var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); +                        format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); +                        if (s.bars.horizontal) { +                            delete format[format.length - 1].y; +                            format[format.length - 1].x = true; +                        } +                    } + +                    s.datapoints.format = format; +                } + +                if (s.datapoints.pointsize != null) +                    continue; // already filled in + +                s.datapoints.pointsize = format.length; + +                ps = s.datapoints.pointsize; +                points = s.datapoints.points; + +                var insertSteps = s.lines.show && s.lines.steps; +                s.xaxis.used = s.yaxis.used = true; + +                for (j = k = 0; j < data.length; ++j, k += ps) { +                    p = data[j]; + +                    var nullify = p == null; +                    if (!nullify) { +                        for (m = 0; m < ps; ++m) { +                            val = p[m]; +                            f = format[m]; + +                            if (f) { +                                if (f.number && val != null) { +                                    val = +val; // convert to number +                                    if (isNaN(val)) +                                        val = null; +                                    else if (val == Infinity) +                                        val = fakeInfinity; +                                    else if (val == -Infinity) +                                        val = -fakeInfinity; +                                } + +                                if (val == null) { +                                    if (f.required) +                                        nullify = true; + +                                    if (f.defaultValue != null) +                                        val = f.defaultValue; +                                } +                            } + +                            points[k + m] = val; +                        } +                    } + +                    if (nullify) { +                        for (m = 0; m < ps; ++m) { +                            val = points[k + m]; +                            if (val != null) { +                                f = format[m]; +                                // extract min/max info +                                if (f.autoscale !== false) { +                                    if (f.x) { +                                        updateAxis(s.xaxis, val, val); +                                    } +                                    if (f.y) { +                                        updateAxis(s.yaxis, val, val); +                                    } +                                } +                            } +                            points[k + m] = null; +                        } +                    } +                    else { +                        // a little bit of line specific stuff that +                        // perhaps shouldn't be here, but lacking +                        // better means... +                        if (insertSteps && k > 0 +                            && points[k - ps] != null +                            && points[k - ps] != points[k] +                            && points[k - ps + 1] != points[k + 1]) { +                            // copy the point to make room for a middle point +                            for (m = 0; m < ps; ++m) +                                points[k + ps + m] = points[k + m]; + +                            // middle point has same y +                            points[k + 1] = points[k - ps + 1]; + +                            // we've added a point, better reflect that +                            k += ps; +                        } +                    } +                } +            } + +            // give the hooks a chance to run +            for (i = 0; i < series.length; ++i) { +                s = series[i]; + +                executeHooks(hooks.processDatapoints, [ s, s.datapoints]); +            } + +            // second pass: find datamax/datamin for auto-scaling +            for (i = 0; i < series.length; ++i) { +                s = series[i]; +                points = s.datapoints.points; +                ps = s.datapoints.pointsize; +                format = s.datapoints.format; + +                var xmin = topSentry, ymin = topSentry, +                    xmax = bottomSentry, ymax = bottomSentry; + +                for (j = 0; j < points.length; j += ps) { +                    if (points[j] == null) +                        continue; + +                    for (m = 0; m < ps; ++m) { +                        val = points[j + m]; +                        f = format[m]; +                        if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) +                            continue; + +                        if (f.x) { +                            if (val < xmin) +                                xmin = val; +                            if (val > xmax) +                                xmax = val; +                        } +                        if (f.y) { +                            if (val < ymin) +                                ymin = val; +                            if (val > ymax) +                                ymax = val; +                        } +                    } +                } + +                if (s.bars.show) { +                    // make sure we got room for the bar on the dancing floor +                    var delta; + +                    switch (s.bars.align) { +                        case "left": +                            delta = 0; +                            break; +                        case "right": +                            delta = -s.bars.barWidth; +                            break; +                        default: +                            delta = -s.bars.barWidth / 2; +                    } + +                    if (s.bars.horizontal) { +                        ymin += delta; +                        ymax += delta + s.bars.barWidth; +                    } +                    else { +                        xmin += delta; +                        xmax += delta + s.bars.barWidth; +                    } +                } + +                updateAxis(s.xaxis, xmin, xmax); +                updateAxis(s.yaxis, ymin, ymax); +            } + +            $.each(allAxes(), function (_, axis) { +                if (axis.datamin == topSentry) +                    axis.datamin = null; +                if (axis.datamax == bottomSentry) +                    axis.datamax = null; +            }); +        } + +        function setupCanvases() { + +            // Make sure the placeholder is clear of everything except canvases +            // from a previous plot in this container that we'll try to re-use. + +            placeholder.css("padding", 0) // padding messes up the positioning +                .children(":not(.flot-base,.flot-overlay)").remove(); + +            if (placeholder.css("position") == 'static') +                placeholder.css("position", "relative"); // for positioning labels and overlay + +            surface = new Canvas("flot-base", placeholder); +            overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features + +            ctx = surface.context; +            octx = overlay.context; + +            // define which element we're listening for events on +            eventHolder = $(overlay.element).unbind(); + +            // If we're re-using a plot object, shut down the old one + +            var existing = placeholder.data("plot"); + +            if (existing) { +                existing.shutdown(); +                overlay.clear(); +            } + +            // save in case we get replotted +            placeholder.data("plot", plot); +        } + +        function bindEvents() { +            // bind events +            if (options.grid.hoverable) { +                eventHolder.mousemove(onMouseMove); + +                // Use bind, rather than .mouseleave, because we officially +                // still support jQuery 1.2.6, which doesn't define a shortcut +                // for mouseenter or mouseleave.  This was a bug/oversight that +                // was fixed somewhere around 1.3.x.  We can return to using +                // .mouseleave when we drop support for 1.2.6. + +                eventHolder.bind("mouseleave", onMouseLeave); +            } + +            if (options.grid.clickable) +                eventHolder.click(onClick); + +            executeHooks(hooks.bindEvents, [eventHolder]); +        } + +        function shutdown() { +            if (redrawTimeout) +                clearTimeout(redrawTimeout); + +            eventHolder.unbind("mousemove", onMouseMove); +            eventHolder.unbind("mouseleave", onMouseLeave); +            eventHolder.unbind("click", onClick); + +            executeHooks(hooks.shutdown, [eventHolder]); +        } + +        function setTransformationHelpers(axis) { +            // set helper functions on the axis, assumes plot area +            // has been computed already + +            function identity(x) { return x; } + +            var s, m, t = axis.options.transform || identity, +                it = axis.options.inverseTransform; + +            // precompute how much the axis is scaling a point +            // in canvas space +            if (axis.direction == "x") { +                s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); +                m = Math.min(t(axis.max), t(axis.min)); +            } +            else { +                s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); +                s = -s; +                m = Math.max(t(axis.max), t(axis.min)); +            } + +            // data point to canvas coordinate +            if (t == identity) // slight optimization +                axis.p2c = function (p) { return (p - m) * s; }; +            else +                axis.p2c = function (p) { return (t(p) - m) * s; }; +            // canvas coordinate to data point +            if (!it) +                axis.c2p = function (c) { return m + c / s; }; +            else +                axis.c2p = function (c) { return it(m + c / s); }; +        } + +        function measureTickLabels(axis) { + +            var opts = axis.options, +                ticks = axis.ticks || [], +                labelWidth = opts.labelWidth || 0, +                labelHeight = opts.labelHeight || 0, +                maxWidth = labelWidth || axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, +                legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", +                layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, +                font = opts.font || "flot-tick-label tickLabel"; + +            for (var i = 0; i < ticks.length; ++i) { + +                var t = ticks[i]; + +                if (!t.label) +                    continue; + +                var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); + +                labelWidth = Math.max(labelWidth, info.width); +                labelHeight = Math.max(labelHeight, info.height); +            } + +            axis.labelWidth = opts.labelWidth || labelWidth; +            axis.labelHeight = opts.labelHeight || labelHeight; +        } + +        function allocateAxisBoxFirstPhase(axis) { +            // find the bounding box of the axis by looking at label +            // widths/heights and ticks, make room by diminishing the +            // plotOffset; this first phase only looks at one +            // dimension per axis, the other dimension depends on the +            // other axes so will have to wait + +            var lw = axis.labelWidth, +                lh = axis.labelHeight, +                pos = axis.options.position, +                tickLength = axis.options.tickLength, +                axisMargin = options.grid.axisMargin, +                padding = options.grid.labelMargin, +                all = axis.direction == "x" ? xaxes : yaxes, +                index, innermost; + +            // determine axis margin +            var samePosition = $.grep(all, function (a) { +                return a && a.options.position == pos && a.reserveSpace; +            }); +            if ($.inArray(axis, samePosition) == samePosition.length - 1) +                axisMargin = 0; // outermost + +            // Determine whether the axis is the first (innermost) on its side + +            innermost = $.inArray(axis, samePosition) == 0; + +            // determine tick length - if we're innermost, we can use "full" + +            if (tickLength == null) { +                if (innermost) +                    tickLength = "full"; +                else +                    tickLength = 5; +            } + +            if (!isNaN(+tickLength)) +                padding += +tickLength; + +            // compute box +            if (axis.direction == "x") { +                lh += padding; + +                if (pos == "bottom") { +                    plotOffset.bottom += lh + axisMargin; +                    axis.box = { top: surface.height - plotOffset.bottom, height: lh }; +                } +                else { +                    axis.box = { top: plotOffset.top + axisMargin, height: lh }; +                    plotOffset.top += lh + axisMargin; +                } +            } +            else { +                lw += padding; + +                if (pos == "left") { +                    axis.box = { left: plotOffset.left + axisMargin, width: lw }; +                    plotOffset.left += lw + axisMargin; +                } +                else { +                    plotOffset.right += lw + axisMargin; +                    axis.box = { left: surface.width - plotOffset.right, width: lw }; +                } +            } + +             // save for future reference +            axis.position = pos; +            axis.tickLength = tickLength; +            axis.box.padding = padding; +            axis.innermost = innermost; +        } + +        function allocateAxisBoxSecondPhase(axis) { +            // now that all axis boxes have been placed in one +            // dimension, we can set the remaining dimension coordinates +            if (axis.direction == "x") { +                axis.box.left = plotOffset.left - axis.labelWidth / 2; +                axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; +            } +            else { +                axis.box.top = plotOffset.top - axis.labelHeight / 2; +                axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; +            } +        } + +        function adjustLayoutForThingsStickingOut() { +            // possibly adjust plot offset to ensure everything stays +            // inside the canvas and isn't clipped off + +            var minMargin = options.grid.minBorderMargin, +                margins = { x: 0, y: 0 }, i, axis; + +            // check stuff from the plot (FIXME: this should just read +            // a value from the series, otherwise it's impossible to +            // customize) +            if (minMargin == null) { +                minMargin = 0; +                for (i = 0; i < series.length; ++i) +                    minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); +            } + +            margins.x = margins.y = Math.ceil(minMargin); + +            // check axis labels, note we don't check the actual +            // labels but instead use the overall width/height to not +            // jump as much around with replots +            $.each(allAxes(), function (_, axis) { +                var dir = axis.direction; +                if (axis.reserveSpace) +                    margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); +            }); + +            plotOffset.left = Math.max(margins.x, plotOffset.left); +            plotOffset.right = Math.max(margins.x, plotOffset.right); +            plotOffset.top = Math.max(margins.y, plotOffset.top); +            plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); +        } + +        function setupGrid() { +            var i, axes = allAxes(), showGrid = options.grid.show; + +            // Initialize the plot's offset from the edge of the canvas + +            for (var a in plotOffset) { +                var margin = options.grid.margin || 0; +                plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; +            } + +            executeHooks(hooks.processOffset, [plotOffset]); + +            // If the grid is visible, add its border width to the offset + +            for (var a in plotOffset) { +                if(typeof(options.grid.borderWidth) == "object") { +                    plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; +                } +                else { +                    plotOffset[a] += showGrid ? options.grid.borderWidth : 0; +                } +            } + +            // init axes +            $.each(axes, function (_, axis) { +                axis.show = axis.options.show; +                if (axis.show == null) +                    axis.show = axis.used; // by default an axis is visible if it's got data + +                axis.reserveSpace = axis.show || axis.options.reserveSpace; + +                setRange(axis); +            }); + +            if (showGrid) { + +                var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + +                $.each(allocatedAxes, function (_, axis) { +                    // make the ticks +                    setupTickGeneration(axis); +                    setTicks(axis); +                    snapRangeToTicks(axis, axis.ticks); +                    // find labelWidth/Height for axis +                    measureTickLabels(axis); +                }); + +                // with all dimensions calculated, we can compute the +                // axis bounding boxes, start from the outside +                // (reverse order) +                for (i = allocatedAxes.length - 1; i >= 0; --i) +                    allocateAxisBoxFirstPhase(allocatedAxes[i]); + +                // make sure we've got enough space for things that +                // might stick out +                adjustLayoutForThingsStickingOut(); + +                $.each(allocatedAxes, function (_, axis) { +                    allocateAxisBoxSecondPhase(axis); +                }); +            } + +            plotWidth = surface.width - plotOffset.left - plotOffset.right; +            plotHeight = surface.height - plotOffset.bottom - plotOffset.top; + +            // now we got the proper plot dimensions, we can compute the scaling +            $.each(axes, function (_, axis) { +                setTransformationHelpers(axis); +            }); + +            if (showGrid) { +                drawAxisLabels(); +            } + +            insertLegend(); +        } + +        function setRange(axis) { +            var opts = axis.options, +                min = +(opts.min != null ? opts.min : axis.datamin), +                max = +(opts.max != null ? opts.max : axis.datamax), +                delta = max - min; + +            if (delta == 0.0) { +                // degenerate case +                var widen = max == 0 ? 1 : 0.01; + +                if (opts.min == null) +                    min -= widen; +                // always widen max if we couldn't widen min to ensure we +                // don't fall into min == max which doesn't work +                if (opts.max == null || opts.min != null) +                    max += widen; +            } +            else { +                // consider autoscaling +                var margin = opts.autoscaleMargin; +                if (margin != null) { +                    if (opts.min == null) { +                        min -= delta * margin; +                        // make sure we don't go below zero if all values +                        // are positive +                        if (min < 0 && axis.datamin != null && axis.datamin >= 0) +                            min = 0; +                    } +                    if (opts.max == null) { +                        max += delta * margin; +                        if (max > 0 && axis.datamax != null && axis.datamax <= 0) +                            max = 0; +                    } +                } +            } +            axis.min = min; +            axis.max = max; +        } + +        function setupTickGeneration(axis) { +            var opts = axis.options; + +            // estimate number of ticks +            var noTicks; +            if (typeof opts.ticks == "number" && opts.ticks > 0) +                noTicks = opts.ticks; +            else +                // heuristic based on the model a*sqrt(x) fitted to +                // some data points that seemed reasonable +                noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); + +            var delta = (axis.max - axis.min) / noTicks, +                dec = -Math.floor(Math.log(delta) / Math.LN10), +                maxDec = opts.tickDecimals; + +            if (maxDec != null && dec > maxDec) { +                dec = maxDec; +            } + +            var magn = Math.pow(10, -dec), +                norm = delta / magn, // norm is between 1.0 and 10.0 +                size; + +            if (norm < 1.5) { +                size = 1; +            } else if (norm < 3) { +                size = 2; +                // special case for 2.5, requires an extra decimal +                if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { +                    size = 2.5; +                    ++dec; +                } +            } else if (norm < 7.5) { +                size = 5; +            } else { +                size = 10; +            } + +            size *= magn; + +            if (opts.minTickSize != null && size < opts.minTickSize) { +                size = opts.minTickSize; +            } + +            axis.delta = delta; +            axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); +            axis.tickSize = opts.tickSize || size; + +            // Time mode was moved to a plug-in in 0.8, but since so many people use this +            // we'll add an especially friendly make sure they remembered to include it. + +            if (opts.mode == "time" && !axis.tickGenerator) { +                throw new Error("Time mode requires the flot.time plugin."); +            } + +            // Flot supports base-10 axes; any other mode else is handled by a plug-in, +            // like flot.time.js. + +            if (!axis.tickGenerator) { + +                axis.tickGenerator = function (axis) { + +                    var ticks = [], +                        start = floorInBase(axis.min, axis.tickSize), +                        i = 0, +                        v = Number.NaN, +                        prev; + +                    do { +                        prev = v; +                        v = start + i * axis.tickSize; +                        ticks.push(v); +                        ++i; +                    } while (v < axis.max && v != prev); +                    return ticks; +                }; + +				axis.tickFormatter = function (value, axis) { + +					var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; +					var formatted = "" + Math.round(value * factor) / factor; + +					// If tickDecimals was specified, ensure that we have exactly that +					// much precision; otherwise default to the value's own precision. + +					if (axis.tickDecimals != null) { +						var decimal = formatted.indexOf("."); +						var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; +						if (precision < axis.tickDecimals) { +							return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); +						} +					} + +                    return formatted; +                }; +            } + +            if ($.isFunction(opts.tickFormatter)) +                axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + +            if (opts.alignTicksWithAxis != null) { +                var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; +                if (otherAxis && otherAxis.used && otherAxis != axis) { +                    // consider snapping min/max to outermost nice ticks +                    var niceTicks = axis.tickGenerator(axis); +                    if (niceTicks.length > 0) { +                        if (opts.min == null) +                            axis.min = Math.min(axis.min, niceTicks[0]); +                        if (opts.max == null && niceTicks.length > 1) +                            axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); +                    } + +                    axis.tickGenerator = function (axis) { +                        // copy ticks, scaled to this axis +                        var ticks = [], v, i; +                        for (i = 0; i < otherAxis.ticks.length; ++i) { +                            v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); +                            v = axis.min + v * (axis.max - axis.min); +                            ticks.push(v); +                        } +                        return ticks; +                    }; + +                    // we might need an extra decimal since forced +                    // ticks don't necessarily fit naturally +                    if (!axis.mode && opts.tickDecimals == null) { +                        var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), +                            ts = axis.tickGenerator(axis); + +                        // only proceed if the tick interval rounded +                        // with an extra decimal doesn't give us a +                        // zero at end +                        if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) +                            axis.tickDecimals = extraDec; +                    } +                } +            } +        } + +        function setTicks(axis) { +            var oticks = axis.options.ticks, ticks = []; +            if (oticks == null || (typeof oticks == "number" && oticks > 0)) +                ticks = axis.tickGenerator(axis); +            else if (oticks) { +                if ($.isFunction(oticks)) +                    // generate the ticks +                    ticks = oticks(axis); +                else +                    ticks = oticks; +            } + +            // clean up/labelify the supplied ticks, copy them over +            var i, v; +            axis.ticks = []; +            for (i = 0; i < ticks.length; ++i) { +                var label = null; +                var t = ticks[i]; +                if (typeof t == "object") { +                    v = +t[0]; +                    if (t.length > 1) +                        label = t[1]; +                } +                else +                    v = +t; +                if (label == null) +                    label = axis.tickFormatter(v, axis); +                if (!isNaN(v)) +                    axis.ticks.push({ v: v, label: label }); +            } +        } + +        function snapRangeToTicks(axis, ticks) { +            if (axis.options.autoscaleMargin && ticks.length > 0) { +                // snap to ticks +                if (axis.options.min == null) +                    axis.min = Math.min(axis.min, ticks[0].v); +                if (axis.options.max == null && ticks.length > 1) +                    axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); +            } +        } + +        function draw() { + +            surface.clear(); + +            executeHooks(hooks.drawBackground, [ctx]); + +            var grid = options.grid; + +            // draw background, if any +            if (grid.show && grid.backgroundColor) +                drawBackground(); + +            if (grid.show && !grid.aboveData) { +                drawGrid(); +            } + +            for (var i = 0; i < series.length; ++i) { +                executeHooks(hooks.drawSeries, [ctx, series[i]]); +                drawSeries(series[i]); +            } + +            executeHooks(hooks.draw, [ctx]); + +            if (grid.show && grid.aboveData) { +                drawGrid(); +            } + +            surface.render(); + +            // A draw implies that either the axes or data have changed, so we +            // should probably update the overlay highlights as well. + +            triggerRedrawOverlay(); +        } + +        function extractRange(ranges, coord) { +            var axis, from, to, key, axes = allAxes(); + +            for (var i = 0; i < axes.length; ++i) { +                axis = axes[i]; +                if (axis.direction == coord) { +                    key = coord + axis.n + "axis"; +                    if (!ranges[key] && axis.n == 1) +                        key = coord + "axis"; // support x1axis as xaxis +                    if (ranges[key]) { +                        from = ranges[key].from; +                        to = ranges[key].to; +                        break; +                    } +                } +            } + +            // backwards-compat stuff - to be removed in future +            if (!ranges[key]) { +                axis = coord == "x" ? xaxes[0] : yaxes[0]; +                from = ranges[coord + "1"]; +                to = ranges[coord + "2"]; +            } + +            // auto-reverse as an added bonus +            if (from != null && to != null && from > to) { +                var tmp = from; +                from = to; +                to = tmp; +            } + +            return { from: from, to: to, axis: axis }; +        } + +        function drawBackground() { +            ctx.save(); +            ctx.translate(plotOffset.left, plotOffset.top); + +            ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); +            ctx.fillRect(0, 0, plotWidth, plotHeight); +            ctx.restore(); +        } + +        function drawGrid() { +            var i, axes, bw, bc; + +            ctx.save(); +            ctx.translate(plotOffset.left, plotOffset.top); + +            // draw markings +            var markings = options.grid.markings; +            if (markings) { +                if ($.isFunction(markings)) { +                    axes = plot.getAxes(); +                    // xmin etc. is backwards compatibility, to be +                    // removed in the future +                    axes.xmin = axes.xaxis.min; +                    axes.xmax = axes.xaxis.max; +                    axes.ymin = axes.yaxis.min; +                    axes.ymax = axes.yaxis.max; + +                    markings = markings(axes); +                } + +                for (i = 0; i < markings.length; ++i) { +                    var m = markings[i], +                        xrange = extractRange(m, "x"), +                        yrange = extractRange(m, "y"); + +                    // fill in missing +                    if (xrange.from == null) +                        xrange.from = xrange.axis.min; +                    if (xrange.to == null) +                        xrange.to = xrange.axis.max; +                    if (yrange.from == null) +                        yrange.from = yrange.axis.min; +                    if (yrange.to == null) +                        yrange.to = yrange.axis.max; + +                    // clip +                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || +                        yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) +                        continue; + +                    xrange.from = Math.max(xrange.from, xrange.axis.min); +                    xrange.to = Math.min(xrange.to, xrange.axis.max); +                    yrange.from = Math.max(yrange.from, yrange.axis.min); +                    yrange.to = Math.min(yrange.to, yrange.axis.max); + +                    if (xrange.from == xrange.to && yrange.from == yrange.to) +                        continue; + +                    // then draw +                    xrange.from = xrange.axis.p2c(xrange.from); +                    xrange.to = xrange.axis.p2c(xrange.to); +                    yrange.from = yrange.axis.p2c(yrange.from); +                    yrange.to = yrange.axis.p2c(yrange.to); + +                    if (xrange.from == xrange.to || yrange.from == yrange.to) { +                        // draw line +                        ctx.beginPath(); +                        ctx.strokeStyle = m.color || options.grid.markingsColor; +                        ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; +                        ctx.moveTo(xrange.from, yrange.from); +                        ctx.lineTo(xrange.to, yrange.to); +                        ctx.stroke(); +                    } +                    else { +                        // fill area +                        ctx.fillStyle = m.color || options.grid.markingsColor; +                        ctx.fillRect(xrange.from, yrange.to, +                                     xrange.to - xrange.from, +                                     yrange.from - yrange.to); +                    } +                } +            } + +            // draw the ticks +            axes = allAxes(); +            bw = options.grid.borderWidth; + +            for (var j = 0; j < axes.length; ++j) { +                var axis = axes[j], box = axis.box, +                    t = axis.tickLength, x, y, xoff, yoff; +                if (!axis.show || axis.ticks.length == 0) +                    continue; + +                ctx.lineWidth = 1; + +                // find the edges +                if (axis.direction == "x") { +                    x = 0; +                    if (t == "full") +                        y = (axis.position == "top" ? 0 : plotHeight); +                    else +                        y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); +                } +                else { +                    y = 0; +                    if (t == "full") +                        x = (axis.position == "left" ? 0 : plotWidth); +                    else +                        x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); +                } + +                // draw tick bar +                if (!axis.innermost) { +                    ctx.strokeStyle = axis.options.color; +                    ctx.beginPath(); +                    xoff = yoff = 0; +                    if (axis.direction == "x") +                        xoff = plotWidth + 1; +                    else +                        yoff = plotHeight + 1; + +                    if (ctx.lineWidth == 1) { +                        if (axis.direction == "x") { +                            y = Math.floor(y) + 0.5; +                        } else { +                            x = Math.floor(x) + 0.5; +                        } +                    } + +                    ctx.moveTo(x, y); +                    ctx.lineTo(x + xoff, y + yoff); +                    ctx.stroke(); +                } + +                // draw ticks + +                ctx.strokeStyle = axis.options.tickColor; + +                ctx.beginPath(); +                for (i = 0; i < axis.ticks.length; ++i) { +                    var v = axis.ticks[i].v; + +                    xoff = yoff = 0; + +                    if (isNaN(v) || v < axis.min || v > axis.max +                        // skip those lying on the axes if we got a border +                        || (t == "full" +                            && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) +                            && (v == axis.min || v == axis.max))) +                        continue; + +                    if (axis.direction == "x") { +                        x = axis.p2c(v); +                        yoff = t == "full" ? -plotHeight : t; + +                        if (axis.position == "top") +                            yoff = -yoff; +                    } +                    else { +                        y = axis.p2c(v); +                        xoff = t == "full" ? -plotWidth : t; + +                        if (axis.position == "left") +                            xoff = -xoff; +                    } + +                    if (ctx.lineWidth == 1) { +                        if (axis.direction == "x") +                            x = Math.floor(x) + 0.5; +                        else +                            y = Math.floor(y) + 0.5; +                    } + +                    ctx.moveTo(x, y); +                    ctx.lineTo(x + xoff, y + yoff); +                } + +                ctx.stroke(); +            } + + +            // draw border +            if (bw) { +                // If either borderWidth or borderColor is an object, then draw the border +                // line by line instead of as one rectangle +                bc = options.grid.borderColor; +                if(typeof bw == "object" || typeof bc == "object") { +                    if (typeof bw !== "object") { +                        bw = {top: bw, right: bw, bottom: bw, left: bw}; +                    } +                    if (typeof bc !== "object") { +                        bc = {top: bc, right: bc, bottom: bc, left: bc}; +                    } + +                    if (bw.top > 0) { +                        ctx.strokeStyle = bc.top; +                        ctx.lineWidth = bw.top; +                        ctx.beginPath(); +                        ctx.moveTo(0 - bw.left, 0 - bw.top/2); +                        ctx.lineTo(plotWidth, 0 - bw.top/2); +                        ctx.stroke(); +                    } + +                    if (bw.right > 0) { +                        ctx.strokeStyle = bc.right; +                        ctx.lineWidth = bw.right; +                        ctx.beginPath(); +                        ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); +                        ctx.lineTo(plotWidth + bw.right / 2, plotHeight); +                        ctx.stroke(); +                    } + +                    if (bw.bottom > 0) { +                        ctx.strokeStyle = bc.bottom; +                        ctx.lineWidth = bw.bottom; +                        ctx.beginPath(); +                        ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); +                        ctx.lineTo(0, plotHeight + bw.bottom / 2); +                        ctx.stroke(); +                    } + +                    if (bw.left > 0) { +                        ctx.strokeStyle = bc.left; +                        ctx.lineWidth = bw.left; +                        ctx.beginPath(); +                        ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); +                        ctx.lineTo(0- bw.left/2, 0); +                        ctx.stroke(); +                    } +                } +                else { +                    ctx.lineWidth = bw; +                    ctx.strokeStyle = options.grid.borderColor; +                    ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); +                } +            } + +            ctx.restore(); +        } + +        function drawAxisLabels() { + +            $.each(allAxes(), function (_, axis) { +                var box = axis.box, +                    legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", +                    layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, +                    font = axis.options.font || "flot-tick-label tickLabel", +                    tick, x, y, halign, valign; + +                // Remove text before checking for axis.show and ticks.length; +                // otherwise plugins, like flot-tickrotor, that draw their own +                // tick labels will end up with both theirs and the defaults. + +                surface.removeText(layer); + +                if (!axis.show || axis.ticks.length == 0) +                    return; + +                for (var i = 0; i < axis.ticks.length; ++i) { + +                    tick = axis.ticks[i]; +                    if (!tick.label || tick.v < axis.min || tick.v > axis.max) +                        continue; + +                    if (axis.direction == "x") { +                        halign = "center"; +                        x = plotOffset.left + axis.p2c(tick.v); +                        if (axis.position == "bottom") { +                            y = box.top + box.padding; +                        } else { +                            y = box.top + box.height - box.padding; +                            valign = "bottom"; +                        } +                    } else { +                        valign = "middle"; +                        y = plotOffset.top + axis.p2c(tick.v); +                        if (axis.position == "left") { +                            x = box.left + box.width - box.padding; +                            halign = "right"; +                        } else { +                            x = box.left + box.padding; +                        } +                    } + +                    surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); +                } +            }); +        } + +        function drawSeries(series) { +            if (series.lines.show) +                drawSeriesLines(series); +            if (series.bars.show) +                drawSeriesBars(series); +            if (series.points.show) +                drawSeriesPoints(series); +        } + +        function drawSeriesLines(series) { +            function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { +                var points = datapoints.points, +                    ps = datapoints.pointsize, +                    prevx = null, prevy = null; + +                ctx.beginPath(); +                for (var i = ps; i < points.length; i += ps) { +                    var x1 = points[i - ps], y1 = points[i - ps + 1], +                        x2 = points[i], y2 = points[i + 1]; + +                    if (x1 == null || x2 == null) +                        continue; + +                    // clip with ymin +                    if (y1 <= y2 && y1 < axisy.min) { +                        if (y2 < axisy.min) +                            continue;   // line segment is outside +                        // compute new intersection point +                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y1 = axisy.min; +                    } +                    else if (y2 <= y1 && y2 < axisy.min) { +                        if (y1 < axisy.min) +                            continue; +                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y2 = axisy.min; +                    } + +                    // clip with ymax +                    if (y1 >= y2 && y1 > axisy.max) { +                        if (y2 > axisy.max) +                            continue; +                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y1 = axisy.max; +                    } +                    else if (y2 >= y1 && y2 > axisy.max) { +                        if (y1 > axisy.max) +                            continue; +                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y2 = axisy.max; +                    } + +                    // clip with xmin +                    if (x1 <= x2 && x1 < axisx.min) { +                        if (x2 < axisx.min) +                            continue; +                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x1 = axisx.min; +                    } +                    else if (x2 <= x1 && x2 < axisx.min) { +                        if (x1 < axisx.min) +                            continue; +                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x2 = axisx.min; +                    } + +                    // clip with xmax +                    if (x1 >= x2 && x1 > axisx.max) { +                        if (x2 > axisx.max) +                            continue; +                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x1 = axisx.max; +                    } +                    else if (x2 >= x1 && x2 > axisx.max) { +                        if (x1 > axisx.max) +                            continue; +                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x2 = axisx.max; +                    } + +                    if (x1 != prevx || y1 != prevy) +                        ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + +                    prevx = x2; +                    prevy = y2; +                    ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); +                } +                ctx.stroke(); +            } + +            function plotLineArea(datapoints, axisx, axisy) { +                var points = datapoints.points, +                    ps = datapoints.pointsize, +                    bottom = Math.min(Math.max(0, axisy.min), axisy.max), +                    i = 0, top, areaOpen = false, +                    ypos = 1, segmentStart = 0, segmentEnd = 0; + +                // we process each segment in two turns, first forward +                // direction to sketch out top, then once we hit the +                // end we go backwards to sketch the bottom +                while (true) { +                    if (ps > 0 && i > points.length + ps) +                        break; + +                    i += ps; // ps is negative if going backwards + +                    var x1 = points[i - ps], +                        y1 = points[i - ps + ypos], +                        x2 = points[i], y2 = points[i + ypos]; + +                    if (areaOpen) { +                        if (ps > 0 && x1 != null && x2 == null) { +                            // at turning point +                            segmentEnd = i; +                            ps = -ps; +                            ypos = 2; +                            continue; +                        } + +                        if (ps < 0 && i == segmentStart + ps) { +                            // done with the reverse sweep +                            ctx.fill(); +                            areaOpen = false; +                            ps = -ps; +                            ypos = 1; +                            i = segmentStart = segmentEnd + ps; +                            continue; +                        } +                    } + +                    if (x1 == null || x2 == null) +                        continue; + +                    // clip x values + +                    // clip with xmin +                    if (x1 <= x2 && x1 < axisx.min) { +                        if (x2 < axisx.min) +                            continue; +                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x1 = axisx.min; +                    } +                    else if (x2 <= x1 && x2 < axisx.min) { +                        if (x1 < axisx.min) +                            continue; +                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x2 = axisx.min; +                    } + +                    // clip with xmax +                    if (x1 >= x2 && x1 > axisx.max) { +                        if (x2 > axisx.max) +                            continue; +                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x1 = axisx.max; +                    } +                    else if (x2 >= x1 && x2 > axisx.max) { +                        if (x1 > axisx.max) +                            continue; +                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; +                        x2 = axisx.max; +                    } + +                    if (!areaOpen) { +                        // open area +                        ctx.beginPath(); +                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); +                        areaOpen = true; +                    } + +                    // now first check the case where both is outside +                    if (y1 >= axisy.max && y2 >= axisy.max) { +                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); +                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); +                        continue; +                    } +                    else if (y1 <= axisy.min && y2 <= axisy.min) { +                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); +                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); +                        continue; +                    } + +                    // else it's a bit more complicated, there might +                    // be a flat maxed out rectangle first, then a +                    // triangular cutout or reverse; to find these +                    // keep track of the current x values +                    var x1old = x1, x2old = x2; + +                    // clip the y values, without shortcutting, we +                    // go through all cases in turn + +                    // clip with ymin +                    if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { +                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y1 = axisy.min; +                    } +                    else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { +                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y2 = axisy.min; +                    } + +                    // clip with ymax +                    if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { +                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y1 = axisy.max; +                    } +                    else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { +                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; +                        y2 = axisy.max; +                    } + +                    // if the x value was changed we got a rectangle +                    // to fill +                    if (x1 != x1old) { +                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); +                        // it goes to (x1, y1), but we fill that below +                    } + +                    // fill triangular section, this sometimes result +                    // in redundant points if (x1, y1) hasn't changed +                    // from previous line to, but we just ignore that +                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); +                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + +                    // fill the other rectangle if it's there +                    if (x2 != x2old) { +                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); +                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); +                    } +                } +            } + +            ctx.save(); +            ctx.translate(plotOffset.left, plotOffset.top); +            ctx.lineJoin = "round"; + +            var lw = series.lines.lineWidth, +                sw = series.shadowSize; +            // FIXME: consider another form of shadow when filling is turned on +            if (lw > 0 && sw > 0) { +                // draw shadow as a thick and thin line with transparency +                ctx.lineWidth = sw; +                ctx.strokeStyle = "rgba(0,0,0,0.1)"; +                // position shadow at angle from the mid of line +                var angle = Math.PI/18; +                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); +                ctx.lineWidth = sw/2; +                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); +            } + +            ctx.lineWidth = lw; +            ctx.strokeStyle = series.color; +            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); +            if (fillStyle) { +                ctx.fillStyle = fillStyle; +                plotLineArea(series.datapoints, series.xaxis, series.yaxis); +            } + +            if (lw > 0) +                plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); +            ctx.restore(); +        } + +        function drawSeriesPoints(series) { +            function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { +                var points = datapoints.points, ps = datapoints.pointsize; + +                for (var i = 0; i < points.length; i += ps) { +                    var x = points[i], y = points[i + 1]; +                    if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) +                        continue; + +                    ctx.beginPath(); +                    x = axisx.p2c(x); +                    y = axisy.p2c(y) + offset; +                    if (symbol == "circle") +                        ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); +                    else +                        symbol(ctx, x, y, radius, shadow); +                    ctx.closePath(); + +                    if (fillStyle) { +                        ctx.fillStyle = fillStyle; +                        ctx.fill(); +                    } +                    ctx.stroke(); +                } +            } + +            ctx.save(); +            ctx.translate(plotOffset.left, plotOffset.top); + +            var lw = series.points.lineWidth, +                sw = series.shadowSize, +                radius = series.points.radius, +                symbol = series.points.symbol; + +            // If the user sets the line width to 0, we change it to a very  +            // small value. A line width of 0 seems to force the default of 1. +            // Doing the conditional here allows the shadow setting to still be  +            // optional even with a lineWidth of 0. + +            if( lw == 0 ) +                lw = 0.0001; + +            if (lw > 0 && sw > 0) { +                // draw shadow in two steps +                var w = sw / 2; +                ctx.lineWidth = w; +                ctx.strokeStyle = "rgba(0,0,0,0.1)"; +                plotPoints(series.datapoints, radius, null, w + w/2, true, +                           series.xaxis, series.yaxis, symbol); + +                ctx.strokeStyle = "rgba(0,0,0,0.2)"; +                plotPoints(series.datapoints, radius, null, w/2, true, +                           series.xaxis, series.yaxis, symbol); +            } + +            ctx.lineWidth = lw; +            ctx.strokeStyle = series.color; +            plotPoints(series.datapoints, radius, +                       getFillStyle(series.points, series.color), 0, false, +                       series.xaxis, series.yaxis, symbol); +            ctx.restore(); +        } + +        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { +            var left, right, bottom, top, +                drawLeft, drawRight, drawTop, drawBottom, +                tmp; + +            // in horizontal mode, we start the bar from the left +            // instead of from the bottom so it appears to be +            // horizontal rather than vertical +            if (horizontal) { +                drawBottom = drawRight = drawTop = true; +                drawLeft = false; +                left = b; +                right = x; +                top = y + barLeft; +                bottom = y + barRight; + +                // account for negative bars +                if (right < left) { +                    tmp = right; +                    right = left; +                    left = tmp; +                    drawLeft = true; +                    drawRight = false; +                } +            } +            else { +                drawLeft = drawRight = drawTop = true; +                drawBottom = false; +                left = x + barLeft; +                right = x + barRight; +                bottom = b; +                top = y; + +                // account for negative bars +                if (top < bottom) { +                    tmp = top; +                    top = bottom; +                    bottom = tmp; +                    drawBottom = true; +                    drawTop = false; +                } +            } + +            // clip +            if (right < axisx.min || left > axisx.max || +                top < axisy.min || bottom > axisy.max) +                return; + +            if (left < axisx.min) { +                left = axisx.min; +                drawLeft = false; +            } + +            if (right > axisx.max) { +                right = axisx.max; +                drawRight = false; +            } + +            if (bottom < axisy.min) { +                bottom = axisy.min; +                drawBottom = false; +            } + +            if (top > axisy.max) { +                top = axisy.max; +                drawTop = false; +            } + +            left = axisx.p2c(left); +            bottom = axisy.p2c(bottom); +            right = axisx.p2c(right); +            top = axisy.p2c(top); + +            // fill the bar +            if (fillStyleCallback) { +                c.fillStyle = fillStyleCallback(bottom, top); +                c.fillRect(left, top, right - left, bottom - top) +            } + +            // draw outline +            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { +                c.beginPath(); + +                // FIXME: inline moveTo is buggy with excanvas +                c.moveTo(left, bottom); +                if (drawLeft) +                    c.lineTo(left, top); +                else +                    c.moveTo(left, top); +                if (drawTop) +                    c.lineTo(right, top); +                else +                    c.moveTo(right, top); +                if (drawRight) +                    c.lineTo(right, bottom); +                else +                    c.moveTo(right, bottom); +                if (drawBottom) +                    c.lineTo(left, bottom); +                else +                    c.moveTo(left, bottom); +                c.stroke(); +            } +        } + +        function drawSeriesBars(series) { +            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { +                var points = datapoints.points, ps = datapoints.pointsize; + +                for (var i = 0; i < points.length; i += ps) { +                    if (points[i] == null) +                        continue; +                    drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); +                } +            } + +            ctx.save(); +            ctx.translate(plotOffset.left, plotOffset.top); + +            // FIXME: figure out a way to add shadows (for instance along the right edge) +            ctx.lineWidth = series.bars.lineWidth; +            ctx.strokeStyle = series.color; + +            var barLeft; + +            switch (series.bars.align) { +                case "left": +                    barLeft = 0; +                    break; +                case "right": +                    barLeft = -series.bars.barWidth; +                    break; +                default: +                    barLeft = -series.bars.barWidth / 2; +            } + +            var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; +            plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); +            ctx.restore(); +        } + +        function getFillStyle(filloptions, seriesColor, bottom, top) { +            var fill = filloptions.fill; +            if (!fill) +                return null; + +            if (filloptions.fillColor) +                return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + +            var c = $.color.parse(seriesColor); +            c.a = typeof fill == "number" ? fill : 0.4; +            c.normalize(); +            return c.toString(); +        } + +        function insertLegend() { + +            placeholder.find(".legend").remove(); + +            if (!options.legend.show) +                return; + +            var fragments = [], entries = [], rowStarted = false, +                lf = options.legend.labelFormatter, s, label; + +            // Build a list of legend entries, with each having a label and a color + +            for (var i = 0; i < series.length; ++i) { +                s = series[i]; +                if (s.label) { +                    label = lf ? lf(s.label, s) : s.label; +                    if (label) { +                        entries.push({ +                            label: label, +                            color: s.color +                        }); +                    } +                } +            } + +            // Sort the legend using either the default or a custom comparator + +            if (options.legend.sorted) { +                if ($.isFunction(options.legend.sorted)) { +                    entries.sort(options.legend.sorted); +                } else if (options.legend.sorted == "reverse") { +                	entries.reverse(); +                } else { +                    var ascending = options.legend.sorted != "descending"; +                    entries.sort(function(a, b) { +                        return a.label == b.label ? 0 : ( +                            (a.label < b.label) != ascending ? 1 : -1   // Logical XOR +                        ); +                    }); +                } +            } + +            // Generate markup for the list of entries, in their final order + +            for (var i = 0; i < entries.length; ++i) { + +                var entry = entries[i]; + +                if (i % options.legend.noColumns == 0) { +                    if (rowStarted) +                        fragments.push('</tr>'); +                    fragments.push('<tr>'); +                    rowStarted = true; +                } + +                fragments.push( +                    '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' + +                    '<td class="legendLabel">' + entry.label + '</td>' +                ); +            } + +            if (rowStarted) +                fragments.push('</tr>'); + +            if (fragments.length == 0) +                return; + +            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; +            if (options.legend.container != null) +                $(options.legend.container).html(table); +            else { +                var pos = "", +                    p = options.legend.position, +                    m = options.legend.margin; +                if (m[0] == null) +                    m = [m, m]; +                if (p.charAt(0) == "n") +                    pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; +                else if (p.charAt(0) == "s") +                    pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; +                if (p.charAt(1) == "e") +                    pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; +                else if (p.charAt(1) == "w") +                    pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; +                var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder); +                if (options.legend.backgroundOpacity != 0.0) { +                    // put in the transparent background +                    // separately to avoid blended labels and +                    // label boxes +                    var c = options.legend.backgroundColor; +                    if (c == null) { +                        c = options.grid.backgroundColor; +                        if (c && typeof c == "string") +                            c = $.color.parse(c); +                        else +                            c = $.color.extract(legend, 'background-color'); +                        c.a = 1; +                        c = c.toString(); +                    } +                    var div = legend.children(); +                    $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); +                } +            } +        } + + +        // interactive features + +        var highlights = [], +            redrawTimeout = null; + +        // returns the data item the mouse is over, or null if none is found +        function findNearbyItem(mouseX, mouseY, seriesFilter) { +            var maxDistance = options.grid.mouseActiveRadius, +                smallestDistance = maxDistance * maxDistance + 1, +                item = null, foundPoint = false, i, j, ps; + +            for (i = series.length - 1; i >= 0; --i) { +                if (!seriesFilter(series[i])) +                    continue; + +                var s = series[i], +                    axisx = s.xaxis, +                    axisy = s.yaxis, +                    points = s.datapoints.points, +                    mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster +                    my = axisy.c2p(mouseY), +                    maxx = maxDistance / axisx.scale, +                    maxy = maxDistance / axisy.scale; + +                ps = s.datapoints.pointsize; +                // with inverse transforms, we can't use the maxx/maxy +                // optimization, sadly +                if (axisx.options.inverseTransform) +                    maxx = Number.MAX_VALUE; +                if (axisy.options.inverseTransform) +                    maxy = Number.MAX_VALUE; + +                if (s.lines.show || s.points.show) { +                    for (j = 0; j < points.length; j += ps) { +                        var x = points[j], y = points[j + 1]; +                        if (x == null) +                            continue; + +                        // For points and lines, the cursor must be within a +                        // certain distance to the data point +                        if (x - mx > maxx || x - mx < -maxx || +                            y - my > maxy || y - my < -maxy) +                            continue; + +                        // We have to calculate distances in pixels, not in +                        // data units, because the scales of the axes may be different +                        var dx = Math.abs(axisx.p2c(x) - mouseX), +                            dy = Math.abs(axisy.p2c(y) - mouseY), +                            dist = dx * dx + dy * dy; // we save the sqrt + +                        // use <= to ensure last point takes precedence +                        // (last generally means on top of) +                        if (dist < smallestDistance) { +                            smallestDistance = dist; +                            item = [i, j / ps]; +                        } +                    } +                } + +                if (s.bars.show && !item) { // no other point can be nearby + +                    var barLeft, barRight; + +                    switch (s.bars.align) { +                        case "left": +                            barLeft = 0; +                            break; +                        case "right": +                            barLeft = -s.bars.barWidth; +                            break; +                        default: +                            barLeft = -s.bars.barWidth / 2; +                    } + +                    barRight = barLeft + s.bars.barWidth; + +                    for (j = 0; j < points.length; j += ps) { +                        var x = points[j], y = points[j + 1], b = points[j + 2]; +                        if (x == null) +                            continue; + +                        // for a bar graph, the cursor must be inside the bar +                        if (series[i].bars.horizontal ? +                            (mx <= Math.max(b, x) && mx >= Math.min(b, x) && +                             my >= y + barLeft && my <= y + barRight) : +                            (mx >= x + barLeft && mx <= x + barRight && +                             my >= Math.min(b, y) && my <= Math.max(b, y))) +                                item = [i, j / ps]; +                    } +                } +            } + +            if (item) { +                i = item[0]; +                j = item[1]; +                ps = series[i].datapoints.pointsize; + +                return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), +                         dataIndex: j, +                         series: series[i], +                         seriesIndex: i }; +            } + +            return null; +        } + +        function onMouseMove(e) { +            if (options.grid.hoverable) +                triggerClickHoverEvent("plothover", e, +                                       function (s) { return s["hoverable"] != false; }); +        } + +        function onMouseLeave(e) { +            if (options.grid.hoverable) +                triggerClickHoverEvent("plothover", e, +                                       function (s) { return false; }); +        } + +        function onClick(e) { +            triggerClickHoverEvent("plotclick", e, +                                   function (s) { return s["clickable"] != false; }); +        } + +        // trigger click or hover event (they send the same parameters +        // so we share their code) +        function triggerClickHoverEvent(eventname, event, seriesFilter) { +            var offset = eventHolder.offset(), +                canvasX = event.pageX - offset.left - plotOffset.left, +                canvasY = event.pageY - offset.top - plotOffset.top, +            pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + +            pos.pageX = event.pageX; +            pos.pageY = event.pageY; + +            var item = findNearbyItem(canvasX, canvasY, seriesFilter); + +            if (item) { +                // fill in mouse pos for any listeners out there +                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); +                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); +            } + +            if (options.grid.autoHighlight) { +                // clear auto-highlights +                for (var i = 0; i < highlights.length; ++i) { +                    var h = highlights[i]; +                    if (h.auto == eventname && +                        !(item && h.series == item.series && +                          h.point[0] == item.datapoint[0] && +                          h.point[1] == item.datapoint[1])) +                        unhighlight(h.series, h.point); +                } + +                if (item) +                    highlight(item.series, item.datapoint, eventname); +            } + +            placeholder.trigger(eventname, [ pos, item ]); +        } + +        function triggerRedrawOverlay() { +            var t = options.interaction.redrawOverlayInterval; +            if (t == -1) {      // skip event queue +                drawOverlay(); +                return; +            } + +            if (!redrawTimeout) +                redrawTimeout = setTimeout(drawOverlay, t); +        } + +        function drawOverlay() { +            redrawTimeout = null; + +            // draw highlights +            octx.save(); +            overlay.clear(); +            octx.translate(plotOffset.left, plotOffset.top); + +            var i, hi; +            for (i = 0; i < highlights.length; ++i) { +                hi = highlights[i]; + +                if (hi.series.bars.show) +                    drawBarHighlight(hi.series, hi.point); +                else +                    drawPointHighlight(hi.series, hi.point); +            } +            octx.restore(); + +            executeHooks(hooks.drawOverlay, [octx]); +        } + +        function highlight(s, point, auto) { +            if (typeof s == "number") +                s = series[s]; + +            if (typeof point == "number") { +                var ps = s.datapoints.pointsize; +                point = s.datapoints.points.slice(ps * point, ps * (point + 1)); +            } + +            var i = indexOfHighlight(s, point); +            if (i == -1) { +                highlights.push({ series: s, point: point, auto: auto }); + +                triggerRedrawOverlay(); +            } +            else if (!auto) +                highlights[i].auto = false; +        } + +        function unhighlight(s, point) { +            if (s == null && point == null) { +                highlights = []; +                triggerRedrawOverlay(); +                return; +            } + +            if (typeof s == "number") +                s = series[s]; + +            if (typeof point == "number") { +                var ps = s.datapoints.pointsize; +                point = s.datapoints.points.slice(ps * point, ps * (point + 1)); +            } + +            var i = indexOfHighlight(s, point); +            if (i != -1) { +                highlights.splice(i, 1); + +                triggerRedrawOverlay(); +            } +        } + +        function indexOfHighlight(s, p) { +            for (var i = 0; i < highlights.length; ++i) { +                var h = highlights[i]; +                if (h.series == s && h.point[0] == p[0] +                    && h.point[1] == p[1]) +                    return i; +            } +            return -1; +        } + +        function drawPointHighlight(series, point) { +            var x = point[0], y = point[1], +                axisx = series.xaxis, axisy = series.yaxis, +                highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); + +            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) +                return; + +            var pointRadius = series.points.radius + series.points.lineWidth / 2; +            octx.lineWidth = pointRadius; +            octx.strokeStyle = highlightColor; +            var radius = 1.5 * pointRadius; +            x = axisx.p2c(x); +            y = axisy.p2c(y); + +            octx.beginPath(); +            if (series.points.symbol == "circle") +                octx.arc(x, y, radius, 0, 2 * Math.PI, false); +            else +                series.points.symbol(octx, x, y, radius, false); +            octx.closePath(); +            octx.stroke(); +        } + +        function drawBarHighlight(series, point) { +            var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), +                fillStyle = highlightColor, +                barLeft; + +            switch (series.bars.align) { +                case "left": +                    barLeft = 0; +                    break; +                case "right": +                    barLeft = -series.bars.barWidth; +                    break; +                default: +                    barLeft = -series.bars.barWidth / 2; +            } + +            octx.lineWidth = series.bars.lineWidth; +            octx.strokeStyle = highlightColor; + +            drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, +                    function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); +        } + +        function getColorOrGradient(spec, bottom, top, defaultColor) { +            if (typeof spec == "string") +                return spec; +            else { +                // assume this is a gradient spec; IE currently only +                // supports a simple vertical gradient properly, so that's +                // what we support too +                var gradient = ctx.createLinearGradient(0, top, 0, bottom); + +                for (var i = 0, l = spec.colors.length; i < l; ++i) { +                    var c = spec.colors[i]; +                    if (typeof c != "string") { +                        var co = $.color.parse(defaultColor); +                        if (c.brightness != null) +                            co = co.scale('rgb', c.brightness); +                        if (c.opacity != null) +                            co.a *= c.opacity; +                        c = co.toString(); +                    } +                    gradient.addColorStop(i / (l - 1), c); +                } + +                return gradient; +            } +        } +    } + +    // Add the plot function to the top level of the jQuery object + +    $.plot = function(placeholder, data, options) { +        //var t0 = new Date(); +        var plot = new Plot($(placeholder), data, options, $.plot.plugins); +        //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); +        return plot; +    }; + +    $.plot.version = "0.8.2-alpha"; + +    $.plot.plugins = []; + +    // Also add the plot function as a chainable property + +    $.fn.plot = function(data, options) { +        return this.each(function() { +            $.plot(this, data, options); +        }); +    }; + +    // round to nearby lower multiple of base +    function floorInBase(n, base) { +        return base * Math.floor(n / base); +    } + +})(jQuery); diff --git a/public/javascripts/jquery.flot.min.js b/public/javascripts/jquery.flot.min.js index 3706512c4..b82faa18f 100644 --- a/public/javascripts/jquery.flot.min.js +++ b/public/javascripts/jquery.flot.min.js @@ -1,29 +1 @@ -/* Javascript plotting library for jQuery, version 0.8.1. - -Copyright (c) 2007-2013 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - *   var c = $.color.extract($("#mydiv"), 'background-color'); - *   console.log(c.r, c.g, c.b, c.a); - *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */(function(e){e.color={},e.color.make=function(t,n,r,i){var s={};return s.r=t||0,s.g=n||0,s.b=r||0,s.a=i!=null?i:1,s.add=function(e,t){for(var n=0;n<e.length;++n)s[e.charAt(n)]+=t;return s.normalize()},s.scale=function(e,t){for(var n=0;n<e.length;++n)s[e.charAt(n)]*=t;return s.normalize()},s.toString=function(){return s.a>=1?"rgb("+[s.r,s.g,s.b].join(",")+")":"rgba("+[s.r,s.g,s.b,s.a].join(",")+")"},s.normalize=function(){function e(e,t,n){return t<e?e:t>n?n:t}return s.r=e(0,parseInt(s.r),255),s.g=e(0,parseInt(s.g),255),s.b=e(0,parseInt(s.b),255),s.a=e(0,s.a,1),s},s.clone=function(){return e.color.make(s.r,s.b,s.g,s.a)},s.normalize()},e.color.extract=function(t,n){var r;do{r=t.css(n).toLowerCase();if(r!=""&&r!="transparent")break;t=t.parent()}while(!e.nodeName(t.get(0),"body"));return r=="rgba(0, 0, 0, 0)"&&(r="transparent"),e.color.parse(r)},e.color.parse=function(n){var r,i=e.color.make;if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/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(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/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(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return i(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return i(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));var s=e.trim(n).toLowerCase();return s=="transparent"?i(255,255,255,0):(r=t[s]||[0,0,0],i(r[0],r[1],r[2]))};var t={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery),function(e){function n(t,n){var r=n.children("."+t)[0];if(r==null){r=document.createElement("canvas"),r.className=t,e(r).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(n);if(!r.getContext){if(!window.G_vmlCanvasManager)throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");r=window.G_vmlCanvasManager.initElement(r)}}this.element=r;var i=this.context=r.getContext("2d"),s=window.devicePixelRatio||1,o=i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1;this.pixelRatio=s/o,this.resize(n.width(),n.height()),this.textContainer=null,this.text={},this._textCache={}}function r(t,r,s,o){function E(e,t){t=[w].concat(t);for(var n=0;n<e.length;++n)e[n].apply(this,t)}function S(){var t={Canvas:n};for(var r=0;r<o.length;++r){var i=o[r];i.init(w,t),i.options&&e.extend(!0,a,i.options)}}function x(n){e.extend(!0,a,n),n&&n.colors&&(a.colors=n.colors),a.xaxis.color==null&&(a.xaxis.color=e.color.parse(a.grid.color).scale("a",.22).toString()),a.yaxis.color==null&&(a.yaxis.color=e.color.parse(a.grid.color).scale("a",.22).toString()),a.xaxis.tickColor==null&&(a.xaxis.tickColor=a.grid.tickColor||a.xaxis.color),a.yaxis.tickColor==null&&(a.yaxis.tickColor=a.grid.tickColor||a.yaxis.color),a.grid.borderColor==null&&(a.grid.borderColor=a.grid.color),a.grid.tickColor==null&&(a.grid.tickColor=e.color.parse(a.grid.color).scale("a",.22).toString());var r,i,s,o={style:t.css("font-style"),size:Math.round(.8*(+t.css("font-size").replace("px","")||13)),variant:t.css("font-variant"),weight:t.css("font-weight"),family:t.css("font-family")};o.lineHeight=o.size*1.15,s=a.xaxes.length||1;for(r=0;r<s;++r)i=a.xaxes[r],i&&!i.tickColor&&(i.tickColor=i.color),i=e.extend(!0,{},a.xaxis,i),a.xaxes[r]=i,i.font&&(i.font=e.extend({},o,i.font),i.font.color||(i.font.color=i.color));s=a.yaxes.length||1;for(r=0;r<s;++r)i=a.yaxes[r],i&&!i.tickColor&&(i.tickColor=i.color),i=e.extend(!0,{},a.yaxis,i),a.yaxes[r]=i,i.font&&(i.font=e.extend({},o,i.font),i.font.color||(i.font.color=i.color));a.xaxis.noTicks&&a.xaxis.ticks==null&&(a.xaxis.ticks=a.xaxis.noTicks),a.yaxis.noTicks&&a.yaxis.ticks==null&&(a.yaxis.ticks=a.yaxis.noTicks),a.x2axis&&(a.xaxes[1]=e.extend(!0,{},a.xaxis,a.x2axis),a.xaxes[1].position="top"),a.y2axis&&(a.yaxes[1]=e.extend(!0,{},a.yaxis,a.y2axis),a.yaxes[1].position="right"),a.grid.coloredAreas&&(a.grid.markings=a.grid.coloredAreas),a.grid.coloredAreasColor&&(a.grid.markingsColor=a.grid.coloredAreasColor),a.lines&&e.extend(!0,a.series.lines,a.lines),a.points&&e.extend(!0,a.series.points,a.points),a.bars&&e.extend(!0,a.series.bars,a.bars),a.shadowSize!=null&&(a.series.shadowSize=a.shadowSize),a.highlightColor!=null&&(a.series.highlightColor=a.highlightColor);for(r=0;r<a.xaxes.length;++r)O(d,r+1).options=a.xaxes[r];for(r=0;r<a.yaxes.length;++r)O(v,r+1).options=a.yaxes[r];for(var u in b)a.hooks[u]&&a.hooks[u].length&&(b[u]=b[u].concat(a.hooks[u]));E(b.processOptions,[a])}function T(e){u=N(e),M(),_()}function N(t){var n=[];for(var r=0;r<t.length;++r){var i=e.extend(!0,{},a.series);t[r].data!=null?(i.data=t[r].data,delete t[r].data,e.extend(!0,i,t[r]),t[r].data=i.data):i.data=t[r],n.push(i)}return n}function C(e,t){var n=e[t+"axis"];return typeof n=="object"&&(n=n.n),typeof n!="number"&&(n=1),n}function k(){return e.grep(d.concat(v),function(e){return e})}function L(e){var t={},n,r;for(n=0;n<d.length;++n)r=d[n],r&&r.used&&(t["x"+r.n]=r.c2p(e.left));for(n=0;n<v.length;++n)r=v[n],r&&r.used&&(t["y"+r.n]=r.c2p(e.top));return t.x1!==undefined&&(t.x=t.x1),t.y1!==undefined&&(t.y=t.y1),t}function A(e){var t={},n,r,i;for(n=0;n<d.length;++n){r=d[n];if(r&&r.used){i="x"+r.n,e[i]==null&&r.n==1&&(i="x");if(e[i]!=null){t.left=r.p2c(e[i]);break}}}for(n=0;n<v.length;++n){r=v[n];if(r&&r.used){i="y"+r.n,e[i]==null&&r.n==1&&(i="y");if(e[i]!=null){t.top=r.p2c(e[i]);break}}}return t}function O(t,n){return t[n-1]||(t[n-1]={n:n,direction:t==d?"x":"y",options:e.extend(!0,{},t==d?a.xaxis:a.yaxis)}),t[n-1]}function M(){var t=u.length,n=-1,r;for(r=0;r<u.length;++r){var i=u[r].color;i!=null&&(t--,typeof i=="number"&&i>n&&(n=i))}t<=n&&(t=n+1);var s,o=[],f=a.colors,l=f.length,c=0;for(r=0;r<t;r++)s=e.color.parse(f[r%l]||"#666"),r%l==0&&r&&(c>=0?c<.5?c=-c-.2:c=0:c=-c),o[r]=s.scale("rgb",1+c);var h=0,p;for(r=0;r<u.length;++r){p=u[r],p.color==null?(p.color=o[h].toString(),++h):typeof p.color=="number"&&(p.color=o[p.color].toString());if(p.lines.show==null){var m,g=!0;for(m in p)if(p[m]&&p[m].show){g=!1;break}g&&(p.lines.show=!0)}p.lines.zero==null&&(p.lines.zero=!!p.lines.fill),p.xaxis=O(d,C(p,"x")),p.yaxis=O(v,C(p,"y"))}}function _(){function x(e,t,n){t<e.datamin&&t!=-r&&(e.datamin=t),n>e.datamax&&n!=r&&(e.datamax=n)}var t=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,r=Number.MAX_VALUE,i,s,o,a,f,l,c,h,p,d,v,m,g,y,w,S;e.each(k(),function(e,r){r.datamin=t,r.datamax=n,r.used=!1});for(i=0;i<u.length;++i)l=u[i],l.datapoints={points:[]},E(b.processRawData,[l,l.data,l.datapoints]);for(i=0;i<u.length;++i){l=u[i],w=l.data,S=l.datapoints.format;if(!S){S=[],S.push({x:!0,number:!0,required:!0}),S.push({y:!0,number:!0,required:!0});if(l.bars.show||l.lines.show&&l.lines.fill){var T=!!(l.bars.show&&l.bars.zero||l.lines.show&&l.lines.zero);S.push({y:!0,number:!0,required:!1,defaultValue:0,autoscale:T}),l.bars.horizontal&&(delete S[S.length-1].y,S[S.length-1].x=!0)}l.datapoints.format=S}if(l.datapoints.pointsize!=null)continue;l.datapoints.pointsize=S.length,h=l.datapoints.pointsize,c=l.datapoints.points;var N=l.lines.show&&l.lines.steps;l.xaxis.used=l.yaxis.used=!0;for(s=o=0;s<w.length;++s,o+=h){y=w[s];var C=y==null;if(!C)for(a=0;a<h;++a)m=y[a],g=S[a],g&&(g.number&&m!=null&&(m=+m,isNaN(m)?m=null:m==Infinity?m=r:m==-Infinity&&(m=-r)),m==null&&(g.required&&(C=!0),g.defaultValue!=null&&(m=g.defaultValue))),c[o+a]=m;if(C)for(a=0;a<h;++a)m=c[o+a],m!=null&&(g=S[a],g.autoscale&&(g.x&&x(l.xaxis,m,m),g.y&&x(l.yaxis,m,m))),c[o+a]=null;else if(N&&o>0&&c[o-h]!=null&&c[o-h]!=c[o]&&c[o-h+1]!=c[o+1]){for(a=0;a<h;++a)c[o+h+a]=c[o+a];c[o+1]=c[o-h+1],o+=h}}}for(i=0;i<u.length;++i)l=u[i],E(b.processDatapoints,[l,l.datapoints]);for(i=0;i<u.length;++i){l=u[i],c=l.datapoints.points,h=l.datapoints.pointsize,S=l.datapoints.format;var L=t,A=t,O=n,M=n;for(s=0;s<c.length;s+=h){if(c[s]==null)continue;for(a=0;a<h;++a){m=c[s+a],g=S[a];if(!g||g.autoscale===!1||m==r||m==-r)continue;g.x&&(m<L&&(L=m),m>O&&(O=m)),g.y&&(m<A&&(A=m),m>M&&(M=m))}}if(l.bars.show){var _;switch(l.bars.align){case"left":_=0;break;case"right":_=-l.bars.barWidth;break;case"center":_=-l.bars.barWidth/2;break;default:throw new Error("Invalid bar alignment: "+l.bars.align)}l.bars.horizontal?(A+=_,M+=_+l.bars.barWidth):(L+=_,O+=_+l.bars.barWidth)}x(l.xaxis,L,O),x(l.yaxis,A,M)}e.each(k(),function(e,r){r.datamin==t&&(r.datamin=null),r.datamax==n&&(r.datamax=null)})}function D(){t.css("padding",0).children(":not(.flot-base,.flot-overlay)").remove(),t.css("position")=="static"&&t.css("position","relative"),f=new n("flot-base",t),l=new n("flot-overlay",t),h=f.context,p=l.context,c=e(l.element).unbind();var r=t.data("plot");r&&(r.shutdown(),l.clear()),t.data("plot",w)}function P(){a.grid.hoverable&&(c.mousemove(at),c.bind("mouseleave",ft)),a.grid.clickable&&c.click(lt),E(b.bindEvents,[c])}function H(){ot&&clearTimeout(ot),c.unbind("mousemove",at),c.unbind("mouseleave",ft),c.unbind("click",lt),E(b.shutdown,[c])}function B(e){function t(e){return e}var n,r,i=e.options.transform||t,s=e.options.inverseTransform;e.direction=="x"?(n=e.scale=g/Math.abs(i(e.max)-i(e.min)),r=Math.min(i(e.max),i(e.min))):(n=e.scale=y/Math.abs(i(e.max)-i(e.min)),n=-n,r=Math.max(i(e.max),i(e.min))),i==t?e.p2c=function(e){return(e-r)*n}:e.p2c=function(e){return(i(e)-r)*n},s?e.c2p=function(e){return s(r+e/n)}:e.c2p=function(e){return r+e/n}}function j(e){var t=e.options,n=e.ticks||[],r=t.labelWidth||0,i=t.labelHeight||0,s=r||e.direction=="x"?Math.floor(f.width/(n.length||1)):null;legacyStyles=e.direction+"Axis "+e.direction+e.n+"Axis",layer="flot-"+e.direction+"-axis flot-"+e.direction+e.n+"-axis "+legacyStyles,font=t.font||"flot-tick-label tickLabel";for(var o=0;o<n.length;++o){var u=n[o];if(!u.label)continue;var a=f.getTextInfo(layer,u.label,font,null,s);r=Math.max(r,a.width),i=Math.max(i,a.height)}e.labelWidth=t.labelWidth||r,e.labelHeight=t.labelHeight||i}function F(t){var n=t.labelWidth,r=t.labelHeight,i=t.options.position,s=t.options.tickLength,o=a.grid.axisMargin,u=a.grid.labelMargin,l=t.direction=="x"?d:v,c,h,p=e.grep(l,function(e){return e&&e.options.position==i&&e.reserveSpace});e.inArray(t,p)==p.length-1&&(o=0);if(s==null){var g=e.grep(l,function(e){return e&&e.reserveSpace});h=e.inArray(t,g)==0,h?s="full":s=5}isNaN(+s)||(u+=+s),t.direction=="x"?(r+=u,i=="bottom"?(m.bottom+=r+o,t.box={top:f.height-m.bottom,height:r}):(t.box={top:m.top+o,height:r},m.top+=r+o)):(n+=u,i=="left"?(t.box={left:m.left+o,width:n},m.left+=n+o):(m.right+=n+o,t.box={left:f.width-m.right,width:n})),t.position=i,t.tickLength=s,t.box.padding=u,t.innermost=h}function I(e){e.direction=="x"?(e.box.left=m.left-e.labelWidth/2,e.box.width=f.width-m.left-m.right+e.labelWidth):(e.box.top=m.top-e.labelHeight/2,e.box.height=f.height-m.bottom-m.top+e.labelHeight)}function q(){var t=a.grid.minBorderMargin,n={x:0,y:0},r,i;if(t==null){t=0;for(r=0;r<u.length;++r)t=Math.max(t,2*(u[r].points.radius+u[r].points.lineWidth/2))}n.x=n.y=Math.ceil(t),e.each(k(),function(e,t){var r=t.direction;t.reserveSpace&&(n[r]=Math.ceil(Math.max(n[r],(r=="x"?t.labelWidth:t.labelHeight)/2)))}),m.left=Math.max(n.x,m.left),m.right=Math.max(n.x,m.right),m.top=Math.max(n.y,m.top),m.bottom=Math.max(n.y,m.bottom)}function R(){var t,n=k(),r=a.grid.show;for(var i in m){var s=a.grid.margin||0;m[i]=typeof s=="number"?s:s[i]||0}E(b.processOffset,[m]);for(var i in m)typeof a.grid.borderWidth=="object"?m[i]+=r?a.grid.borderWidth[i]:0:m[i]+=r?a.grid.borderWidth:0;e.each(n,function(e,t){t.show=t.options.show,t.show==null&&(t.show=t.used),t.reserveSpace=t.show||t.options.reserveSpace,U(t)});if(r){var o=e.grep(n,function(e){return e.reserveSpace});e.each(o,function(e,t){z(t),W(t),X(t,t.ticks),j(t)});for(t=o.length-1;t>=0;--t)F(o[t]);q(),e.each(o,function(e,t){I(t)})}g=f.width-m.left-m.right,y=f.height-m.bottom-m.top,e.each(n,function(e,t){B(t)}),r&&G(),it()}function U(e){var t=e.options,n=+(t.min!=null?t.min:e.datamin),r=+(t.max!=null?t.max:e.datamax),i=r-n;if(i==0){var s=r==0?1:.01;t.min==null&&(n-=s);if(t.max==null||t.min!=null)r+=s}else{var o=t.autoscaleMargin;o!=null&&(t.min==null&&(n-=i*o,n<0&&e.datamin!=null&&e.datamin>=0&&(n=0)),t.max==null&&(r+=i*o,r>0&&e.datamax!=null&&e.datamax<=0&&(r=0)))}e.min=n,e.max=r}function z(t){var n=t.options,r;typeof n.ticks=="number"&&n.ticks>0?r=n.ticks:r=.3*Math.sqrt(t.direction=="x"?f.width:f.height);var s=(t.max-t.min)/r,o=-Math.floor(Math.log(s)/Math.LN10),u=n.tickDecimals;u!=null&&o>u&&(o=u);var a=Math.pow(10,-o),l=s/a,c;l<1.5?c=1:l<3?(c=2,l>2.25&&(u==null||o+1<=u)&&(c=2.5,++o)):l<7.5?c=5:c=10,c*=a,n.minTickSize!=null&&c<n.minTickSize&&(c=n.minTickSize),t.delta=s,t.tickDecimals=Math.max(0,u!=null?u:o),t.tickSize=n.tickSize||c;if(n.mode=="time"&&!t.tickGenerator)throw new Error("Time mode requires the flot.time plugin.");t.tickGenerator||(t.tickGenerator=function(e){var t=[],n=i(e.min,e.tickSize),r=0,s=Number.NaN,o;do o=s,s=n+r*e.tickSize,t.push(s),++r;while(s<e.max&&s!=o);return t},t.tickFormatter=function(e,t){var n=t.tickDecimals?Math.pow(10,t.tickDecimals):1,r=""+Math.round(e*n)/n;if(t.tickDecimals!=null){var i=r.indexOf("."),s=i==-1?0:r.length-i-1;if(s<t.tickDecimals)return(s?r:r+".")+(""+n).substr(1,t.tickDecimals-s)}return r}),e.isFunction(n.tickFormatter)&&(t.tickFormatter=function(e,t){return""+n.tickFormatter(e,t)});if(n.alignTicksWithAxis!=null){var h=(t.direction=="x"?d:v)[n.alignTicksWithAxis-1];if(h&&h.used&&h!=t){var p=t.tickGenerator(t);p.length>0&&(n.min==null&&(t.min=Math.min(t.min,p[0])),n.max==null&&p.length>1&&(t.max=Math.max(t.max,p[p.length-1]))),t.tickGenerator=function(e){var t=[],n,r;for(r=0;r<h.ticks.length;++r)n=(h.ticks[r].v-h.min)/(h.max-h.min),n=e.min+n*(e.max-e.min),t.push(n);return t};if(!t.mode&&n.tickDecimals==null){var m=Math.max(0,-Math.floor(Math.log(t.delta)/Math.LN10)+1),g=t.tickGenerator(t);g.length>1&&/\..*0$/.test((g[1]-g[0]).toFixed(m))||(t.tickDecimals=m)}}}}function W(t){var n=t.options.ticks,r=[];n==null||typeof n=="number"&&n>0?r=t.tickGenerator(t):n&&(e.isFunction(n)?r=n(t):r=n);var i,s;t.ticks=[];for(i=0;i<r.length;++i){var o=null,u=r[i];typeof u=="object"?(s=+u[0],u.length>1&&(o=u[1])):s=+u,o==null&&(o=t.tickFormatter(s,t)),isNaN(s)||t.ticks.push({v:s,label:o})}}function X(e,t){e.options.autoscaleMargin&&t.length>0&&(e.options.min==null&&(e.min=Math.min(e.min,t[0].v)),e.options.max==null&&t.length>1&&(e.max=Math.max(e.max,t[t.length-1].v)))}function V(){f.clear(),E(b.drawBackground,[h]);var e=a.grid;e.show&&e.backgroundColor&&K(),e.show&&!e.aboveData&&Q();for(var t=0;t<u.length;++t)E(b.drawSeries,[h,u[t]]),Y(u[t]);E(b.draw,[h]),e.show&&e.aboveData&&Q(),f.render(),ht()}function J(e,t){var n,r,i,s,o=k();for(var u=0;u<o.length;++u){n=o[u];if(n.direction==t){s=t+n.n+"axis",!e[s]&&n.n==1&&(s=t+"axis");if(e[s]){r=e[s].from,i=e[s].to;break}}}e[s]||(n=t=="x"?d[0]:v[0],r=e[t+"1"],i=e[t+"2"]);if(r!=null&&i!=null&&r>i){var a=r;r=i,i=a}return{from:r,to:i,axis:n}}function K(){h.save(),h.translate(m.left,m.top),h.fillStyle=bt(a.grid.backgroundColor,y,0,"rgba(255, 255, 255, 0)"),h.fillRect(0,0,g,y),h.restore()}function Q(){var t,n,r,i;h.save(),h.translate(m.left,m.top);var s=a.grid.markings;if(s){e.isFunction(s)&&(n=w.getAxes(),n.xmin=n.xaxis.min,n.xmax=n.xaxis.max,n.ymin=n.yaxis.min,n.ymax=n.yaxis.max,s=s(n));for(t=0;t<s.length;++t){var o=s[t],u=J(o,"x"),f=J(o,"y");u.from==null&&(u.from=u.axis.min),u.to==null&&(u.to=u.axis.max),f.from==null&&(f.from=f.axis.min),f.to==null&&(f.to=f.axis.max);if(u.to<u.axis.min||u.from>u.axis.max||f.to<f.axis.min||f.from>f.axis.max)continue;u.from=Math.max(u.from,u.axis.min),u.to=Math.min(u.to,u.axis.max),f.from=Math.max(f.from,f.axis.min),f.to=Math.min(f.to,f.axis.max);if(u.from==u.to&&f.from==f.to)continue;u.from=u.axis.p2c(u.from),u.to=u.axis.p2c(u.to),f.from=f.axis.p2c(f.from),f.to=f.axis.p2c(f.to),u.from==u.to||f.from==f.to?(h.beginPath(),h.strokeStyle=o.color||a.grid.markingsColor,h.lineWidth=o.lineWidth||a.grid.markingsLineWidth,h.moveTo(u.from,f.from),h.lineTo(u.to,f.to),h.stroke()):(h.fillStyle=o.color||a.grid.markingsColor,h.fillRect(u.from,f.to,u.to-u.from,f.from-f.to))}}n=k(),r=a.grid.borderWidth;for(var l=0;l<n.length;++l){var c=n[l],p=c.box,d=c.tickLength,v,b,E,S;if(!c.show||c.ticks.length==0)continue;h.lineWidth=1,c.direction=="x"?(v=0,d=="full"?b=c.position=="top"?0:y:b=p.top-m.top+(c.position=="top"?p.height:0)):(b=0,d=="full"?v=c.position=="left"?0:g:v=p.left-m.left+(c.position=="left"?p.width:0)),c.innermost||(h.strokeStyle=c.options.color,h.beginPath(),E=S=0,c.direction=="x"?E=g+1:S=y+1,h.lineWidth==1&&(c.direction=="x"?b=Math.floor(b)+.5:v=Math.floor(v)+.5),h.moveTo(v,b),h.lineTo(v+E,b+S),h.stroke()),h.strokeStyle=c.options.tickColor,h.beginPath();for(t=0;t<c.ticks.length;++t){var x=c.ticks[t].v;E=S=0;if(isNaN(x)||x<c.min||x>c.max||d=="full"&&(typeof r=="object"&&r[c.position]>0||r>0)&&(x==c.min||x==c.max))continue;c.direction=="x"?(v=c.p2c(x),S=d=="full"?-y:d,c.position=="top"&&(S=-S)):(b=c.p2c(x),E=d=="full"?-g:d,c.position=="left"&&(E=-E)),h.lineWidth==1&&(c.direction=="x"?v=Math.floor(v)+.5:b=Math.floor(b)+.5),h.moveTo(v,b),h.lineTo(v+E,b+S)}h.stroke()}r&&(i=a.grid.borderColor,typeof r=="object"||typeof i=="object"?(typeof r!="object"&&(r={top:r,right:r,bottom:r,left:r}),typeof i!="object"&&(i={top:i,right:i,bottom:i,left:i}),r.top>0&&(h.strokeStyle=i.top,h.lineWidth=r.top,h.beginPath(),h.moveTo(0-r.left,0-r.top/2),h.lineTo(g,0-r.top/2),h.stroke()),r.right>0&&(h.strokeStyle=i.right,h.lineWidth=r.right,h.beginPath(),h.moveTo(g+r.right/2,0-r.top),h.lineTo(g+r.right/2,y),h.stroke()),r.bottom>0&&(h.strokeStyle=i.bottom,h.lineWidth=r.bottom,h.beginPath(),h.moveTo(g+r.right,y+r.bottom/2),h.lineTo(0,y+r.bottom/2),h.stroke()),r.left>0&&(h.strokeStyle=i.left,h.lineWidth=r.left,h.beginPath(),h.moveTo(0-r.left/2,y+r.bottom),h.lineTo(0-r.left/2,0),h.stroke())):(h.lineWidth=r,h.strokeStyle=a.grid.borderColor,h.strokeRect(-r/2,-r/2,g+r,y+r))),h.restore()}function G(){e.each(k(),function(e,t){if(!t.show||t.ticks.length==0)return;var n=t.box,r=t.direction+"Axis "+t.direction+t.n+"Axis",i="flot-"+t.direction+"-axis flot-"+t.direction+t.n+"-axis "+r,s=t.options.font||"flot-tick-label tickLabel",o,u,a,l,c;f.removeText(i);for(var h=0;h<t.ticks.length;++h){o=t.ticks[h];if(!o.label||o.v<t.min||o.v>t.max)continue;t.direction=="x"?(l="center",u=m.left+t.p2c(o.v),t.position=="bottom"?a=n.top+n.padding:(a=n.top+n.height-n.padding,c="bottom")):(c="middle",a=m.top+t.p2c(o.v),t.position=="left"?(u=n.left+n.width-n.padding,l="right"):u=n.left+n.padding),f.addText(i,u,a,o.label,s,null,null,l,c)}})}function Y(e){e.lines.show&&Z(e),e.bars.show&&nt(e),e.points.show&&et(e)}function Z(e){function t(e,t,n,r,i){var s=e.points,o=e.pointsize,u=null,a=null;h.beginPath();for(var f=o;f<s.length;f+=o){var l=s[f-o],c=s[f-o+1],p=s[f],d=s[f+1];if(l==null||p==null)continue;if(c<=d&&c<i.min){if(d<i.min)continue;l=(i.min-c)/(d-c)*(p-l)+l,c=i.min}else if(d<=c&&d<i.min){if(c<i.min)continue;p=(i.min-c)/(d-c)*(p-l)+l,d=i.min}if(c>=d&&c>i.max){if(d>i.max)continue;l=(i.max-c)/(d-c)*(p-l)+l,c=i.max}else if(d>=c&&d>i.max){if(c>i.max)continue;p=(i.max-c)/(d-c)*(p-l)+l,d=i.max}if(l<=p&&l<r.min){if(p<r.min)continue;c=(r.min-l)/(p-l)*(d-c)+c,l=r.min}else if(p<=l&&p<r.min){if(l<r.min)continue;d=(r.min-l)/(p-l)*(d-c)+c,p=r.min}if(l>=p&&l>r.max){if(p>r.max)continue;c=(r.max-l)/(p-l)*(d-c)+c,l=r.max}else if(p>=l&&p>r.max){if(l>r.max)continue;d=(r.max-l)/(p-l)*(d-c)+c,p=r.max}(l!=u||c!=a)&&h.moveTo(r.p2c(l)+t,i.p2c(c)+n),u=p,a=d,h.lineTo(r.p2c(p)+t,i.p2c(d)+n)}h.stroke()}function n(e,t,n){var r=e.points,i=e.pointsize,s=Math.min(Math.max(0,n.min),n.max),o=0,u,a=!1,f=1,l=0,c=0;for(;;){if(i>0&&o>r.length+i)break;o+=i;var p=r[o-i],d=r[o-i+f],v=r[o],m=r[o+f];if(a){if(i>0&&p!=null&&v==null){c=o,i=-i,f=2;continue}if(i<0&&o==l+i){h.fill(),a=!1,i=-i,f=1,o=l=c+i;continue}}if(p==null||v==null)continue;if(p<=v&&p<t.min){if(v<t.min)continue;d=(t.min-p)/(v-p)*(m-d)+d,p=t.min}else if(v<=p&&v<t.min){if(p<t.min)continue;m=(t.min-p)/(v-p)*(m-d)+d,v=t.min}if(p>=v&&p>t.max){if(v>t.max)continue;d=(t.max-p)/(v-p)*(m-d)+d,p=t.max}else if(v>=p&&v>t.max){if(p>t.max)continue;m=(t.max-p)/(v-p)*(m-d)+d,v=t.max}a||(h.beginPath(),h.moveTo(t.p2c(p),n.p2c(s)),a=!0);if(d>=n.max&&m>=n.max){h.lineTo(t.p2c(p),n.p2c(n.max)),h.lineTo(t.p2c(v),n.p2c(n.max));continue}if(d<=n.min&&m<=n.min){h.lineTo(t.p2c(p),n.p2c(n.min)),h.lineTo(t.p2c(v),n.p2c(n.min));continue}var g=p,y=v;d<=m&&d<n.min&&m>=n.min?(p=(n.min-d)/(m-d)*(v-p)+p,d=n.min):m<=d&&m<n.min&&d>=n.min&&(v=(n.min-d)/(m-d)*(v-p)+p,m=n.min),d>=m&&d>n.max&&m<=n.max?(p=(n.max-d)/(m-d)*(v-p)+p,d=n.max):m>=d&&m>n.max&&d<=n.max&&(v=(n.max-d)/(m-d)*(v-p)+p,m=n.max),p!=g&&h.lineTo(t.p2c(g),n.p2c(d)),h.lineTo(t.p2c(p),n.p2c(d)),h.lineTo(t.p2c(v),n.p2c(m)),v!=y&&(h.lineTo(t.p2c(v),n.p2c(m)),h.lineTo(t.p2c(y),n.p2c(m)))}}h.save(),h.translate(m.left,m.top),h.lineJoin="round";var r=e.lines.lineWidth,i=e.shadowSize;if(r>0&&i>0){h.lineWidth=i,h.strokeStyle="rgba(0,0,0,0.1)";var s=Math.PI/18;t(e.datapoints,Math.sin(s)*(r/2+i/2),Math.cos(s)*(r/2+i/2),e.xaxis,e.yaxis),h.lineWidth=i/2,t(e.datapoints,Math.sin(s)*(r/2+i/4),Math.cos(s)*(r/2+i/4),e.xaxis,e.yaxis)}h.lineWidth=r,h.strokeStyle=e.color;var o=rt(e.lines,e.color,0,y);o&&(h.fillStyle=o,n(e.datapoints,e.xaxis,e.yaxis)),r>0&&t(e.datapoints,0,0,e.xaxis,e.yaxis),h.restore()}function et(e){function t(e,t,n,r,i,s,o,u){var a=e.points,f=e.pointsize;for(var l=0;l<a.length;l+=f){var c=a[l],p=a[l+1];if(c==null||c<s.min||c>s.max||p<o.min||p>o.max)continue;h.beginPath(),c=s.p2c(c),p=o.p2c(p)+r,u=="circle"?h.arc(c,p,t,0,i?Math.PI:Math.PI*2,!1):u(h,c,p,t,i),h.closePath(),n&&(h.fillStyle=n,h.fill()),h.stroke()}}h.save(),h.translate(m.left,m.top);var n=e.points.lineWidth,r=e.shadowSize,i=e.points.radius,s=e.points.symbol;n==0&&(n=1e-4);if(n>0&&r>0){var o=r/2;h.lineWidth=o,h.strokeStyle="rgba(0,0,0,0.1)",t(e.datapoints,i,null,o+o/2,!0,e.xaxis,e.yaxis,s),h.strokeStyle="rgba(0,0,0,0.2)",t(e.datapoints,i,null,o/2,!0,e.xaxis,e.yaxis,s)}h.lineWidth=n,h.strokeStyle=e.color,t(e.datapoints,i,rt(e.points,e.color),0,!1,e.xaxis,e.yaxis,s),h.restore()}function tt(e,t,n,r,i,s,o,u,a,f,l,c){var h,p,d,v,m,g,y,b,w;l?(b=g=y=!0,m=!1,h=n,p=e,v=t+r,d=t+i,p<h&&(w=p,p=h,h=w,m=!0,g=!1)):(m=g=y=!0,b=!1,h=e+r,p=e+i,d=n,v=t,v<d&&(w=v,v=d,d=w,b=!0,y=!1));if(p<u.min||h>u.max||v<a.min||d>a.max)return;h<u.min&&(h=u.min,m=!1),p>u.max&&(p=u.max,g=!1),d<a.min&&(d=a.min,b=!1),v>a.max&&(v=a.max,y=!1),h=u.p2c(h),d=a.p2c(d),p=u.p2c(p),v=a.p2c(v),o&&(f.beginPath(),f.moveTo(h,d),f.lineTo(h,v),f.lineTo(p,v),f.lineTo(p,d),f.fillStyle=o(d,v),f.fill()),c>0&&(m||g||y||b)&&(f.beginPath(),f.moveTo(h,d+s),m?f.lineTo(h,v+s):f.moveTo(h,v+s),y?f.lineTo(p,v+s):f.moveTo(p,v+s),g?f.lineTo(p,d+s):f.moveTo(p,d+s),b?f.lineTo(h,d+s):f.moveTo(h,d+s),f.stroke())}function nt(e){function t(t,n,r,i,s,o,u){var a=t.points,f=t.pointsize;for(var l=0;l<a.length;l+=f){if(a[l]==null)continue;tt(a[l],a[l+1],a[l+2],n,r,i,s,o,u,h,e.bars.horizontal,e.bars.lineWidth)}}h.save(),h.translate(m.left,m.top),h.lineWidth=e.bars.lineWidth,h.strokeStyle=e.color;var n;switch(e.bars.align){case"left":n=0;break;case"right":n=-e.bars.barWidth;break;case"center":n=-e.bars.barWidth/2;break;default:throw new Error("Invalid bar alignment: "+e.bars.align)}var r=e.bars.fill?function(t,n){return rt(e.bars,e.color,t,n)}:null;t(e.datapoints,n,n+e.bars.barWidth,0,r,e.xaxis,e.yaxis),h.restore()}function rt(t,n,r,i){var s=t.fill;if(!s)return null;if(t.fillColor)return bt(t.fillColor,r,i,n);var o=e.color.parse(n);return o.a=typeof s=="number"?s:.4,o.normalize(),o.toString()}function it(){t.find(".legend").remove();if(!a.legend.show)return;var n=[],r=[],i=!1,s=a.legend.labelFormatter,o,f;for(var l=0;l<u.length;++l)o=u[l],o.label&&(f=s?s(o.label,o):o.label,f&&r.push({label:f,color:o.color}));if(a.legend.sorted)if(e.isFunction(a.legend.sorted))r.sort(a.legend.sorted);else if(a.legend.sorted=="reverse")r.reverse();else{var c=a.legend.sorted!="descending";r.sort(function(e,t){return e.label==t.label?0:e.label<t.label!=c?1:-1})}for(var l=0;l<r.length;++l){var h=r[l];l%a.legend.noColumns==0&&(i&&n.push("</tr>"),n.push("<tr>"),i=!0),n.push('<td class="legendColorBox"><div style="border:1px solid '+a.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+h.color+';overflow:hidden"></div></div></td>'+'<td class="legendLabel">'+h.label+"</td>")}i&&n.push("</tr>");if(n.length==0)return;var p='<table style="font-size:smaller;color:'+a.grid.color+'">'+n.join("")+"</table>";if(a.legend.container!=null)e(a.legend.container).html(p);else{var d="",v=a.legend.position,g=a.legend.margin;g[0]==null&&(g=[g,g]),v.charAt(0)=="n"?d+="top:"+(g[1]+m.top)+"px;":v.charAt(0)=="s"&&(d+="bottom:"+(g[1]+m.bottom)+"px;"),v.charAt(1)=="e"?d+="right:"+(g[0]+m.right)+"px;":v.charAt(1)=="w"&&(d+="left:"+(g[0]+m.left)+"px;");var y=e('<div class="legend">'+p.replace('style="','style="position:absolute;'+d+";")+"</div>").appendTo(t);if(a.legend.backgroundOpacity!=0){var b=a.legend.backgroundColor;b==null&&(b=a.grid.backgroundColor,b&&typeof b=="string"?b=e.color.parse(b):b=e.color.extract(y,"background-color"),b.a=1,b=b.toString());var w=y.children();e('<div style="position:absolute;width:'+w.width()+"px;height:"+w.height()+"px;"+d+"background-color:"+b+';"> </div>').prependTo(y).css("opacity",a.legend.backgroundOpacity)}}}function ut(e,t,n){var r=a.grid.mouseActiveRadius,i=r*r+1,s=null,o=!1,f,l,c;for(f=u.length-1;f>=0;--f){if(!n(u[f]))continue;var h=u[f],p=h.xaxis,d=h.yaxis,v=h.datapoints.points,m=p.c2p(e),g=d.c2p(t),y=r/p.scale,b=r/d.scale;c=h.datapoints.pointsize,p.options.inverseTransform&&(y=Number.MAX_VALUE),d.options.inverseTransform&&(b=Number.MAX_VALUE);if(h.lines.show||h.points.show)for(l=0;l<v.length;l+=c){var w=v[l],E=v[l+1];if(w==null)continue;if(w-m>y||w-m<-y||E-g>b||E-g<-b)continue;var S=Math.abs(p.p2c(w)-e),x=Math.abs(d.p2c(E)-t),T=S*S+x*x;T<i&&(i=T,s=[f,l/c])}if(h.bars.show&&!s){var N=h.bars.align=="left"?0:-h.bars.barWidth/2,C=N+h.bars.barWidth;for(l=0;l<v.length;l+=c){var w=v[l],E=v[l+1],k=v[l+2];if(w==null)continue;if(u[f].bars.horizontal?m<=Math.max(k,w)&&m>=Math.min(k,w)&&g>=E+N&&g<=E+C:m>=w+N&&m<=w+C&&g>=Math.min(k,E)&&g<=Math.max(k,E))s=[f,l/c]}}}return s?(f=s[0],l=s[1],c=u[f].datapoints.pointsize,{datapoint:u[f].datapoints.points.slice(l*c,(l+1)*c),dataIndex:l,series:u[f],seriesIndex:f}):null}function at(e){a.grid.hoverable&&ct("plothover",e,function(e){return e["hoverable"]!=0})}function ft(e){a.grid.hoverable&&ct("plothover",e,function(e){return!1})}function lt(e){ct("plotclick",e,function(e){return e["clickable"]!=0})}function ct(e,n,r){var i=c.offset(),s=n.pageX-i.left-m.left,o=n.pageY-i.top-m.top,u=L({left:s,top:o});u.pageX=n.pageX,u.pageY=n.pageY;var f=ut(s,o,r);f&&(f.pageX=parseInt(f.series.xaxis.p2c(f.datapoint[0])+i.left+m.left,10),f.pageY=parseInt(f.series.yaxis.p2c(f.datapoint[1])+i.top+m.top,10));if(a.grid.autoHighlight){for(var l=0;l<st.length;++l){var h=st[l];h.auto==e&&(!f||h.series!=f.series||h.point[0]!=f.datapoint[0]||h.point[1]!=f.datapoint[1])&&vt(h.series,h.point)}f&&dt(f.series,f.datapoint,e)}t.trigger(e,[u,f])}function ht(){var e=a.interaction.redrawOverlayInterval;if(e==-1){pt();return}ot||(ot=setTimeout(pt,e))}function pt(){ot=null,p.save(),l.clear(),p.translate(m.left,m.top);var e,t;for(e=0;e<st.length;++e)t=st[e],t.series.bars.show?yt(t.series,t.point):gt(t.series,t.point);p.restore(),E(b.drawOverlay,[p])}function dt(e,t,n){typeof e=="number"&&(e=u[e]);if(typeof t=="number"){var r=e.datapoints.pointsize;t=e.datapoints.points.slice(r*t,r*(t+1))}var i=mt(e,t);i==-1?(st.push({series:e,point:t,auto:n}),ht()):n||(st[i].auto=!1)}function vt(e,t){if(e==null&&t==null){st=[],ht();return}typeof e=="number"&&(e=u[e]);if(typeof t=="number"){var n=e.datapoints.pointsize;t=e.datapoints.points.slice(n*t,n*(t+1))}var r=mt(e,t);r!=-1&&(st.splice(r,1),ht())}function mt(e,t){for(var n=0;n<st.length;++n){var r=st[n];if(r.series==e&&r.point[0]==t[0]&&r.point[1]==t[1])return n}return-1}function gt(t,n){var r=n[0],i=n[1],s=t.xaxis,o=t.yaxis,u=typeof t.highlightColor=="string"?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString();if(r<s.min||r>s.max||i<o.min||i>o.max)return;var a=t.points.radius+t.points.lineWidth/2;p.lineWidth=a,p.strokeStyle=u;var f=1.5*a;r=s.p2c(r),i=o.p2c(i),p.beginPath(),t.points.symbol=="circle"?p.arc(r,i,f,0,2*Math.PI,!1):t.points.symbol(p,r,i,f,!1),p.closePath(),p.stroke()}function yt(t,n){var r=typeof t.highlightColor=="string"?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString(),i=r,s=t.bars.align=="left"?0:-t.bars.barWidth/2;p.lineWidth=t.bars.lineWidth,p.strokeStyle=r,tt(n[0],n[1],n[2]||0,s,s+t.bars.barWidth,0,function(){return i},t.xaxis,t.yaxis,p,t.bars.horizontal,t.bars.lineWidth)}function bt(t,n,r,i){if(typeof t=="string")return t;var s=h.createLinearGradient(0,r,0,n);for(var o=0,u=t.colors.length;o<u;++o){var a=t.colors[o];if(typeof a!="string"){var f=e.color.parse(i);a.brightness!=null&&(f=f.scale("rgb",a.brightness)),a.opacity!=null&&(f.a*=a.opacity),a=f.toString()}s.addColorStop(o/(u-1),a)}return s}var u=[],a={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:!0,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:!1,fillColor:null,steps:!1},bars:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,align:"left",horizontal:!1,zero:!0},shadowSize:3,highlightColor:null},grid:{show:!0,aboveData:!1,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:!1,hoverable:!1,autoHighlight:!0,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},f=null,l=null,c=null,h=null,p=null,d=[],v=[],m={left:0,right:0,top:0,bottom -:0},g=0,y=0,b={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},w=this;w.setData=T,w.setupGrid=R,w.draw=V,w.getPlaceholder=function(){return t},w.getCanvas=function(){return f.element},w.getPlotOffset=function(){return m},w.width=function(){return g},w.height=function(){return y},w.offset=function(){var e=c.offset();return e.left+=m.left,e.top+=m.top,e},w.getData=function(){return u},w.getAxes=function(){var t={},n;return e.each(d.concat(v),function(e,n){n&&(t[n.direction+(n.n!=1?n.n:"")+"axis"]=n)}),t},w.getXAxes=function(){return d},w.getYAxes=function(){return v},w.c2p=L,w.p2c=A,w.getOptions=function(){return a},w.highlight=dt,w.unhighlight=vt,w.triggerRedrawOverlay=ht,w.pointOffset=function(e){return{left:parseInt(d[C(e,"x")-1].p2c(+e.x)+m.left,10),top:parseInt(v[C(e,"y")-1].p2c(+e.y)+m.top,10)}},w.shutdown=H,w.resize=function(){var e=t.width(),n=t.height();f.resize(e,n),l.resize(e,n)},w.hooks=b,S(w),x(s),D(),T(r),R(),V(),P();var st=[],ot=null}function i(e,t){return t*Math.floor(e/t)}var t=Object.prototype.hasOwnProperty;n.prototype.resize=function(e,t){if(e<=0||t<=0)throw new Error("Invalid dimensions for plot, width = "+e+", height = "+t);var n=this.element,r=this.context,i=this.pixelRatio;this.width!=e&&(n.width=e*i,n.style.width=e+"px",this.width=e),this.height!=t&&(n.height=t*i,n.style.height=t+"px",this.height=t),r.restore(),r.save(),r.scale(i,i)},n.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},n.prototype.render=function(){var e=this._textCache;for(var n in e)if(t.call(e,n)){var r=this.getTextLayer(n),i=e[n];r.hide();for(var s in i)if(t.call(i,s)){var o=i[s];for(var u in o)if(t.call(o,u)){var a=o[u].positions;for(var f=0,l;l=a[f];f++)l.active?l.rendered||(r.append(l.element),l.rendered=!0):(a.splice(f--,1),l.rendered&&l.element.detach());a.length==0&&delete o[u]}}r.show()}},n.prototype.getTextLayer=function(t){var n=this.text[t];return n==null&&(this.textContainer==null&&(this.textContainer=e("<div class='flot-text'></div>").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)),n=this.text[t]=e("<div></div>").addClass(t).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)),n},n.prototype.getTextInfo=function(t,n,r,i,s){var o,u,a,f;n=""+n,typeof r=="object"?o=r.style+" "+r.variant+" "+r.weight+" "+r.size+"px/"+r.lineHeight+"px "+r.family:o=r,u=this._textCache[t],u==null&&(u=this._textCache[t]={}),a=u[o],a==null&&(a=u[o]={}),f=a[n];if(f==null){var l=e("<div></div>").html(n).css({position:"absolute","max-width":s,top:-9999}).appendTo(this.getTextLayer(t));typeof r=="object"?l.css({font:o,color:r.color}):typeof r=="string"&&l.addClass(r),f=a[n]={width:l.outerWidth(!0),height:l.outerHeight(!0),element:l,positions:[]},l.detach()}return f},n.prototype.addText=function(e,t,n,r,i,s,o,u,a){var f=this.getTextInfo(e,r,i,s,o),l=f.positions;u=="center"?t-=f.width/2:u=="right"&&(t-=f.width),a=="middle"?n-=f.height/2:a=="bottom"&&(n-=f.height);for(var c=0,h;h=l[c];c++)if(h.x==t&&h.y==n){h.active=!0;return}h={active:!0,rendered:!1,element:l.length?f.element.clone():f.element,x:t,y:n},l.push(h),h.element.css({top:Math.round(n),left:Math.round(t),"text-align":u})},n.prototype.removeText=function(e,n,r,i,s,o){if(i==null){var u=this._textCache[e];if(u!=null)for(var a in u)if(t.call(u,a)){var f=u[a];for(var l in f)if(t.call(f,l)){var c=f[l].positions;for(var h=0,p;p=c[h];h++)p.active=!1}}}else{var c=this.getTextInfo(e,i,s,o).positions;for(var h=0,p;p=c[h];h++)p.x==n&&p.y==r&&(p.active=!1)}},e.plot=function(t,n,i){var s=new r(e(t),n,i,e.plot.plugins);return s},e.plot.version="0.8.1",e.plot.plugins=[],e.fn.plot=function(t,n){return this.each(function(){e.plot(this,t,n)})}}(jQuery);
\ No newline at end of file +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){var cache=this._textCache;for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layer=this.getTextLayer(layerKey),layerCache=cache[layerKey];layer.hide();for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){if(position.active){if(!position.rendered){layer.append(position.element);position.rendered=true}}else{positions.splice(i--,1);if(position.rendered){position.element.detach()}}}if(positions.length==0){delete styleCache[key]}}}}}layer.show()}}};Canvas.prototype.getTextLayer=function(classes){var layer=this.text[classes];if(layer==null){if(this.textContainer==null){this.textContainer=$("<div class='flot-text'></div>").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}layer=this.text[classes]=$("<div></div>").addClass(classes).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return layer};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px/"+font.lineHeight+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var element=$("<div></div>").html(text).css({position:"absolute","max-width":width,top:-9999}).appendTo(this.getTextLayer(layer));if(typeof font==="object"){element.css({font:textStyle,color:font.color})}else{if(typeof font==="string"){element.addClass(font)}}info=styleCache[text]={width:element.outerWidth(true),height:element.outerHeight(true),element:element,positions:[]};element.detach()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions;if(halign=="center"){x-=info.width/2}else{if(halign=="right"){x-=info.width}}if(valign=="middle"){y-=info.height/2}else{if(valign=="bottom"){y-=info.height}}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,rendered:false,element:positions.length?info.element.clone():info.element,x:x,y:y};positions.push(position);position.element.css({top:Math.round(y),left:Math.round(x),"text-align":halign})};Canvas.prototype.removeText=function(layer,x,y,text,font,angle){if(text==null){var layerCache=this._textCache[layer];if(layerCache!=null){for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){position.active=false}}}}}}}else{var positions=this.getTextInfo(layer,text,font,angle).positions;for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=false}}}};function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1000/60},hooks:{}},surface=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return surface.element};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis){res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis}});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left,10),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top,10)}};plot.shutdown=shutdown;plot.resize=function(){var width=placeholder.width(),height=placeholder.height();surface.resize(width,height);overlay.resize(width,height)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;i<hook.length;++i){hook[i].apply(this,args)}}function initPlugins(){var classes={Canvas:Canvas};for(var i=0;i<plugins.length;++i){var p=plugins[i];p.init(plot,classes);if(p.options){$.extend(true,options,p.options)}}}function parseOptions(opts){$.extend(true,options,opts);if(opts&&opts.colors){options.colors=opts.colors}if(options.xaxis.color==null){options.xaxis.color=$.color.parse(options.grid.color).scale("a",0.22).toString()}if(options.yaxis.color==null){options.yaxis.color=$.color.parse(options.grid.color).scale("a",0.22).toString()}if(options.xaxis.tickColor==null){options.xaxis.tickColor=options.grid.tickColor||options.xaxis.color}if(options.yaxis.tickColor==null){options.yaxis.tickColor=options.grid.tickColor||options.yaxis.color}if(options.grid.borderColor==null){options.grid.borderColor=options.grid.color}if(options.grid.tickColor==null){options.grid.tickColor=$.color.parse(options.grid.color).scale("a",0.22).toString()}var i,axisOptions,axisCount,fontDefaults={style:placeholder.css("font-style"),size:Math.round(0.8*(+placeholder.css("font-size").replace("px","")||13)),variant:placeholder.css("font-variant"),weight:placeholder.css("font-weight"),family:placeholder.css("font-family")};fontDefaults.lineHeight=fontDefaults.size*1.15;axisCount=options.xaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.xaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.xaxis,axisOptions);options.xaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}}}axisCount=options.yaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.yaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.yaxis,axisOptions);options.yaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}}}if(options.xaxis.noTicks&&options.xaxis.ticks==null){options.xaxis.ticks=options.xaxis.noTicks}if(options.yaxis.noTicks&&options.yaxis.ticks==null){options.yaxis.ticks=options.yaxis.noTicks}if(options.x2axis){options.xaxes[1]=$.extend(true,{},options.xaxis,options.x2axis);options.xaxes[1].position="top"}if(options.y2axis){options.yaxes[1]=$.extend(true,{},options.yaxis,options.y2axis);options.yaxes[1].position="right"}if(options.grid.coloredAreas){options.grid.markings=options.grid.coloredAreas}if(options.grid.coloredAreasColor){options.grid.markingsColor=options.grid.coloredAreasColor}if(options.lines){$.extend(true,options.series.lines,options.lines)}if(options.points){$.extend(true,options.series.points,options.points)}if(options.bars){$.extend(true,options.series.bars,options.bars)}if(options.shadowSize!=null){options.series.shadowSize=options.shadowSize}if(options.highlightColor!=null){options.series.highlightColor=options.highlightColor}for(i=0;i<options.xaxes.length;++i){getOrCreateAxis(xaxes,i+1).options=options.xaxes[i]}for(i=0;i<options.yaxes.length;++i){getOrCreateAxis(yaxes,i+1).options=options.yaxes[i]}for(var n in hooks){if(options.hooks[n]&&options.hooks[n].length){hooks[n]=hooks[n].concat(options.hooks[n])}}executeHooks(hooks.processOptions,[options])}function setData(d){series=parseData(d);fillInSeriesOptions();processData()}function parseData(d){var res=[];for(var i=0;i<d.length;++i){var s=$.extend(true,{},options.series);if(d[i].data!=null){s.data=d[i].data;delete d[i].data;$.extend(true,s,d[i]);d[i].data=s.data}else{s.data=d[i]}res.push(s)}return res}function axisNumber(obj,coord){var a=obj[coord+"axis"];if(typeof a=="object"){a=a.n}if(typeof a!="number"){a=1}return a}function allAxes(){return $.grep(xaxes.concat(yaxes),function(a){return a})}function canvasToAxisCoords(pos){var res={},i,axis;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used){res["x"+axis.n]=axis.c2p(pos.left)}}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used){res["y"+axis.n]=axis.c2p(pos.top)}}if(res.x1!==undefined){res.x=res.x1}if(res.y1!==undefined){res.y=res.y1}return res}function axisToCanvasCoords(pos){var res={},i,axis,key;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used){key="x"+axis.n;if(pos[key]==null&&axis.n==1){key="x"}if(pos[key]!=null){res.left=axis.p2c(pos[key]);break}}}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used){key="y"+axis.n;if(pos[key]==null&&axis.n==1){key="y"}if(pos[key]!=null){res.top=axis.p2c(pos[key]);break}}}return res}function getOrCreateAxis(axes,number){if(!axes[number-1]){axes[number-1]={n:number,direction:axes==xaxes?"x":"y",options:$.extend(true,{},axes==xaxes?options.xaxis:options.yaxis)}}return axes[number-1]}function fillInSeriesOptions(){var neededColors=series.length,maxIndex=-1,i;for(i=0;i<series.length;++i){var sc=series[i].color;if(sc!=null){neededColors--;if(typeof sc=="number"&&sc>maxIndex){maxIndex=sc}}}if(neededColors<=maxIndex){neededColors=maxIndex+1}var c,colors=[],colorPool=options.colors,colorPoolSize=colorPool.length,variation=0;for(i=0;i<neededColors;i++){c=$.color.parse(colorPool[i%colorPoolSize]||"#666");if(i%colorPoolSize==0&&i){if(variation>=0){if(variation<0.5){variation=-variation-0.2}else{variation=0}}else{variation=-variation}}colors[i]=c.scale("rgb",1+variation)}var colori=0,s;for(i=0;i<series.length;++i){s=series[i];if(s.color==null){s.color=colors[colori].toString();++colori}else{if(typeof s.color=="number"){s.color=colors[s.color].toString()}}if(s.lines.show==null){var v,show=true;for(v in s){if(s[v]&&s[v].show){show=false;break}}if(show){s.lines.show=true}}if(s.lines.zero==null){s.lines.zero=!!s.lines.fill}s.xaxis=getOrCreateAxis(xaxes,axisNumber(s,"x"));s.yaxis=getOrCreateAxis(yaxes,axisNumber(s,"y"))}}function processData(){var topSentry=Number.POSITIVE_INFINITY,bottomSentry=Number.NEGATIVE_INFINITY,fakeInfinity=Number.MAX_VALUE,i,j,k,m,length,s,points,ps,x,y,axis,val,f,p,data,format;function updateAxis(axis,min,max){if(min<axis.datamin&&min!=-fakeInfinity){axis.datamin=min}if(max>axis.datamax&&max!=fakeInfinity){axis.datamax=max}}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i<series.length;++i){s=series[i];s.datapoints={points:[]};executeHooks(hooks.processRawData,[s,s.data,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];data=s.data;format=s.datapoints.format;if(!format){format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||(s.lines.show&&s.lines.fill)){var autoscale=!!((s.bars.show&&s.bars.zero)||(s.lines.show&&s.lines.zero));format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}s.datapoints.format=format}if(s.datapoints.pointsize!=null){continue}s.datapoints.pointsize=format.length;ps=s.datapoints.pointsize;points=s.datapoints.points;var insertSteps=s.lines.show&&s.lines.steps;s.xaxis.used=s.yaxis.used=true;for(j=k=0;j<data.length;++j,k+=ps){p=data[j];var nullify=p==null;if(!nullify){for(m=0;m<ps;++m){val=p[m];f=format[m];if(f){if(f.number&&val!=null){val=+val;if(isNaN(val)){val=null}else{if(val==Infinity){val=fakeInfinity}else{if(val==-Infinity){val=-fakeInfinity}}}}if(val==null){if(f.required){nullify=true}if(f.defaultValue!=null){val=f.defaultValue}}}points[k+m]=val}}if(nullify){for(m=0;m<ps;++m){val=points[k+m];if(val!=null){f=format[m];if(f.autoscale!==false){if(f.x){updateAxis(s.xaxis,val,val)}if(f.y){updateAxis(s.yaxis,val,val)}}}points[k+m]=null}}else{if(insertSteps&&k>0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;m<ps;++m){points[k+ps+m]=points[k+m]}points[k+1]=points[k-ps+1];k+=ps}}}}for(i=0;i<series.length;++i){s=series[i];executeHooks(hooks.processDatapoints,[s,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];points=s.datapoints.points;ps=s.datapoints.pointsize;format=s.datapoints.format;var xmin=topSentry,ymin=topSentry,xmax=bottomSentry,ymax=bottomSentry;for(j=0;j<points.length;j+=ps){if(points[j]==null){continue}for(m=0;m<ps;++m){val=points[j+m];f=format[m];if(!f||f.autoscale===false||val==fakeInfinity||val==-fakeInfinity){continue}if(f.x){if(val<xmin){xmin=val}if(val>xmax){xmax=val}}if(f.y){if(val<ymin){ymin=val}if(val>ymax){ymax=val}}}}if(s.bars.show){var delta;switch(s.bars.align){case"left":delta=0;break;case"right":delta=-s.bars.barWidth;break;default:delta=-s.bars.barWidth/2}if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry){axis.datamin=null}if(axis.datamax==bottomSentry){axis.datamax=null}})}function setupCanvases(){placeholder.css("padding",0).children(":not(.flot-base,.flot-overlay)").remove();if(placeholder.css("position")=="static"){placeholder.css("position","relative")}surface=new Canvas("flot-base",placeholder);overlay=new Canvas("flot-overlay",placeholder);ctx=surface.context;octx=overlay.context;eventHolder=$(overlay.element).unbind();var existing=placeholder.data("plot");if(existing){existing.shutdown();overlay.clear()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.bind("mouseleave",onMouseLeave)}if(options.grid.clickable){eventHolder.click(onClick)}executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout){clearTimeout(redrawTimeout)}eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity){axis.p2c=function(p){return(p-m)*s}}else{axis.p2c=function(p){return(t(p)-m)*s}}if(!it){axis.c2p=function(c){return m+c/s}}else{axis.c2p=function(c){return it(m+c/s)}}}function measureTickLabels(axis){var opts=axis.options,ticks=axis.ticks||[],labelWidth=opts.labelWidth||0,labelHeight=opts.labelHeight||0,maxWidth=labelWidth||axis.direction=="x"?Math.floor(surface.width/(ticks.length||1)):null,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=opts.font||"flot-tick-label tickLabel";for(var i=0;i<ticks.length;++i){var t=ticks[i];if(!t.label){continue}var info=surface.getTextInfo(layer,t.label,font,null,maxWidth);labelWidth=Math.max(labelWidth,info.width);labelHeight=Math.max(labelHeight,info.height)}axis.labelWidth=opts.labelWidth||labelWidth;axis.labelHeight=opts.labelHeight||labelHeight}function allocateAxisBoxFirstPhase(axis){var lw=axis.labelWidth,lh=axis.labelHeight,pos=axis.options.position,tickLength=axis.options.tickLength,axisMargin=options.grid.axisMargin,padding=options.grid.labelMargin,all=axis.direction=="x"?xaxes:yaxes,index,innermost;var samePosition=$.grep(all,function(a){return a&&a.options.position==pos&&a.reserveSpace});if($.inArray(axis,samePosition)==samePosition.length-1){axisMargin=0}innermost=$.inArray(axis,samePosition)==0;if(tickLength==null){if(innermost){tickLength="full"}else{tickLength=5}}if(!isNaN(+tickLength)){padding+=+tickLength}if(axis.direction=="x"){lh+=padding;if(pos=="bottom"){plotOffset.bottom+=lh+axisMargin;axis.box={top:surface.height-plotOffset.bottom,height:lh}}else{axis.box={top:plotOffset.top+axisMargin,height:lh};plotOffset.top+=lh+axisMargin}}else{lw+=padding;if(pos=="left"){axis.box={left:plotOffset.left+axisMargin,width:lw};plotOffset.left+=lw+axisMargin}else{plotOffset.right+=lw+axisMargin;axis.box={left:surface.width-plotOffset.right,width:lw}}}axis.position=pos;axis.tickLength=tickLength;axis.box.padding=padding;axis.innermost=innermost}function allocateAxisBoxSecondPhase(axis){if(axis.direction=="x"){axis.box.left=plotOffset.left-axis.labelWidth/2;axis.box.width=surface.width-plotOffset.left-plotOffset.right+axis.labelWidth}else{axis.box.top=plotOffset.top-axis.labelHeight/2;axis.box.height=surface.height-plotOffset.bottom-plotOffset.top+axis.labelHeight}}function adjustLayoutForThingsStickingOut(){var minMargin=options.grid.minBorderMargin,margins={x:0,y:0},i,axis;if(minMargin==null){minMargin=0;for(i=0;i<series.length;++i){minMargin=Math.max(minMargin,2*(series[i].points.radius+series[i].points.lineWidth/2))}}margins.x=margins.y=Math.ceil(minMargin);$.each(allAxes(),function(_,axis){var dir=axis.direction;if(axis.reserveSpace){margins[dir]=Math.ceil(Math.max(margins[dir],(dir=="x"?axis.labelWidth:axis.labelHeight)/2))}});plotOffset.left=Math.max(margins.x,plotOffset.left);plotOffset.right=Math.max(margins.x,plotOffset.right);plotOffset.top=Math.max(margins.y,plotOffset.top);plotOffset.bottom=Math.max(margins.y,plotOffset.bottom)}function setupGrid(){var i,axes=allAxes(),showGrid=options.grid.show;for(var a in plotOffset){var margin=options.grid.margin||0;plotOffset[a]=typeof margin=="number"?margin:margin[a]||0}executeHooks(hooks.processOffset,[plotOffset]);for(var a in plotOffset){if(typeof(options.grid.borderWidth)=="object"){plotOffset[a]+=showGrid?options.grid.borderWidth[a]:0}else{plotOffset[a]+=showGrid?options.grid.borderWidth:0}}$.each(axes,function(_,axis){axis.show=axis.options.show;if(axis.show==null){axis.show=axis.used}axis.reserveSpace=axis.show||axis.options.reserveSpace;setRange(axis)});if(showGrid){var allocatedAxes=$.grep(axes,function(axis){return axis.reserveSpace});$.each(allocatedAxes,function(_,axis){setupTickGeneration(axis);setTicks(axis);snapRangeToTicks(axis,axis.ticks);measureTickLabels(axis)});for(i=allocatedAxes.length-1;i>=0;--i){allocateAxisBoxFirstPhase(allocatedAxes[i])}adjustLayoutForThingsStickingOut();$.each(allocatedAxes,function(_,axis){allocateAxisBoxSecondPhase(axis)})}plotWidth=surface.width-plotOffset.left-plotOffset.right;plotHeight=surface.height-plotOffset.bottom-plotOffset.top;$.each(axes,function(_,axis){setTransformationHelpers(axis)});if(showGrid){drawAxisLabels()}insertLegend()}function setRange(axis){var opts=axis.options,min=+(opts.min!=null?opts.min:axis.datamin),max=+(opts.max!=null?opts.max:axis.datamax),delta=max-min;if(delta==0){var widen=max==0?1:0.01;if(opts.min==null){min-=widen}if(opts.max==null||opts.min!=null){max+=widen}}else{var margin=opts.autoscaleMargin;if(margin!=null){if(opts.min==null){min-=delta*margin;if(min<0&&axis.datamin!=null&&axis.datamin>=0){min=0}}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0){max=0}}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0){noTicks=opts.ticks}else{noTicks=0.3*Math.sqrt(axis.direction=="x"?surface.width:surface.height)}var delta=(axis.max-axis.min)/noTicks,dec=-Math.floor(Math.log(delta)/Math.LN10),maxDec=opts.tickDecimals;if(maxDec!=null&&dec>maxDec){dec=maxDec}var magn=Math.pow(10,-dec),norm=delta/magn,size;if(norm<1.5){size=1}else{if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else{if(norm<7.5){size=5}else{size=10}}}size*=magn;if(opts.minTickSize!=null&&size<opts.minTickSize){size=opts.minTickSize}axis.delta=delta;axis.tickDecimals=Math.max(0,maxDec!=null?maxDec:dec);axis.tickSize=opts.tickSize||size;if(opts.mode=="time"&&!axis.tickGenerator){throw new Error("Time mode requires the flot.time plugin.")}if(!axis.tickGenerator){axis.tickGenerator=function(axis){var ticks=[],start=floorInBase(axis.min,axis.tickSize),i=0,v=Number.NaN,prev;do{prev=v;v=start+i*axis.tickSize;ticks.push(v);++i}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(value,axis){var factor=axis.tickDecimals?Math.pow(10,axis.tickDecimals):1;var formatted=""+Math.round(value*factor)/factor;if(axis.tickDecimals!=null){var decimal=formatted.indexOf(".");var precision=decimal==-1?0:formatted.length-decimal-1;if(precision<axis.tickDecimals){return(precision?formatted:formatted+".")+(""+factor).substr(1,axis.tickDecimals-precision)}}return formatted}}if($.isFunction(opts.tickFormatter)){axis.tickFormatter=function(v,axis){return""+opts.tickFormatter(v,axis)}}if(opts.alignTicksWithAxis!=null){var otherAxis=(axis.direction=="x"?xaxes:yaxes)[opts.alignTicksWithAxis-1];if(otherAxis&&otherAxis.used&&otherAxis!=axis){var niceTicks=axis.tickGenerator(axis);if(niceTicks.length>0){if(opts.min==null){axis.min=Math.min(axis.min,niceTicks[0])}if(opts.max==null&&niceTicks.length>1){axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}}axis.tickGenerator=function(axis){var ticks=[],v,i;for(i=0;i<otherAxis.ticks.length;++i){v=(otherAxis.ticks[i].v-otherAxis.min)/(otherAxis.max-otherAxis.min);v=axis.min+v*(axis.max-axis.min);ticks.push(v)}return ticks};if(!axis.mode&&opts.tickDecimals==null){var extraDec=Math.max(0,-Math.floor(Math.log(axis.delta)/Math.LN10)+1),ts=axis.tickGenerator(axis);if(!(ts.length>1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec)))){axis.tickDecimals=extraDec}}}}}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||(typeof oticks=="number"&&oticks>0)){ticks=axis.tickGenerator(axis)}else{if(oticks){if($.isFunction(oticks)){ticks=oticks(axis)}else{ticks=oticks}}}var i,v;axis.ticks=[];for(i=0;i<ticks.length;++i){var label=null;var t=ticks[i];if(typeof t=="object"){v=+t[0];if(t.length>1){label=t[1]}}else{v=+t}if(label==null){label=axis.tickFormatter(v,axis)}if(!isNaN(v)){axis.ticks.push({v:v,label:label})}}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null){axis.min=Math.min(axis.min,ticks[0].v)}if(axis.options.max==null&&ticks.length>1){axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}}function draw(){surface.clear();executeHooks(hooks.drawBackground,[ctx]);var grid=options.grid;if(grid.show&&grid.backgroundColor){drawBackground()}if(grid.show&&!grid.aboveData){drawGrid()}for(var i=0;i<series.length;++i){executeHooks(hooks.drawSeries,[ctx,series[i]]);drawSeries(series[i])}executeHooks(hooks.draw,[ctx]);if(grid.show&&grid.aboveData){drawGrid()}surface.render();triggerRedrawOverlay()}function extractRange(ranges,coord){var axis,from,to,key,axes=allAxes();for(var i=0;i<axes.length;++i){axis=axes[i];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1){key=coord+"axis"}if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?xaxes[0]:yaxes[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i,axes,bw,bc;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;i<markings.length;++i){var m=markings[i],xrange=extractRange(m,"x"),yrange=extractRange(m,"y");if(xrange.from==null){xrange.from=xrange.axis.min}if(xrange.to==null){xrange.to=xrange.axis.max}if(yrange.from==null){yrange.from=yrange.axis.min}if(yrange.to==null){yrange.to=yrange.axis.max}if(xrange.to<xrange.axis.min||xrange.from>xrange.axis.max||yrange.to<yrange.axis.min||yrange.from>yrange.axis.max){continue}xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);if(xrange.from==xrange.to&&yrange.from==yrange.to){continue}xrange.from=xrange.axis.p2c(xrange.from);xrange.to=xrange.axis.p2c(xrange.to);yrange.from=yrange.axis.p2c(yrange.from);yrange.to=yrange.axis.p2c(yrange.to);if(xrange.from==xrange.to||yrange.from==yrange.to){ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=m.lineWidth||options.grid.markingsLineWidth;ctx.moveTo(xrange.from,yrange.from);ctx.lineTo(xrange.to,yrange.to);ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;j<axes.length;++j){var axis=axes[j],box=axis.box,t=axis.tickLength,x,y,xoff,yoff;if(!axis.show||axis.ticks.length==0){continue}ctx.lineWidth=1;if(axis.direction=="x"){x=0;if(t=="full"){y=(axis.position=="top"?0:plotHeight)}else{y=box.top-plotOffset.top+(axis.position=="top"?box.height:0)}}else{y=0;if(t=="full"){x=(axis.position=="left"?0:plotWidth)}else{x=box.left-plotOffset.left+(axis.position=="left"?box.width:0)}}if(!axis.innermost){ctx.strokeStyle=axis.options.color;ctx.beginPath();xoff=yoff=0;if(axis.direction=="x"){xoff=plotWidth+1}else{yoff=plotHeight+1}if(ctx.lineWidth==1){if(axis.direction=="x"){y=Math.floor(y)+0.5}else{x=Math.floor(x)+0.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff);ctx.stroke()}ctx.strokeStyle=axis.options.tickColor;ctx.beginPath();for(i=0;i<axis.ticks.length;++i){var v=axis.ticks[i].v;xoff=yoff=0;if(isNaN(v)||v<axis.min||v>axis.max||(t=="full"&&((typeof bw=="object"&&bw[axis.position]>0)||bw>0)&&(v==axis.min||v==axis.max))){continue}if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top"){yoff=-yoff}}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left"){xoff=-xoff}}if(ctx.lineWidth==1){if(axis.direction=="x"){x=Math.floor(x)+0.5}else{y=Math.floor(y)+0.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0){return}for(var i=0;i<axis.ticks.length;++i){tick=axis.ticks[i];if(!tick.label||tick.v<axis.min||tick.v>axis.max){continue}if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show){drawSeriesLines(series)}if(series.bars.show){drawSeriesBars(series)}if(series.points.show){drawSeriesPoints(series)}}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i<points.length;i+=ps){var x1=points[i-ps],y1=points[i-ps+1],x2=points[i],y2=points[i+1];if(x1==null||x2==null){continue}if(y1<=y2&&y1<axisy.min){if(y2<axisy.min){continue}x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else{if(y2<=y1&&y2<axisy.min){if(y1<axisy.min){continue}x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}}if(y1>=y2&&y1>axisy.max){if(y2>axisy.max){continue}x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else{if(y2>=y1&&y2>axisy.max){if(y1>axisy.max){continue}x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}}if(x1<=x2&&x1<axisx.min){if(x2<axisx.min){continue}y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else{if(x2<=x1&&x2<axisx.min){if(x1<axisx.min){continue}y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max){continue}y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else{if(x2>=x1&&x2>axisx.max){if(x1>axisx.max){continue}y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}}if(x1!=prevx||y1!=prevy){ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset)}prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps){break}i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null){continue}if(x1<=x2&&x1<axisx.min){if(x2<axisx.min){continue}y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else{if(x2<=x1&&x2<axisx.min){if(x1<axisx.min){continue}y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max){continue}y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else{if(x2>=x1&&x2>axisx.max){if(x1>axisx.max){continue}y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else{if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}}var x1old=x1,x2old=x2;if(y1<=y2&&y1<axisy.min&&y2>=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else{if(y2<=y1&&y2<axisy.min&&y1>=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else{if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0){plotLine(series.datapoints,0,0,series.xaxis,series.yaxis)}ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var x=points[i],y=points[i+1];if(x==null||x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max){continue}ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle"){ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false)}else{symbol(ctx,x,y,radius,shadow)}ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw==0){lw=0.0001}if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(right<left){tmp=right;right=left;left=tmp;drawLeft=true;drawRight=false}}else{drawLeft=drawRight=drawTop=true;drawBottom=false;left=x+barLeft;right=x+barRight;bottom=b;top=y;if(top<bottom){tmp=top;top=bottom;bottom=tmp;drawBottom=true;drawTop=false}}if(right<axisx.min||left>axisx.max||top<axisy.min||bottom>axisy.max){return}if(left<axisx.min){left=axisx.min;drawLeft=false}if(right>axisx.max){right=axisx.max;drawRight=false}if(bottom<axisy.min){bottom=axisy.min;drawBottom=false}if(top>axisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.fillStyle=fillStyleCallback(bottom,top);c.fillRect(left,top,right-left,bottom-top)}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom);if(drawLeft){c.lineTo(left,top)}else{c.moveTo(left,top)}if(drawTop){c.lineTo(right,top)}else{c.moveTo(right,top)}if(drawRight){c.lineTo(right,bottom)}else{c.moveTo(right,bottom)}if(drawBottom){c.lineTo(left,bottom)}else{c.moveTo(left,bottom)}c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){if(points[i]==null){continue}drawBar(points[i],points[i+1],points[i+2],barLeft,barRight,fillStyleCallback,axisx,axisy,ctx,series.bars.horizontal,series.bars.lineWidth)}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineWidth=series.bars.lineWidth;ctx.strokeStyle=series.color;var barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}var fillStyleCallback=series.bars.fill?function(bottom,top){return getFillStyle(series.bars,series.color,bottom,top)}:null;plotBars(series.datapoints,barLeft,barLeft+series.bars.barWidth,fillStyleCallback,series.xaxis,series.yaxis);ctx.restore()}function getFillStyle(filloptions,seriesColor,bottom,top){var fill=filloptions.fill;if(!fill){return null}if(filloptions.fillColor){return getColorOrGradient(filloptions.fillColor,bottom,top,seriesColor)}var c=$.color.parse(seriesColor);c.a=typeof fill=="number"?fill:0.4;c.normalize();return c.toString()}function insertLegend(){placeholder.find(".legend").remove();if(!options.legend.show){return}var fragments=[],entries=[],rowStarted=false,lf=options.legend.labelFormatter,s,label;for(var i=0;i<series.length;++i){s=series[i];if(s.label){label=lf?lf(s.label,s):s.label;if(label){entries.push({label:label,color:s.color})}}}if(options.legend.sorted){if($.isFunction(options.legend.sorted)){entries.sort(options.legend.sorted)}else{if(options.legend.sorted=="reverse"){entries.reverse()}else{var ascending=options.legend.sorted!="descending";entries.sort(function(a,b){return a.label==b.label?0:((a.label<b.label)!=ascending?1:-1)})}}}for(var i=0;i<entries.length;++i){var entry=entries[i];if(i%options.legend.noColumns==0){if(rowStarted){fragments.push("</tr>")}fragments.push("<tr>");rowStarted=true}fragments.push('<td class="legendColorBox"><div style="border:1px solid '+options.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+entry.color+';overflow:hidden"></div></div></td><td class="legendLabel">'+entry.label+"</td>")}if(rowStarted){fragments.push("</tr>")}if(fragments.length==0){return}var table='<table style="font-size:smaller;color:'+options.grid.color+'">'+fragments.join("")+"</table>";if(options.legend.container!=null){$(options.legend.container).html(table)}else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null){m=[m,m]}if(p.charAt(0)=="n"){pos+="top:"+(m[1]+plotOffset.top)+"px;"}else{if(p.charAt(0)=="s"){pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;"}}if(p.charAt(1)=="e"){pos+="right:"+(m[0]+plotOffset.right)+"px;"}else{if(p.charAt(1)=="w"){pos+="left:"+(m[0]+plotOffset.left)+"px;"}}var legend=$('<div class="legend">'+table.replace('style="','style="position:absolute;'+pos+";")+"</div>").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string"){c=$.color.parse(c)}else{c=$.color.extract(legend,"background-color")}c.a=1;c=c.toString()}var div=legend.children();$('<div style="position:absolute;width:'+div.width()+"px;height:"+div.height()+"px;"+pos+"background-color:"+c+';"> </div>').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j,ps;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i])){continue}var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;ps=s.datapoints.pointsize;if(axisx.options.inverseTransform){maxx=Number.MAX_VALUE}if(axisy.options.inverseTransform){maxy=Number.MAX_VALUE}if(s.lines.show||s.points.show){for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1];if(x==null){continue}if(x-mx>maxx||x-mx<-maxx||y-my>maxy||y-my<-maxy){continue}var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist<smallestDistance){smallestDistance=dist;item=[i,j/ps]}}}if(s.bars.show&&!item){var barLeft,barRight;switch(s.bars.align){case"left":barLeft=0;break;case"right":barLeft=-s.bars.barWidth;break;default:barLeft=-s.bars.barWidth/2}barRight=barLeft+s.bars.barWidth;for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1],b=points[j+2];if(x==null){continue}if(series[i].bars.horizontal?(mx<=Math.max(b,x)&&mx>=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight):(mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))){item=[i,j/ps]}}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable){triggerClickHoverEvent("plothover",e,function(s){return s.hoverable!=false})}}function onMouseLeave(e){if(options.grid.hoverable){triggerClickHoverEvent("plothover",e,function(s){return false})}}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s.clickable!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left,10);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top,10)}if(options.grid.autoHighlight){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.auto==eventname&&!(item&&h.series==item.series&&h.point[0]==item.datapoint[0]&&h.point[1]==item.datapoint[1])){unhighlight(h.series,h.point)}}if(item){highlight(item.series,item.datapoint,eventname)}}placeholder.trigger(eventname,[pos,item])}function triggerRedrawOverlay(){var t=options.interaction.redrawOverlayInterval;if(t==-1){drawOverlay();return}if(!redrawTimeout){redrawTimeout=setTimeout(drawOverlay,t)}}function drawOverlay(){redrawTimeout=null;octx.save();overlay.clear();octx.translate(plotOffset.left,plotOffset.top);var i,hi;for(i=0;i<highlights.length;++i){hi=highlights[i];if(hi.series.bars.show){drawBarHighlight(hi.series,hi.point)}else{drawPointHighlight(hi.series,hi.point)}}octx.restore();executeHooks(hooks.drawOverlay,[octx])}function highlight(s,point,auto){if(typeof s=="number"){s=series[s]}if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i==-1){highlights.push({series:s,point:point,auto:auto});triggerRedrawOverlay()}else{if(!auto){highlights[i].auto=false}}}function unhighlight(s,point){if(s==null&&point==null){highlights=[];triggerRedrawOverlay();return}if(typeof s=="number"){s=series[s]}if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i!=-1){highlights.splice(i,1);triggerRedrawOverlay()}}function indexOfHighlight(s,p){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.series==s&&h.point[0]==p[0]&&h.point[1]==p[1]){return i}}return -1}function drawPointHighlight(series,point){var x=point[0],y=point[1],axisx=series.xaxis,axisy=series.yaxis,highlightColor=(typeof series.highlightColor==="string")?series.highlightColor:$.color.parse(series.color).scale("a",0.5).toString();if(x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max){return}var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=highlightColor;var radius=1.5*pointRadius;x=axisx.p2c(x);y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle"){octx.arc(x,y,radius,0,2*Math.PI,false)}else{series.points.symbol(octx,x,y,radius,false)}octx.closePath();octx.stroke()}function drawBarHighlight(series,point){var highlightColor=(typeof series.highlightColor==="string")?series.highlightColor:$.color.parse(series.color).scale("a",0.5).toString(),fillStyle=highlightColor,barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=highlightColor;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string"){return spec}else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i<l;++i){var c=spec.colors[i];if(typeof c!="string"){var co=$.color.parse(defaultColor);if(c.brightness!=null){co=co.scale("rgb",c.brightness)}if(c.opacity!=null){co.a*=c.opacity}c=co.toString()}gradient.addColorStop(i/(l-1),c)}return gradient}}}$.plot=function(placeholder,data,options){var plot=new Plot($(placeholder),data,options,$.plot.plugins);return plot};$.plot.version="0.8.2-alpha";$.plot.plugins=[];$.fn.plot=function(data,options){return this.each(function(){$.plot(this,data,options)})};function floorInBase(n,base){return base*Math.floor(n/base)}})(jQuery);
\ No newline at end of file diff --git a/public/javascripts/jquery.flot.tickrotor.js b/public/javascripts/jquery.flot.tickrotor.js new file mode 100644 index 000000000..404b2b0a7 --- /dev/null +++ b/public/javascripts/jquery.flot.tickrotor.js @@ -0,0 +1,205 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is flot-tickrotor. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + *     Mark Cote <mcote@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * flot-tickrotor: flot plugin to display angled X-axis tick labels. + *  + * Requires flot 0.7 or higher and a browser supporting <canvas>. + *  + * To activate, just set xaxis.rotateTicks to an angle in degrees.  Labels + * are rotated clockwise, so if you want the labels to angle up and to the right (/) + * you need to provide an angle > 90.  The text will be flipped so that it is still + * right-side-up. + * Angles greater than or equal to 180 are ignored. + */ +(function ($) { +    var options = { }; + +    function init(plot) { +        // Taken from flot-axislabels. +        // This is kind of a hack. There are no hooks in Flot between +        // the creation and measuring of the ticks (setTicks, measureTickLabels +        // in setupGrid() ) and the drawing of the ticks and plot box +        // (insertAxisLabels in setupGrid() ). +        // +        // Therefore, we use a trick where we run the draw routine twice: +        // the first time to get the tick measurements, so that we can change +        // them, and then have it draw it again. +        var ticks = []; +        var font; +        var secondPass = false; +        var rotateTicks, rotateTicksRads, radsAboveHoriz; +        plot.hooks.draw.push(function (plot, ctx) { +            if (!secondPass) { +                var opts = plot.getAxes().xaxis.options; +                if (opts.rotateTicks === undefined) { +                    return; +                } +                 +                rotateTicks = parseInt(opts.rotateTicks, 10); +                if (rotateTicks.toString() != opts.rotateTicks || rotateTicks == 0 || rotateTicks >= 180) { +                    return; +                } +                 +                rotateTicksRads = rotateTicks * Math.PI/180; +                if (rotateTicks > 90) { +                    radsAboveHoriz = Math.PI - rotateTicksRads; +                } else { +                    radsAboveHoriz = Math.PI/2 - rotateTicksRads; +                } + +                font = opts.rotateTicksFont; +                if (!font) { +                    font = $('.tickLabel').css('font'); +                } +                if (!font) { +                    font = 'smaller sans-serif'; +                } +                 +                var elem, maxLabelWidth = 0, maxLabelHeight = 0, minX = 0, maxX = 0; + +                var xaxis = plot.getAxes().xaxis; +                ticks = plot.getAxes().xaxis.ticks; +                opts.ticks = [];  // we'll make our own + +                var x; +                for (var i = 0; i < ticks.length; i++) { +                  elem = $('<span style="font:' + font + '">' + ticks[i].label + '</span>'); +                  plot.getPlaceholder().append(elem); +                  ticks[i].height = elem.outerHeight(true); +                  ticks[i].width = elem.outerWidth(true); +                  elem.remove(); +                  if (ticks[i].height > maxLabelHeight) { +                      maxLabelHeight = ticks[i].height; +                  } +                  if (ticks[i].width > maxLabelWidth) { +                      maxLabelWidth = ticks[i].width; +                  } +                  var tick = ticks[i]; +                  // See second-draw code below for explanation of offsets. +                  if (rotateTicks > 90) { +                      // See if any labels are too long and require increased left +                      // padding. +                      x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) +                          - Math.ceil(Math.cos(radsAboveHoriz) * tick.height) +                          - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); +                      if (x < minX) { +                          minX = x; +                      } +                  } else { +                      // See if any labels are too long and require increased right +                      // padding. +                      x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) +                          + Math.ceil(Math.cos(radsAboveHoriz) * tick.height) +                          + Math.ceil(Math.cos(radsAboveHoriz) * tick.width); +                      if (x > maxX) { +                          maxX = x; +                      } +                  } +                } +                 +                // Calculate maximum label height after rotating. +                if (rotateTicks > 90) { +                    var acuteRads = rotateTicksRads - Math.PI/2; +                    opts.labelHeight = Math.ceil(Math.sin(acuteRads) * maxLabelWidth) +                                       + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); +                } else { +                    var acuteRads = Math.PI/2 - rotateTicksRads; +                    // Center such that the top of the label is at the center of the tick. +                    opts.labelHeight = Math.ceil(Math.sin(rotateTicksRads) * maxLabelWidth) +                                       + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); +                } +                 +                if (minX < 0) { +                  plot.getAxes().yaxis.options.labelWidth = -1 * minX; +                } +                 +                // Doesn't seem to work if there are no values using the second y axis. +                //if (maxX > xaxis.box.left + xaxis.box.width) { +                //  plot.getAxes().y2axis.options.labelWidth = maxX - xaxis.box.left - xaxis.box.width; +                //} +                 +                // re-draw with new label widths and heights +                secondPass = true; +                plot.setupGrid(); +                plot.draw(); +            } else { +                if (ticks.length == 0) { +                    return; +                } +                var xaxis = plot.getAxes().xaxis; +                var box = xaxis.box; +                var tick, label, xoffset, yoffset; +                for (var i = 0; i < ticks.length; i++) { +                    tick = ticks[i]; +                    if (!tick.label) { +                        continue; +                    } +                    ctx.save(); +                    ctx.font = font; +                    if (rotateTicks <= 90) { +                        // Center such that the top of the label is at the center of the tick. +                        xoffset = -Math.ceil(Math.cos(radsAboveHoriz) * tick.height); +                        yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.height); +                        ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) + xoffset, +                                      box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset); +                        ctx.rotate(rotateTicksRads); +                    } else { +                        // We want the text to facing up, so we have to rotate counterclockwise, +                        // which means the label has to *end* at the center of the tick.  +                        xoffset = Math.ceil(Math.cos(radsAboveHoriz) * tick.height) +                                  - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); +                        yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.width) +                                  + Math.ceil(Math.sin(radsAboveHoriz) * tick.height); +                        ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v) + xoffset), +                                      box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset);  +                        ctx.rotate(-radsAboveHoriz); +                    } +                    ctx.fillText(tick.label, 0, 0); +                    ctx.restore(); +                } +            } +        }); +    } + +    $.plot.plugins.push({ +        init: init, +        options: options, +        name: 'tickRotor', +        version: '1.0' +    }); +})(jQuery); diff --git a/public/javascripts/jquery.flot.tickrotor.min.js b/public/javascripts/jquery.flot.tickrotor.min.js new file mode 100644 index 000000000..87fc79d10 --- /dev/null +++ b/public/javascripts/jquery.flot.tickrotor.min.js @@ -0,0 +1 @@ +(function($){var options={};function init(plot){var ticks=[];var font;var secondPass=false;var rotateTicks,rotateTicksRads,radsAboveHoriz;plot.hooks.draw.push(function(plot,ctx){if(!secondPass){var opts=plot.getAxes().xaxis.options;if(opts.rotateTicks===undefined){return}rotateTicks=parseInt(opts.rotateTicks,10);if(rotateTicks.toString()!=opts.rotateTicks||rotateTicks==0||rotateTicks>=180){return}rotateTicksRads=rotateTicks*Math.PI/180;if(rotateTicks>90){radsAboveHoriz=Math.PI-rotateTicksRads}else{radsAboveHoriz=Math.PI/2-rotateTicksRads}font=opts.rotateTicksFont;if(!font){font=$(".tickLabel").css("font")}if(!font){font="smaller sans-serif"}var elem,maxLabelWidth=0,maxLabelHeight=0,minX=0,maxX=0;var xaxis=plot.getAxes().xaxis;ticks=plot.getAxes().xaxis.ticks;opts.ticks=[];var x;for(var i=0;i<ticks.length;i++){elem=$('<span style="font:'+font+'">'+ticks[i].label+"</span>");plot.getPlaceholder().append(elem);ticks[i].height=elem.outerHeight(true);ticks[i].width=elem.outerWidth(true);elem.remove();if(ticks[i].height>maxLabelHeight){maxLabelHeight=ticks[i].height}if(ticks[i].width>maxLabelWidth){maxLabelWidth=ticks[i].width}var tick=ticks[i];if(rotateTicks>90){x=Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v))-Math.ceil(Math.cos(radsAboveHoriz)*tick.height)-Math.ceil(Math.cos(radsAboveHoriz)*tick.width);if(x<minX){minX=x}}else{x=Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v))+Math.ceil(Math.cos(radsAboveHoriz)*tick.height)+Math.ceil(Math.cos(radsAboveHoriz)*tick.width);if(x>maxX){maxX=x}}}if(rotateTicks>90){var acuteRads=rotateTicksRads-Math.PI/2;opts.labelHeight=Math.ceil(Math.sin(acuteRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}else{var acuteRads=Math.PI/2-rotateTicksRads;opts.labelHeight=Math.ceil(Math.sin(rotateTicksRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}if(minX<0){plot.getAxes().yaxis.options.labelWidth=-1*minX}secondPass=true;plot.setupGrid();plot.draw()}else{if(ticks.length==0){return}var xaxis=plot.getAxes().xaxis;var box=xaxis.box;var tick,label,xoffset,yoffset;for(var i=0;i<ticks.length;i++){tick=ticks[i];if(!tick.label){continue}ctx.save();ctx.font=font;if(rotateTicks<=90){xoffset=-Math.ceil(Math.cos(radsAboveHoriz)*tick.height);yoffset=Math.ceil(Math.sin(radsAboveHoriz)*tick.height);ctx.translate(Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v))+xoffset,box.top+box.padding+plot.getOptions().grid.labelMargin+yoffset);ctx.rotate(rotateTicksRads)}else{xoffset=Math.ceil(Math.cos(radsAboveHoriz)*tick.height)-Math.ceil(Math.cos(radsAboveHoriz)*tick.width);yoffset=Math.ceil(Math.sin(radsAboveHoriz)*tick.width)+Math.ceil(Math.sin(radsAboveHoriz)*tick.height);ctx.translate(Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v)+xoffset),box.top+box.padding+plot.getOptions().grid.labelMargin+yoffset);ctx.rotate(-radsAboveHoriz)}ctx.fillText(tick.label,0,0);ctx.restore()}}})}$.plot.plugins.push({init:init,options:options,name:"tickRotor",version:"1.0"})})(jQuery);
\ No newline at end of file diff --git a/public/javascripts/stats-graphs.js b/public/javascripts/stats-graphs.js index 73e19a6fc..ff3d3a11a 100644 --- a/public/javascripts/stats-graphs.js +++ b/public/javascripts/stats-graphs.js @@ -11,7 +11,8 @@ $(document).ready(function() {              dataset,              plot,              graph_data, -            graph_div = $('#' + graph_id); +            graph_div = $('#' + graph_id), +            previousPoint = null;          if (!graph_data.x_values) {              /* Then there's no data for this graph */ @@ -19,7 +20,7 @@ $(document).ready(function() {          }          graph_div.css('width', '700px'); -        graph_div.css('height', '400px'); +        graph_div.css('height', '600px');          dataset = [              {'color': 'orange', @@ -56,8 +57,10 @@ $(document).ready(function() {          }          options = { +            'grid': { 'hoverable': true, 'clickable': true },              'xaxis': {                  'ticks': graph_data.x_ticks, +                'rotateTicks': 90              },              'yaxis': {                  'min': 0, @@ -83,5 +86,50 @@ $(document).ready(function() {          plot = $.plot(graph_div,                        dataset,                        options); + +        graph_div.bind("plotclick", function(event, pos, item) { +            var i, pb, url, name; +            if (item) { +                i = item.dataIndex; +                pb = graph_data.public_bodies[i]; +                url = pb.url; +                name = pb.name; +                window.location.href = url; +            } +        }); + +        /* This code is adapted from: +           http://www.flotcharts.org/flot/examples/interacting/ */ + +        function showTooltip(x, y, contents) { +            $('<div id="flot-tooltip">' + contents + '</div>').css({ +                'position': 'absolute', +                'display': 'none', +                'top': y + 10, +                'left': x + 10, +                'border': '1px solid #fdd', +                'padding': '2px', +                'background-color': '#fee', +                'opacity': 0.80 +            }).appendTo("body").fadeIn(200); +        } + +        graph_div.bind("plothover", function (event, pos, item) { +            var escapedName, x, y; +            if (item) { +                if (previousPoint != item.dataIndex) { +                    previousPoint = item.dataIndex; +                    $("#flot-tooltip").remove(); +                    escapedName = $('<div />').text( +                        graph_data.tooltips[item.dataIndex]).html(); +                    showTooltip(item.pageX, +                                item.pageY, +                                escapedName); +                } +            } else { +                $("#flot-tooltip").remove(); +                previousPoint = null; +            } +        });      });  });  | 
