Warning, file /office/tellico/xslt/report-templates/jquery.flot.js was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* Javascript plotting library for jQuery, v. 0.5.
0002  *
0003  * Released under the MIT license by IOLA, December 2007.
0004  *
0005  */
0006 
0007 (function($) {
0008     function Plot(target, data_, options_, plugins) {
0009         // data is on the form:
0010         //   [ series1, series2 ... ]
0011         // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
0012         // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
0013         
0014         var series = [],
0015             options = {
0016                 // the color theme used for graphs
0017                 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
0018                 legend: {
0019                     show: true,
0020                     noColumns: 1, // number of colums in legend table
0021                     labelFormatter: null, // fn: string -> string
0022                     labelBoxBorderColor: "#ccc", // border color for the little label boxes
0023                     container: null, // container (as jQuery object) to put legend in, null means default on top of graph
0024                     position: "ne", // position of default legend container within plot
0025                     margin: 5, // distance from grid edge to default legend container within plot
0026                     backgroundColor: null, // null means auto-detect
0027                     backgroundOpacity: 0.85 // set to 0 to avoid background
0028                 },
0029                 xaxis: {
0030                     mode: null, // null or "time"
0031                     min: null, // min. value to show, null means set automatically
0032                     max: null, // max. value to show, null means set automatically
0033                     autoscaleMargin: null, // margin in % to add if auto-setting min/max
0034                     ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
0035                     tickFormatter: null, // fn: number -> string
0036                     labelWidth: null, // size of tick labels in pixels
0037                     labelHeight: null,
0038                     
0039                     // mode specific options
0040                     tickDecimals: null, // no. of decimals, null means auto
0041                     tickSize: null, // number or [number, "unit"]
0042                     minTickSize: null, // number or [number, "unit"]
0043                     monthNames: null, // list of names of months
0044                     timeformat: null // format string to use
0045                 },
0046                 yaxis: {
0047                     autoscaleMargin: 0.02
0048                 },
0049                 x2axis: {
0050                     autoscaleMargin: null
0051                 },
0052                 y2axis: {
0053                     autoscaleMargin: 0.02
0054                 },
0055                 series: {
0056                     points: {
0057                         show: false,
0058                         radius: 3,
0059                         lineWidth: 2, // in pixels
0060                         fill: true,
0061                         fillColor: "#ffffff"
0062                     },
0063                     lines: {
0064                         // we don't put in show: false so we can see
0065                         // whether lines were actively disabled 
0066                         lineWidth: 2, // in pixels
0067                         fill: false,
0068                         fillColor: null,
0069                         steps: false
0070                     },
0071                     bars: {
0072                         show: false,
0073                         lineWidth: 2, // in pixels
0074                         barWidth: 1, // in units of the x axis
0075                         fill: true,
0076                         fillColor: null,
0077                         align: "left", // or "center" 
0078                         horizontal: false // when horizontal, left is now top
0079                     },
0080                     shadowSize: 3
0081                 },
0082                 grid: {
0083                     show: true,
0084                     color: "#545454", // primary color used for outline and labels
0085                     backgroundColor: null, // null for transparent, else color
0086                     tickColor: "#dddddd", // color used for the ticks
0087                     labelMargin: 5, // in pixels
0088                     borderWidth: 2, // in pixels
0089                     borderColor: null, // set if different from the grid color
0090                     markings: null, // array of ranges or fn: axes -> array of ranges
0091                     markingsColor: "#f4f4f4",
0092                     markingsLineWidth: 2,
0093                     // interactive stuff
0094                     clickable: false,
0095                     hoverable: false,
0096                     autoHighlight: true, // highlight in case mouse is near
0097                     mouseActiveRadius: 10 // how far the mouse can be away to activate an item
0098                 },
0099                 selection: {
0100                     mode: null, // one of null, "x", "y" or "xy"
0101                     color: "#e8cfac"
0102                 }
0103             },
0104         canvas = null,      // the canvas for the plot itself
0105         overlay = null,     // canvas for interactive stuff on top of plot
0106         eventHolder = null, // jQuery object that events should be bound to
0107         ctx = null, octx = null,
0108         axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
0109         plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
0110         canvasWidth = 0, canvasHeight = 0,
0111         plotWidth = 0, plotHeight = 0,
0112         hooks = {
0113             processOptions: [],
0114             processRawData: [],
0115             processDatapoints: [],
0116             draw: [],
0117             bindEvents: [],
0118             drawOverlay: []
0119         },
0120         plot = this,
0121         // dedicated to storing data for buggy standard compliance cases
0122         workarounds = {};
0123 
0124         // public functions
0125         plot.setData = setData;
0126         plot.setupGrid = setupGrid;
0127         plot.draw = draw;
0128         plot.clearSelection = clearSelection;
0129         plot.setSelection = setSelection;
0130         plot.getSelection = getSelection;
0131         plot.getCanvas = function() { return canvas; };
0132         plot.getPlotOffset = function() { return plotOffset; };
0133         plot.width = function () { return plotWidth; };
0134         plot.height = function () { return plotHeight; };
0135         plot.offset = function () {
0136             var o = eventHolder.offset();
0137             o.left += plotOffset.left;
0138             o.top += plotOffset.top;
0139             return o;
0140         };
0141         plot.getData = function() { return series; };
0142         plot.getAxes = function() { return axes; };
0143         plot.getOptions = function() { return options; };
0144         plot.highlight = highlight;
0145         plot.unhighlight = unhighlight;
0146         plot.triggerRedrawOverlay = triggerRedrawOverlay;
0147         plot.pointOffset = function(point) {
0148             return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
0149                      top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
0150         };
0151         
0152 
0153         // public attributes
0154         plot.hooks = hooks;
0155         
0156         // initialize
0157         initPlugins(plot);
0158         parseOptions(options_);
0159         constructCanvas();
0160         setData(data_);
0161         setupGrid();
0162         draw();
0163         bindEvents();
0164 
0165 
0166         function executeHooks(hook, args) {
0167             args = [plot].concat(args);
0168             for (var i = 0; i < hook.length; ++i)
0169                 hook[i].apply(this, args);
0170         }
0171 
0172         function initPlugins() {
0173             for (var i = 0; i < plugins.length; ++i) {
0174                 var p = plugins[i];
0175                 p.init(plot);
0176                 if (p.options)
0177                     $.extend(true, options, p.options);
0178             }
0179         }
0180         
0181         function parseOptions(opts) {
0182             $.extend(true, options, opts);
0183             if (options.grid.borderColor == null)
0184                 options.grid.borderColor = options.grid.color;
0185             // backwards compatibility, to be removed in future
0186             if (options.xaxis.noTicks && options.xaxis.ticks == null)
0187                 options.xaxis.ticks = options.xaxis.noTicks;
0188             if (options.yaxis.noTicks && options.yaxis.ticks == null)
0189                 options.yaxis.ticks = options.yaxis.noTicks;
0190             if (options.grid.coloredAreas)
0191                 options.grid.markings = options.grid.coloredAreas;
0192             if (options.grid.coloredAreasColor)
0193                 options.grid.markingsColor = options.grid.coloredAreasColor;
0194             if (options.lines)
0195                 $.extend(true, options.series.lines, options.lines);
0196             if (options.points)
0197                 $.extend(true, options.series.points, options.points);
0198             if (options.bars)
0199                 $.extend(true, options.series.bars, options.bars);
0200             if (options.shadowSize)
0201                 options.series.shadowSize = options.shadowSize;
0202 
0203             executeHooks(hooks.processOptions, [options]);
0204         }
0205 
0206         function setData(d) {
0207             series = parseData(d);
0208             fillInSeriesOptions();
0209             processData();
0210         }
0211         
0212         function parseData(d) {
0213             var res = [];
0214             for (var i = 0; i < d.length; ++i) {
0215                 var s = $.extend(true, {}, options.series);
0216 
0217                 if (d[i].data) {
0218                     s.data = d[i].data; // move the data instead of deep-copy
0219                     delete d[i].data;
0220 
0221                     $.extend(true, s, d[i]);
0222 
0223                     d[i].data = s.data;
0224                 }
0225                 else
0226                     s.data = d[i];
0227                 res.push(s);
0228             }
0229 
0230             return res;
0231         }
0232         
0233         function axisSpecToRealAxis(obj, attr) {
0234             var a = obj[attr];
0235             if (!a || a == 1)
0236                 return axes[attr];
0237             if (typeof a == "number")
0238                 return axes[attr.charAt(0) + a + attr.slice(1)];
0239             return a; // assume it's OK
0240         }
0241         
0242         function fillInSeriesOptions() {
0243             var i;
0244             
0245             // collect what we already got of colors
0246             var neededColors = series.length,
0247                 usedColors = [],
0248                 assignedColors = [];
0249             for (i = 0; i < series.length; ++i) {
0250                 var sc = series[i].color;
0251                 if (sc != null) {
0252                     --neededColors;
0253                     if (typeof sc == "number")
0254                         assignedColors.push(sc);
0255                     else
0256                         usedColors.push(parseColor(series[i].color));
0257                 }
0258             }
0259             
0260             // we might need to generate more colors if higher indices
0261             // are assigned
0262             for (i = 0; i < assignedColors.length; ++i) {
0263                 neededColors = Math.max(neededColors, assignedColors[i] + 1);
0264             }
0265 
0266             // produce colors as needed
0267             var colors = [], variation = 0;
0268             i = 0;
0269             while (colors.length < neededColors) {
0270                 var c;
0271                 if (options.colors.length == i) // check degenerate case
0272                     c = new Color(100, 100, 100);
0273                 else
0274                     c = parseColor(options.colors[i]);
0275 
0276                 // vary color if needed
0277                 var sign = variation % 2 == 1 ? -1 : 1;
0278                 var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
0279                 c.scale(factor, factor, factor);
0280 
0281                 // FIXME: if we're getting to close to something else,
0282                 // we should probably skip this one
0283                 colors.push(c);
0284                 
0285                 ++i;
0286                 if (i >= options.colors.length) {
0287                     i = 0;
0288                     ++variation;
0289                 }
0290             }
0291 
0292             // fill in the options
0293             var colori = 0, s;
0294             for (i = 0; i < series.length; ++i) {
0295                 s = series[i];
0296                 
0297                 // assign colors
0298                 if (s.color == null) {
0299                     s.color = colors[colori].toString();
0300                     ++colori;
0301                 }
0302                 else if (typeof s.color == "number")
0303                     s.color = colors[s.color].toString();
0304 
0305                 // turn on lines automatically in case nothing is set
0306                 if (s.lines.show == null && !s.bars.show && !s.points.show)
0307                     s.lines.show = true;
0308 
0309                 // setup axes
0310                 s.xaxis = axisSpecToRealAxis(s, "xaxis");
0311                 s.yaxis = axisSpecToRealAxis(s, "yaxis");
0312             }
0313         }
0314         
0315         function processData() {
0316             var topSentry = Number.POSITIVE_INFINITY,
0317                 bottomSentry = Number.NEGATIVE_INFINITY,
0318                 i, j, k, m, length,
0319                 s, points, ps, x, y, axis;
0320 
0321             for (axis in axes) {
0322                 axes[axis].datamin = topSentry;
0323                 axes[axis].datamax = bottomSentry;
0324                 axes[axis].min = options[axis].min;
0325                 axes[axis].max = options[axis].max;
0326                 axes[axis].used = false;
0327             }
0328 
0329             function updateAxis(axis, min, max) {
0330                 if (min < axis.datamin)
0331                     axis.datamin = min;
0332                 if (max > axis.datamax)
0333                     axis.datamax = max;
0334             }
0335 
0336             for (i = 0; i < series.length; ++i) {
0337                 s = series[i];
0338                 s.datapoints = { points: [] };
0339                 
0340                 executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
0341             }
0342             
0343             // first pass: clean and copy data
0344             for (i = 0; i < series.length; ++i) {
0345                 s = series[i];
0346 
0347                 if (s.datapoints.pointsize != null)
0348                     continue; // already filled in
0349 
0350                 var data = s.data, format = [], p;
0351 
0352                 // determine the point size
0353                 if (s.bars.show) {
0354                     s.datapoints.pointsize = 3;
0355                     format.push({ d: 0 });
0356                 }
0357                 else
0358                     s.datapoints.pointsize = 2;
0359                 
0360                 /*
0361                 // examine data to find out how to copy
0362                 for (j = 0; j < data.length; ++j) {
0363                 }*/
0364                 
0365                 ps = s.datapoints.pointsize;
0366                 points = s.datapoints.points;
0367 
0368                 insertSteps = s.lines.show && s.lines.steps;
0369                 s.xaxis.used = s.yaxis.used = true;
0370                 
0371                 for (j = k = 0; j < data.length; ++j, k += ps) {
0372                     p = data[j];
0373 
0374                     if (p != null) {
0375                         x = p[0];
0376                         y = p[1];
0377                     }
0378                     else
0379                         y = x = null;
0380 
0381                     if (x != null) {
0382                         x = +x; // convert to number
0383                         if (isNaN(x))
0384                             x = null;
0385                     }
0386 
0387                     if (y != null) {
0388                         y = +y; // convert to number
0389                         if (isNaN(y))
0390                             y = null;
0391                     }
0392 
0393                     // check validity of point, making sure both are cleared
0394                     if (x == null && y != null) {
0395                         // extract min/max info before we whack
0396                         updateAxis(s.yaxis, y, y);
0397                         y = null;
0398                     }
0399 
0400                     if (y == null && x != null) {
0401                         updateAxis(s.xaxis, x, x);
0402                         x = null;
0403                     }
0404                     
0405                     if (insertSteps && x != null && k > 0
0406                         && points[k - ps] != null
0407                         && points[k - ps] != x && points[k - ps + 1] != y) {
0408                         points[k + 1] = points[k - ps + 1];
0409                         points[k] = x;
0410                             
0411                         // copy the remainding from real point
0412                         for (m = 2; m < ps; ++m)
0413                             points[k + m] = p[m] == null ? format[m-2].d : p[m];
0414                         k += ps;
0415                     }
0416 
0417                     for (m = 2; m < ps; ++m)
0418                         points[k + m] = p == null || p[m] == null ? format[m-2].d : p[m];
0419                     
0420                     points[k] = x;
0421                     points[k + 1] = y;
0422                 }
0423             }
0424 
0425             for (i = 0; i < series.length; ++i) {
0426                 s = series[i];
0427                 
0428                 executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
0429             }
0430 
0431             // second pass: find datamax/datamin for auto-scaling
0432             for (i = 0; i < series.length; ++i) {
0433                 s = series[i];
0434                 points = s.datapoints.points,
0435                 ps = s.datapoints.pointsize;
0436 
0437                 var xmin = topSentry, ymin = topSentry,
0438                     xmax = bottomSentry, ymax = bottomSentry;
0439                 
0440                 for (j = 0; j < points.length; j += ps) {
0441                     x = points[j];
0442 
0443                     if (x == null)
0444                         continue;
0445 
0446                     if (x < xmin)
0447                         xmin = x;
0448                     if (x > xmax)
0449                         xmax = x;
0450 
0451                     y = points[j + 1];
0452 
0453                     if (y < ymin)
0454                         ymin = y;
0455                     if (y > ymax)
0456                         ymax = y;
0457                 }
0458                 
0459                 if (s.bars.show) {
0460                     // make sure we got room for the bar on the dancing floor
0461                     var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
0462                     if (s.bars.horizontal) {
0463                         ymin += delta;
0464                         ymax += delta + s.bars.barWidth;
0465                     }
0466                     else {
0467                         xmin += delta;
0468                         xmax += delta + s.bars.barWidth;
0469                     }
0470                 }
0471                 
0472                 updateAxis(s.xaxis, xmin, xmax);
0473                 updateAxis(s.yaxis, ymin, ymax);
0474             }
0475 
0476             for (axis in axes) {
0477                 if (axes[axis].datamin == topSentry)
0478                     axes[axis].datamin = null;
0479                 if (axes[axis].datamax == bottomSentry)
0480                     axes[axis].datamax = null;
0481             }
0482         }
0483 
0484         function constructCanvas() {
0485             function makeCanvas(width, height) {
0486                 var c = document.createElement('canvas');
0487                 c.width = width;
0488                 c.height = height;
0489                 if ($.browser.msie) // excanvas hack
0490                     c = window.G_vmlCanvasManager.initElement(c);
0491                 return c;
0492             }
0493             
0494             canvasWidth = target.width();
0495             canvasHeight = target.height();
0496             target.html(""); // clear target
0497             if (target.css("position") == 'static')
0498                 target.css("position", "relative"); // for positioning labels and overlay
0499 
0500             if (canvasWidth <= 0 || canvasHeight <= 0)
0501                 throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
0502 
0503             if ($.browser.msie) // excanvas hack
0504                 window.G_vmlCanvasManager.init_(document); // make sure everything is setup
0505             
0506             // the canvas
0507             canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(target).get(0);
0508             ctx = canvas.getContext("2d");
0509 
0510             // overlay canvas for interactive features
0511             overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(target).get(0);
0512             octx = overlay.getContext("2d");
0513             octx.stroke();
0514         }
0515 
0516         function bindEvents() {
0517             // we include the canvas in the event holder too, because IE 7
0518             // sometimes has trouble with the stacking order
0519             eventHolder = $([overlay, canvas]);
0520 
0521             // bind events
0522             if (options.selection.mode != null
0523                 || options.grid.hoverable)
0524                 eventHolder.mousemove(onMouseMove);
0525 
0526             if (options.selection.mode != null)
0527                 eventHolder.mousedown(onMouseDown);
0528 
0529             if (options.grid.clickable)
0530                 eventHolder.click(onClick);
0531 
0532             executeHooks(hooks.bindEvents, [eventHolder]);
0533         }
0534 
0535         function setupGrid() {
0536             function setTransformationHelpers(axis) {
0537                 var s, m;
0538                     
0539                 // add transformation helpers
0540                 if (axis == axes.xaxis || axis == axes.x2axis) {
0541                     // precompute how much the axis is scaling a point
0542                     // in canvas space
0543                     s = axis.scale = plotWidth / (axis.max - axis.min);
0544                     m = axis.min;
0545                     
0546                     // data point to canvas coordinate
0547                     axis.p2c = function (p) { return (p - m) * s; };
0548                     // canvas coordinate to data point 
0549                     axis.c2p = function (c) { return m + c / s; };
0550                 }
0551                 else {
0552                     s = axis.scale = plotHeight / (axis.max - axis.min);
0553                     m = axis.max;
0554                     
0555                     axis.p2c = function (p) { return (m - p) * s; };
0556                     axis.c2p = function (p) { return m - p / s; };
0557                 }
0558             }
0559 
0560             var axis;
0561             for (axis in axes)
0562                 setRange(axes[axis], options[axis]);
0563             
0564             if (options.grid.show) {
0565                 for (axis in axes) {
0566                     prepareTickGeneration(axes[axis], options[axis]);
0567                     setTicks(axes[axis], options[axis]);
0568                 }                    
0569 
0570                 setGridSpacing();
0571             }
0572             else {
0573                 plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
0574                 plotWidth = canvasWidth;
0575                 plotHeight = canvasHeight;
0576             }
0577             
0578             for (axis in axes)
0579                 setTransformationHelpers(axes[axis]);
0580 
0581             if (options.grid.show)
0582                 insertLabels();
0583             
0584             insertLegend();
0585         }
0586         
0587         function setRange(axis, axisOptions) {
0588             var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin),
0589                 max = +(axisOptions.max != null ? axisOptions.max : axis.datamax);
0590 
0591             if (max - min == 0.0) {
0592                 // degenerate case
0593                 var widen = max == 0 ? 1 : 0.01;
0594 
0595                 if (axisOptions.min == null)
0596                     min -= widen;
0597                 // alway widen max if we couldn't widen min to ensure we
0598                 // don't fall into min == max which doesn't work
0599                 if (axisOptions.max == null || axisOptions.min != null)
0600                     max += widen;
0601             }
0602             else {
0603                 // consider autoscaling
0604                 var margin = axisOptions.autoscaleMargin;
0605                 if (margin != null) {
0606                     if (axisOptions.min == null) {
0607                         min -= (max - min) * margin;
0608                         // make sure we don't go below zero if all values
0609                         // are positive
0610                         if (min < 0 && axis.datamin != null && axis.datamin >= 0)
0611                             min = 0;
0612                     }
0613                     if (axisOptions.max == null) {
0614                         max += (max - min) * margin;
0615                         if (max > 0 && axis.datamax != null && axis.datamax <= 0)
0616                             max = 0;
0617                     }
0618                 }
0619             }
0620             axis.min = min;
0621             axis.max = max;
0622         }
0623 
0624         function prepareTickGeneration(axis, axisOptions) {
0625             // estimate number of ticks
0626             var noTicks;
0627             if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
0628                 noTicks = axisOptions.ticks;
0629             else if (axis == axes.xaxis || axis == axes.x2axis)
0630                 noTicks = canvasWidth / 100;
0631             else
0632                 noTicks = canvasHeight / 60;
0633             
0634             var delta = (axis.max - axis.min) / noTicks;
0635             var size, generator, unit, formatter, i, magn, norm;
0636 
0637             if (axisOptions.mode == "time") {
0638                 // pretty handling of time
0639                 
0640                 // map of app. size of time units in milliseconds
0641                 var timeUnitSize = {
0642                     "second": 1000,
0643                     "minute": 60 * 1000,
0644                     "hour": 60 * 60 * 1000,
0645                     "day": 24 * 60 * 60 * 1000,
0646                     "month": 30 * 24 * 60 * 60 * 1000,
0647                     "year": 365.2425 * 24 * 60 * 60 * 1000
0648                 };
0649 
0650 
0651                 // the allowed tick sizes, after 1 year we use
0652                 // an integer algorithm
0653                 var spec = [
0654                     [1, "second"], [2, "second"], [5, "second"], [10, "second"],
0655                     [30, "second"], 
0656                     [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
0657                     [30, "minute"], 
0658                     [1, "hour"], [2, "hour"], [4, "hour"],
0659                     [8, "hour"], [12, "hour"],
0660                     [1, "day"], [2, "day"], [3, "day"],
0661                     [0.25, "month"], [0.5, "month"], [1, "month"],
0662                     [2, "month"], [3, "month"], [6, "month"],
0663                     [1, "year"]
0664                 ];
0665 
0666                 var minSize = 0;
0667                 if (axisOptions.minTickSize != null) {
0668                     if (typeof axisOptions.tickSize == "number")
0669                         minSize = axisOptions.tickSize;
0670                     else
0671                         minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
0672                 }
0673 
0674                 for (i = 0; i < spec.length - 1; ++i)
0675                     if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
0676                                  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
0677                        && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
0678                         break;
0679                 size = spec[i][0];
0680                 unit = spec[i][1];
0681                 
0682                 // special-case the possibility of several years
0683                 if (unit == "year") {
0684                     magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
0685                     norm = (delta / timeUnitSize.year) / magn;
0686                     if (norm < 1.5)
0687                         size = 1;
0688                     else if (norm < 3)
0689                         size = 2;
0690                     else if (norm < 7.5)
0691                         size = 5;
0692                     else
0693                         size = 10;
0694 
0695                     size *= magn;
0696                 }
0697 
0698                 if (axisOptions.tickSize) {
0699                     size = axisOptions.tickSize[0];
0700                     unit = axisOptions.tickSize[1];
0701                 }
0702                 
0703                 generator = function(axis) {
0704                     var ticks = [],
0705                         tickSize = axis.tickSize[0], unit = axis.tickSize[1],
0706                         d = new Date(axis.min);
0707                     
0708                     var step = tickSize * timeUnitSize[unit];
0709 
0710                     if (unit == "second")
0711                         d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
0712                     if (unit == "minute")
0713                         d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
0714                     if (unit == "hour")
0715                         d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
0716                     if (unit == "month")
0717                         d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
0718                     if (unit == "year")
0719                         d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
0720                     
0721                     // reset smaller components
0722                     d.setUTCMilliseconds(0);
0723                     if (step >= timeUnitSize.minute)
0724                         d.setUTCSeconds(0);
0725                     if (step >= timeUnitSize.hour)
0726                         d.setUTCMinutes(0);
0727                     if (step >= timeUnitSize.day)
0728                         d.setUTCHours(0);
0729                     if (step >= timeUnitSize.day * 4)
0730                         d.setUTCDate(1);
0731                     if (step >= timeUnitSize.year)
0732                         d.setUTCMonth(0);
0733 
0734 
0735                     var carry = 0, v = Number.NaN, prev;
0736                     do {
0737                         prev = v;
0738                         v = d.getTime();
0739                         ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
0740                         if (unit == "month") {
0741                             if (tickSize < 1) {
0742                                 // a bit complicated - we'll divide the month
0743                                 // up but we need to take care of fractions
0744                                 // so we don't end up in the middle of a day
0745                                 d.setUTCDate(1);
0746                                 var start = d.getTime();
0747                                 d.setUTCMonth(d.getUTCMonth() + 1);
0748                                 var end = d.getTime();
0749                                 d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
0750                                 carry = d.getUTCHours();
0751                                 d.setUTCHours(0);
0752                             }
0753                             else
0754                                 d.setUTCMonth(d.getUTCMonth() + tickSize);
0755                         }
0756                         else if (unit == "year") {
0757                             d.setUTCFullYear(d.getUTCFullYear() + tickSize);
0758                         }
0759                         else
0760                             d.setTime(v + step);
0761                     } while (v < axis.max && v != prev);
0762 
0763                     return ticks;
0764                 };
0765 
0766                 formatter = function (v, axis) {
0767                     var d = new Date(v);
0768 
0769                     // first check global format
0770                     if (axisOptions.timeformat != null)
0771                         return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
0772                     
0773                     var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
0774                     var span = axis.max - axis.min;
0775                     
0776                     if (t < timeUnitSize.minute)
0777                         fmt = "%h:%M:%S";
0778                     else if (t < timeUnitSize.day) {
0779                         if (span < 2 * timeUnitSize.day)
0780                             fmt = "%h:%M";
0781                         else
0782                             fmt = "%b %d %h:%M";
0783                     }
0784                     else if (t < timeUnitSize.month)
0785                         fmt = "%b %d";
0786                     else if (t < timeUnitSize.year) {
0787                         if (span < timeUnitSize.year)
0788                             fmt = "%b";
0789                         else
0790                             fmt = "%b %y";
0791                     }
0792                     else
0793                         fmt = "%y";
0794                     
0795                     return $.plot.formatDate(d, fmt, axisOptions.monthNames);
0796                 };
0797             }
0798             else {
0799                 // pretty rounding of base-10 numbers
0800                 var maxDec = axisOptions.tickDecimals;
0801                 var dec = -Math.floor(Math.log(delta) / Math.LN10);
0802                 if (maxDec != null && dec > maxDec)
0803                     dec = maxDec;
0804                 
0805                 magn = Math.pow(10, -dec);
0806                 norm = delta / magn; // norm is between 1.0 and 10.0
0807                 
0808                 if (norm < 1.5)
0809                     size = 1;
0810                 else if (norm < 3) {
0811                     size = 2;
0812                     // special case for 2.5, requires an extra decimal
0813                     if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
0814                         size = 2.5;
0815                         ++dec;
0816                     }
0817                 }
0818                 else if (norm < 7.5)
0819                     size = 5;
0820                 else
0821                     size = 10;
0822 
0823                 size *= magn;
0824                 
0825                 if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
0826                     size = axisOptions.minTickSize;
0827 
0828                 if (axisOptions.tickSize != null)
0829                     size = axisOptions.tickSize;
0830                 
0831                 axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
0832                 
0833                 generator = function (axis) {
0834                     var ticks = [];
0835 
0836                     // spew out all possible ticks
0837                     var start = floorInBase(axis.min, axis.tickSize),
0838                         i = 0, v = Number.NaN, prev;
0839                     do {
0840                         prev = v;
0841                         v = start + i * axis.tickSize;
0842                         ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
0843                         ++i;
0844                     } while (v < axis.max && v != prev);
0845                     return ticks;
0846                 };
0847 
0848                 formatter = function (v, axis) {
0849                     return v.toFixed(axis.tickDecimals);
0850                 };
0851             }
0852 
0853             axis.tickSize = unit ? [size, unit] : size;
0854             axis.tickGenerator = generator;
0855             if ($.isFunction(axisOptions.tickFormatter))
0856                 axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
0857             else
0858                 axis.tickFormatter = formatter;
0859             if (axisOptions.labelWidth != null)
0860                 axis.labelWidth = axisOptions.labelWidth;
0861             if (axisOptions.labelHeight != null)
0862                 axis.labelHeight = axisOptions.labelHeight;
0863         }
0864         
0865         function setTicks(axis, axisOptions) {
0866             axis.ticks = [];
0867 
0868             if (!axis.used)
0869                 return;
0870             
0871             if (axisOptions.ticks == null)
0872                 axis.ticks = axis.tickGenerator(axis);
0873             else if (typeof axisOptions.ticks == "number") {
0874                 if (axisOptions.ticks > 0)
0875                     axis.ticks = axis.tickGenerator(axis);
0876             }
0877             else if (axisOptions.ticks) {
0878                 var ticks = axisOptions.ticks;
0879 
0880                 if ($.isFunction(ticks))
0881                     // generate the ticks
0882                     ticks = ticks({ min: axis.min, max: axis.max });
0883                 
0884                 // clean up the user-supplied ticks, copy them over
0885                 var i, v;
0886                 for (i = 0; i < ticks.length; ++i) {
0887                     var label = null;
0888                     var t = ticks[i];
0889                     if (typeof t == "object") {
0890                         v = t[0];
0891                         if (t.length > 1)
0892                             label = t[1];
0893                     }
0894                     else
0895                         v = t;
0896                     if (label == null)
0897                         label = axis.tickFormatter(v, axis);
0898                     axis.ticks[i] = { v: v, label: label };
0899                 }
0900             }
0901 
0902             if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
0903                 // snap to ticks
0904                 if (axisOptions.min == null)
0905                     axis.min = Math.min(axis.min, axis.ticks[0].v);
0906                 if (axisOptions.max == null && axis.ticks.length > 1)
0907                     axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v);
0908             }
0909         }
0910         
0911         function setGridSpacing() {
0912             function measureXLabels(axis) {
0913                 // to avoid measuring the widths of the labels, we
0914                 // construct fixed-size boxes and put the labels inside
0915                 // them, we don't need the exact figures and the
0916                 // fixed-size box content is easy to center
0917                 if (axis.labelWidth == null)
0918                     axis.labelWidth = canvasWidth / 6;
0919 
0920                 // measure x label heights
0921                 if (axis.labelHeight == null) {
0922                     labels = [];
0923                     for (i = 0; i < axis.ticks.length; ++i) {
0924                         l = axis.ticks[i].label;
0925                         if (l)
0926                             labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
0927                     }
0928                     
0929                     axis.labelHeight = 0;
0930                     if (labels.length > 0) {
0931                         var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
0932                                          + labels.join("") + '<div style="clear:left"></div></div>').appendTo(target);
0933                         axis.labelHeight = dummyDiv.height();
0934                         dummyDiv.remove();
0935                     }
0936                 }
0937             }
0938             
0939             function measureYLabels(axis) {
0940                 if (axis.labelWidth == null || axis.labelHeight == null) {
0941                     var i, labels = [], l;
0942                     // calculate y label dimensions
0943                     for (i = 0; i < axis.ticks.length; ++i) {
0944                         l = axis.ticks[i].label;
0945                         if (l)
0946                             labels.push('<div class="tickLabel">' + l + '</div>');
0947                     }
0948                     
0949                     if (labels.length > 0) {
0950                         var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">'
0951                                          + labels.join("") + '</div>').appendTo(target);
0952                         if (axis.labelWidth == null)
0953                             axis.labelWidth = dummyDiv.width();
0954                         if (axis.labelHeight == null)
0955                             axis.labelHeight = dummyDiv.find("div").height();
0956                         dummyDiv.remove();
0957                     }
0958                     
0959                     if (axis.labelWidth == null)
0960                         axis.labelWidth = 0;
0961                     if (axis.labelHeight == null)
0962                         axis.labelHeight = 0;
0963                 }
0964             }
0965 
0966             // get the most space needed around the grid for things
0967             // that may stick out
0968             var maxOutset = options.grid.borderWidth;
0969             for (i = 0; i < series.length; ++i)
0970                 maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
0971             
0972             plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
0973             
0974             var margin = options.grid.labelMargin + options.grid.borderWidth;
0975             
0976             measureXLabels(axes.xaxis);
0977             measureYLabels(axes.yaxis);
0978             measureXLabels(axes.x2axis);
0979             measureYLabels(axes.y2axis);
0980             
0981             if (axes.xaxis.labelHeight > 0)
0982                 plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
0983             if (axes.yaxis.labelWidth > 0)
0984                     plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
0985             
0986             if (axes.x2axis.labelHeight > 0)
0987                 plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
0988             
0989             if (axes.y2axis.labelWidth > 0)
0990                 plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
0991             
0992             plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
0993             plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
0994         }
0995         
0996         function draw() {
0997             if (options.grid.show)
0998                 drawGrid();
0999 
1000             for (var i = 0; i < series.length; ++i)
1001                 drawSeries(series[i]);
1002 
1003             executeHooks(hooks.draw, [ctx]);
1004         }
1005 
1006         function extractRange(ranges, coord) {
1007             var firstAxis = coord + "axis",
1008                 secondaryAxis = coord + "2axis",
1009                 axis, from, to, reverse;
1010 
1011             if (ranges[firstAxis]) {
1012                 axis = axes[firstAxis];
1013                 from = ranges[firstAxis].from;
1014                 to = ranges[firstAxis].to;
1015             }
1016             else if (ranges[secondaryAxis]) {
1017                 axis = axes[secondaryAxis];
1018                 from = ranges[secondaryAxis].from;
1019                 to = ranges[secondaryAxis].to;
1020             }
1021             else {
1022                 // backwards-compat stuff - to be removed in future
1023                 axis = axes[firstAxis];
1024                 from = ranges[coord + "1"];
1025                 to = ranges[coord + "2"];
1026             }
1027 
1028             // auto-reverse as an added bonus
1029             if (from != null && to != null && from > to)
1030                 return { from: to, to: from, axis: axis };
1031             
1032             return { from: from, to: to, axis: axis };
1033         }
1034         
1035         function drawGrid() {
1036             var i;
1037             
1038             ctx.save();
1039             ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1040             ctx.translate(plotOffset.left, plotOffset.top);
1041 
1042             // draw background, if any
1043             if (options.grid.backgroundColor) {
1044                 ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1045                 ctx.fillRect(0, 0, plotWidth, plotHeight);
1046             }
1047 
1048             // draw markings
1049             var markings = options.grid.markings;
1050             if (markings) {
1051                 if ($.isFunction(markings))
1052                     // xmin etc. are backwards-compatible, to be removed in future
1053                     markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
1054 
1055                 for (i = 0; i < markings.length; ++i) {
1056                     var m = markings[i],
1057                         xrange = extractRange(m, "x"),
1058                         yrange = extractRange(m, "y");
1059 
1060                     // fill in missing
1061                     if (xrange.from == null)
1062                         xrange.from = xrange.axis.min;
1063                     if (xrange.to == null)
1064                         xrange.to = xrange.axis.max;
1065                     if (yrange.from == null)
1066                         yrange.from = yrange.axis.min;
1067                     if (yrange.to == null)
1068                         yrange.to = yrange.axis.max;
1069 
1070                     // clip
1071                     if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1072                         yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1073                         continue;
1074 
1075                     xrange.from = Math.max(xrange.from, xrange.axis.min);
1076                     xrange.to = Math.min(xrange.to, xrange.axis.max);
1077                     yrange.from = Math.max(yrange.from, yrange.axis.min);
1078                     yrange.to = Math.min(yrange.to, yrange.axis.max);
1079 
1080                     if (xrange.from == xrange.to && yrange.from == yrange.to)
1081                         continue;
1082 
1083                     // then draw
1084                     xrange.from = xrange.axis.p2c(xrange.from);
1085                     xrange.to = xrange.axis.p2c(xrange.to);
1086                     yrange.from = yrange.axis.p2c(yrange.from);
1087                     yrange.to = yrange.axis.p2c(yrange.to);
1088                     
1089                     if (xrange.from == xrange.to || yrange.from == yrange.to) {
1090                         // draw line
1091                         ctx.strokeStyle = m.color || options.grid.markingsColor;
1092                         ctx.beginPath();
1093                         ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1094                         //ctx.moveTo(Math.floor(xrange.from), yrange.from);
1095                         //ctx.lineTo(Math.floor(xrange.to), yrange.to);
1096                         ctx.moveTo(xrange.from, yrange.from);
1097                         ctx.lineTo(xrange.to, yrange.to);
1098                         ctx.stroke();
1099                     }
1100                     else {
1101                         // fill area
1102                         ctx.fillStyle = m.color || options.grid.markingsColor;
1103                         ctx.fillRect(xrange.from, yrange.to,
1104                                      xrange.to - xrange.from,
1105                                      yrange.from - yrange.to);
1106                     }
1107                 }
1108             }
1109             
1110             // draw the inner grid
1111             ctx.lineWidth = 1;
1112             ctx.strokeStyle = options.grid.tickColor;
1113             ctx.beginPath();
1114             var v, axis = axes.xaxis;
1115             for (i = 0; i < axis.ticks.length; ++i) {
1116                 v = axis.ticks[i].v;
1117                 if (v <= axis.min || v >= axes.xaxis.max)
1118                     continue;   // skip those lying on the axes
1119 
1120                 ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
1121                 ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
1122             }
1123 
1124             axis = axes.yaxis;
1125             for (i = 0; i < axis.ticks.length; ++i) {
1126                 v = axis.ticks[i].v;
1127                 if (v <= axis.min || v >= axis.max)
1128                     continue;
1129 
1130                 ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1131                 ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1132             }
1133 
1134             axis = axes.x2axis;
1135             for (i = 0; i < axis.ticks.length; ++i) {
1136                 v = axis.ticks[i].v;
1137                 if (v <= axis.min || v >= axis.max)
1138                     continue;
1139     
1140                 ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
1141                 ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
1142             }
1143 
1144             axis = axes.y2axis;
1145             for (i = 0; i < axis.ticks.length; ++i) {
1146                 v = axis.ticks[i].v;
1147                 if (v <= axis.min || v >= axis.max)
1148                     continue;
1149 
1150                 ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1151                 ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1152             }
1153             
1154             ctx.stroke();
1155             
1156             if (options.grid.borderWidth) {
1157                 // draw border
1158                 var bw = options.grid.borderWidth;
1159                 ctx.lineWidth = bw;
1160                 ctx.strokeStyle = options.grid.borderColor;
1161                 ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1162             }
1163 
1164             ctx.restore();
1165         }
1166 
1167         function insertLabels() {
1168             target.find(".tickLabels").remove();
1169             
1170             var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
1171 
1172             function addLabels(axis, labelGenerator) {
1173                 for (var i = 0; i < axis.ticks.length; ++i) {
1174                     var tick = axis.ticks[i];
1175                     if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1176                         continue;
1177                     html.push(labelGenerator(tick, axis));
1178                 }
1179             }
1180 
1181             var margin = options.grid.labelMargin + options.grid.borderWidth;
1182             
1183             addLabels(axes.xaxis, function (tick, axis) {
1184                 return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
1185             });
1186             
1187             
1188             addLabels(axes.yaxis, function (tick, axis) {
1189                 return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
1190             });
1191             
1192             addLabels(axes.x2axis, function (tick, axis) {
1193                 return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
1194             });
1195             
1196             addLabels(axes.y2axis, function (tick, axis) {
1197                 return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
1198             });
1199 
1200             html.push('</div>');
1201             
1202             target.append(html.join(""));
1203         }
1204 
1205         function drawSeries(series) {
1206             if (series.lines.show)
1207                 drawSeriesLines(series);
1208             if (series.bars.show)
1209                 drawSeriesBars(series);
1210             if (series.points.show)
1211                 drawSeriesPoints(series);
1212         }
1213         
1214         function drawSeriesLines(series) {
1215             function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1216                 var points = datapoints.points,
1217                     ps = datapoints.pointsize,
1218                     prevx = null, prevy = null;
1219                 
1220                 ctx.beginPath();
1221                 for (var i = ps; i < points.length; i += ps) {
1222                     var x1 = points[i - ps], y1 = points[i - ps + 1],
1223                         x2 = points[i], y2 = points[i + 1];
1224                     
1225                     if (x1 == null || x2 == null)
1226                         continue;
1227 
1228                     // clip with ymin
1229                     if (y1 <= y2 && y1 < axisy.min) {
1230                         if (y2 < axisy.min)
1231                             continue;   // line segment is outside
1232                         // compute new intersection point
1233                         x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1234                         y1 = axisy.min;
1235                     }
1236                     else if (y2 <= y1 && y2 < axisy.min) {
1237                         if (y1 < axisy.min)
1238                             continue;
1239                         x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1240                         y2 = axisy.min;
1241                     }
1242 
1243                     // clip with ymax
1244                     if (y1 >= y2 && y1 > axisy.max) {
1245                         if (y2 > axisy.max)
1246                             continue;
1247                         x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1248                         y1 = axisy.max;
1249                     }
1250                     else if (y2 >= y1 && y2 > axisy.max) {
1251                         if (y1 > axisy.max)
1252                             continue;
1253                         x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1254                         y2 = axisy.max;
1255                     }
1256 
1257                     // clip with xmin
1258                     if (x1 <= x2 && x1 < axisx.min) {
1259                         if (x2 < axisx.min)
1260                             continue;
1261                         y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1262                         x1 = axisx.min;
1263                     }
1264                     else if (x2 <= x1 && x2 < axisx.min) {
1265                         if (x1 < axisx.min)
1266                             continue;
1267                         y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1268                         x2 = axisx.min;
1269                     }
1270 
1271                     // clip with xmax
1272                     if (x1 >= x2 && x1 > axisx.max) {
1273                         if (x2 > axisx.max)
1274                             continue;
1275                         y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1276                         x1 = axisx.max;
1277                     }
1278                     else if (x2 >= x1 && x2 > axisx.max) {
1279                         if (x1 > axisx.max)
1280                             continue;
1281                         y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1282                         x2 = axisx.max;
1283                     }
1284 
1285                     if (x1 != prevx || y1 != prevy)
1286                         ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1287                     
1288                     prevx = x2;
1289                     prevy = y2;
1290                     ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1291                 }
1292                 ctx.stroke();
1293             }
1294 
1295             function plotLineArea(datapoints, axisx, axisy) {
1296                 var points = datapoints.points,
1297                     ps = datapoints.pointsize,
1298                     bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1299                     top, lastX = 0, areaOpen = false;
1300                 
1301                 for (var i = ps; i < points.length; i += ps) {
1302                     var x1 = points[i - ps], y1 = points[i - ps + 1],
1303                         x2 = points[i], y2 = points[i + 1];
1304                     
1305                     if (areaOpen && x1 != null && x2 == null) {
1306                         // close area
1307                         ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1308                         ctx.fill();
1309                         areaOpen = false;
1310                         continue;
1311                     }
1312 
1313                     if (x1 == null || x2 == null)
1314                         continue;
1315 
1316                     // clip x values
1317                     
1318                     // clip with xmin
1319                     if (x1 <= x2 && x1 < axisx.min) {
1320                         if (x2 < axisx.min)
1321                             continue;
1322                         y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1323                         x1 = axisx.min;
1324                     }
1325                     else if (x2 <= x1 && x2 < axisx.min) {
1326                         if (x1 < axisx.min)
1327                             continue;
1328                         y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1329                         x2 = axisx.min;
1330                     }
1331 
1332                     // clip with xmax
1333                     if (x1 >= x2 && x1 > axisx.max) {
1334                         if (x2 > axisx.max)
1335                             continue;
1336                         y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1337                         x1 = axisx.max;
1338                     }
1339                     else if (x2 >= x1 && x2 > axisx.max) {
1340                         if (x1 > axisx.max)
1341                             continue;
1342                         y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1343                         x2 = axisx.max;
1344                     }
1345 
1346                     if (!areaOpen) {
1347                         // open area
1348                         ctx.beginPath();
1349                         ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1350                         areaOpen = true;
1351                     }
1352                     
1353                     // now first check the case where both is outside
1354                     if (y1 >= axisy.max && y2 >= axisy.max) {
1355                         ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1356                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1357                         lastX = x2;
1358                         continue;
1359                     }
1360                     else if (y1 <= axisy.min && y2 <= axisy.min) {
1361                         ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1362                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1363                         lastX = x2;
1364                         continue;
1365                     }
1366                     
1367                     // else it's a bit more complicated, there might
1368                     // be two rectangles and two triangles we need to fill
1369                     // in; to find these keep track of the current x values
1370                     var x1old = x1, x2old = x2;
1371 
1372                     // and clip the y values, without shortcutting
1373                     
1374                     // clip with ymin
1375                     if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1376                         x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1377                         y1 = axisy.min;
1378                     }
1379                     else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1380                         x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1381                         y2 = axisy.min;
1382                     }
1383 
1384                     // clip with ymax
1385                     if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1386                         x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1387                         y1 = axisy.max;
1388                     }
1389                     else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1390                         x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1391                         y2 = axisy.max;
1392                     }
1393 
1394 
1395                     // if the x value was changed we got a rectangle
1396                     // to fill
1397                     if (x1 != x1old) {
1398                         if (y1 <= axisy.min)
1399                             top = axisy.min;
1400                         else
1401                             top = axisy.max;
1402                         
1403                         ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
1404                         ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
1405                     }
1406                     
1407                     // fill the triangles
1408                     ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1409                     ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1410 
1411                     // fill the other rectangle if it's there
1412                     if (x2 != x2old) {
1413                         if (y2 <= axisy.min)
1414                             top = axisy.min;
1415                         else
1416                             top = axisy.max;
1417                         
1418                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
1419                         ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
1420                     }
1421 
1422                     lastX = Math.max(x2, x2old);
1423                 }
1424 
1425                 if (areaOpen) {
1426                     ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1427                     ctx.fill();
1428                 }
1429             }
1430             
1431             ctx.save();
1432             ctx.translate(plotOffset.left, plotOffset.top);
1433             ctx.lineJoin = "round";
1434 
1435             var lw = series.lines.lineWidth,
1436                 sw = series.shadowSize;
1437             // FIXME: consider another form of shadow when filling is turned on
1438             if (lw > 0 && sw > 0) {
1439                 // draw shadow as a thick and thin line with transparency
1440                 ctx.lineWidth = sw;
1441                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
1442                 var xoffset = 1;
1443                 plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis);
1444                 ctx.lineWidth = sw/2;
1445                 plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis);
1446             }
1447 
1448             ctx.lineWidth = lw;
1449             ctx.strokeStyle = series.color;
1450             var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1451             if (fillStyle) {
1452                 ctx.fillStyle = fillStyle;
1453                 plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1454             }
1455 
1456             if (lw > 0)
1457                 plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1458             ctx.restore();
1459         }
1460 
1461         function drawSeriesPoints(series) {
1462             function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
1463                 var points = datapoints.points, ps = datapoints.pointsize;
1464                 
1465                 for (var i = 0; i < points.length; i += ps) {
1466                     var x = points[i], y = points[i + 1];
1467                     if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1468                         continue;
1469                     
1470                     ctx.beginPath();
1471                     ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false);
1472                     if (fillStyle) {
1473                         ctx.fillStyle = fillStyle;
1474                         ctx.fill();
1475                     }
1476                     ctx.stroke();
1477                 }
1478             }
1479             
1480             ctx.save();
1481             ctx.translate(plotOffset.left, plotOffset.top);
1482 
1483             var lw = series.lines.lineWidth,
1484                 sw = series.shadowSize,
1485                 radius = series.points.radius;
1486             if (lw > 0 && sw > 0) {
1487                 // draw shadow in two steps
1488                 var w = sw / 2;
1489                 ctx.lineWidth = w;
1490                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
1491                 plotPoints(series.datapoints, radius, null, w + w/2, Math.PI,
1492                            series.xaxis, series.yaxis);
1493 
1494                 ctx.strokeStyle = "rgba(0,0,0,0.2)";
1495                 plotPoints(series.datapoints, radius, null, w/2, Math.PI,
1496                            series.xaxis, series.yaxis);
1497             }
1498 
1499             ctx.lineWidth = lw;
1500             ctx.strokeStyle = series.color;
1501             plotPoints(series.datapoints, radius,
1502                        getFillStyle(series.points, series.color), 0, 2 * Math.PI,
1503                        series.xaxis, series.yaxis);
1504             ctx.restore();
1505         }
1506 
1507         function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
1508             var left, right, bottom, top,
1509                 drawLeft, drawRight, drawTop, drawBottom,
1510                 tmp;
1511 
1512             if (horizontal) {
1513                 drawBottom = drawRight = drawTop = true;
1514                 drawLeft = false;
1515                 left = b;
1516                 right = x;
1517                 top = y + barLeft;
1518                 bottom = y + barRight;
1519 
1520                 // account for negative bars
1521                 if (right < left) {
1522                     tmp = right;
1523                     right = left;
1524                     left = tmp;
1525                     drawLeft = true;
1526                     drawRight = false;
1527                 }
1528             }
1529             else {
1530                 drawLeft = drawRight = drawTop = true;
1531                 drawBottom = false;
1532                 left = x + barLeft;
1533                 right = x + barRight;
1534                 bottom = b;
1535                 top = y;
1536 
1537                 // account for negative bars
1538                 if (top < bottom) {
1539                     tmp = top;
1540                     top = bottom;
1541                     bottom = tmp;
1542                     drawBottom = true;
1543                     drawTop = false;
1544                 }
1545             }
1546            
1547             // clip
1548             if (right < axisx.min || left > axisx.max ||
1549                 top < axisy.min || bottom > axisy.max)
1550                 return;
1551             
1552             if (left < axisx.min) {
1553                 left = axisx.min;
1554                 drawLeft = false;
1555             }
1556 
1557             if (right > axisx.max) {
1558                 right = axisx.max;
1559                 drawRight = false;
1560             }
1561 
1562             if (bottom < axisy.min) {
1563                 bottom = axisy.min;
1564                 drawBottom = false;
1565             }
1566             
1567             if (top > axisy.max) {
1568                 top = axisy.max;
1569                 drawTop = false;
1570             }
1571 
1572             left = axisx.p2c(left);
1573             bottom = axisy.p2c(bottom);
1574             right = axisx.p2c(right);
1575             top = axisy.p2c(top);
1576             
1577             // fill the bar
1578             if (fillStyleCallback) {
1579                 c.beginPath();
1580                 c.moveTo(left, bottom);
1581                 c.lineTo(left, top);
1582                 c.lineTo(right, top);
1583                 c.lineTo(right, bottom);
1584                 c.fillStyle = fillStyleCallback(bottom, top);
1585                 c.fill();
1586             }
1587 
1588             // draw outline
1589             if (drawLeft || drawRight || drawTop || drawBottom) {
1590                 c.beginPath();
1591 
1592                 // FIXME: inline moveTo is buggy with excanvas
1593                 c.moveTo(left, bottom + offset);
1594                 if (drawLeft)
1595                     c.lineTo(left, top + offset);
1596                 else
1597                     c.moveTo(left, top + offset);
1598                 if (drawTop)
1599                     c.lineTo(right, top + offset);
1600                 else
1601                     c.moveTo(right, top + offset);
1602                 if (drawRight)
1603                     c.lineTo(right, bottom + offset);
1604                 else
1605                     c.moveTo(right, bottom + offset);
1606                 if (drawBottom)
1607                     c.lineTo(left, bottom + offset);
1608                 else
1609                     c.moveTo(left, bottom + offset);
1610                 c.stroke();
1611             }
1612         }
1613         
1614         function drawSeriesBars(series) {
1615             function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
1616                 var points = datapoints.points, ps = datapoints.pointsize;
1617                 
1618                 for (var i = 0; i < points.length; i += ps) {
1619                     if (points[i] == null)
1620                         continue;
1621                     drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
1622                 }
1623             }
1624 
1625             ctx.save();
1626             ctx.translate(plotOffset.left, plotOffset.top);
1627 
1628             // FIXME: figure out a way to add shadows (for instance along the right edge)
1629             ctx.lineWidth = series.bars.lineWidth;
1630             ctx.strokeStyle = series.color;
1631             var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
1632             var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
1633             plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
1634             ctx.restore();
1635         }
1636 
1637         function getFillStyle(filloptions, seriesColor, bottom, top) {
1638             var fill = filloptions.fill;
1639             if (!fill)
1640                 return null;
1641 
1642             if (filloptions.fillColor)
1643                 return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
1644             
1645             var c = parseColor(seriesColor);
1646             c.a = typeof fill == "number" ? fill : 0.4;
1647             c.normalize();
1648             return c.toString();
1649         }
1650         
1651         function insertLegend() {
1652             target.find(".legend").remove();
1653 
1654             if (!options.legend.show)
1655                 return;
1656             
1657             var fragments = [], rowStarted = false,
1658                 lf = options.legend.labelFormatter, s, label;
1659             for (i = 0; i < series.length; ++i) {
1660                 s = series[i];
1661                 label = s.label;
1662                 if (!label)
1663                     continue;
1664                 
1665                 if (i % options.legend.noColumns == 0) {
1666                     if (rowStarted)
1667                         fragments.push('</tr>');
1668                     fragments.push('<tr>');
1669                     rowStarted = true;
1670                 }
1671 
1672                 if (lf)
1673                     label = lf(label, s);
1674                 
1675                 fragments.push(
1676                     '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
1677                     '<td class="legendLabel">' + label + '</td>');
1678             }
1679             if (rowStarted)
1680                 fragments.push('</tr>');
1681             
1682             if (fragments.length == 0)
1683                 return;
1684 
1685             var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
1686             if (options.legend.container != null)
1687                 $(options.legend.container).html(table);
1688             else {
1689                 var pos = "",
1690                     p = options.legend.position,
1691                     m = options.legend.margin;
1692                 if (m[0] == null)
1693                     m = [m, m];
1694                 if (p.charAt(0) == "n")
1695                     pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
1696                 else if (p.charAt(0) == "s")
1697                     pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
1698                 if (p.charAt(1) == "e")
1699                     pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
1700                 else if (p.charAt(1) == "w")
1701                     pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
1702                 var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target);
1703                 if (options.legend.backgroundOpacity != 0.0) {
1704                     // put in the transparent background
1705                     // separately to avoid blended labels and
1706                     // label boxes
1707                     var c = options.legend.backgroundColor;
1708                     if (c == null) {
1709                         var tmp;
1710                         if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string")
1711                             tmp = options.grid.backgroundColor;
1712                         else
1713                             tmp = extractColor(legend);
1714                         c = parseColor(tmp).adjust(null, null, null, 1).toString();
1715                     }
1716                     var div = legend.children();
1717                     $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
1718                 }
1719             }
1720         }
1721 
1722 
1723         // interactive features
1724         
1725         var lastMousePos = { pageX: null, pageY: null },
1726             selection = {
1727                 first: { x: -1, y: -1}, second: { x: -1, y: -1},
1728                 show: false,
1729                 active: false
1730             },
1731             highlights = [],
1732             clickIsMouseUp = false,
1733             redrawTimeout = null,
1734             hoverTimeout = null;
1735         
1736         // returns the data item the mouse is over, or null if none is found
1737         function findNearbyItem(mouseX, mouseY, seriesFilter) {
1738             var maxDistance = options.grid.mouseActiveRadius,
1739                 lowestDistance = maxDistance * maxDistance + 1,
1740                 item = null, foundPoint = false, i, j;
1741 
1742             for (var i = 0; i < series.length; ++i) {
1743                 if (!seriesFilter(series[i]))
1744                     continue;
1745                 
1746                 var s = series[i],
1747                     axisx = s.xaxis,
1748                     axisy = s.yaxis,
1749                     points = s.datapoints.points,
1750                     ps = s.datapoints.pointsize,
1751                     mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
1752                     my = axisy.c2p(mouseY),
1753                     maxx = maxDistance / axisx.scale,
1754                     maxy = maxDistance / axisy.scale;
1755 
1756                 if (s.lines.show || s.points.show) {
1757                     for (j = 0; j < points.length; j += ps) {
1758                         var x = points[j], y = points[j + 1];
1759                         if (x == null)
1760                             continue;
1761                         
1762                         // For points and lines, the cursor must be within a
1763                         // certain distance to the data point
1764                         if (x - mx > maxx || x - mx < -maxx ||
1765                             y - my > maxy || y - my < -maxy)
1766                             continue;
1767 
1768                         // We have to calculate distances in pixels, not in
1769                         // data units, because the scales of the axes may be different
1770                         var dx = Math.abs(axisx.p2c(x) - mouseX),
1771                             dy = Math.abs(axisy.p2c(y) - mouseY),
1772                             dist = dx * dx + dy * dy; // no idea in taking sqrt
1773                         if (dist < lowestDistance) {
1774                             lowestDistance = dist;
1775                             item = [i, j / ps];
1776                         }
1777                     }
1778                 }
1779                     
1780                 if (s.bars.show && !item) { // no other point can be nearby
1781                     var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
1782                         barRight = barLeft + s.bars.barWidth;
1783                     
1784                     for (j = 0; j < points.length; j += ps) {
1785                         var x = points[j], y = points[j + 1], b = points[j + 2];
1786                         if (x == null)
1787                             continue;
1788   
1789                         // for a bar graph, the cursor must be inside the bar
1790                         if (series[i].bars.horizontal ? 
1791                             (mx <= Math.max(b, x) && mx >= Math.min(b, x) && 
1792                              my >= y + barLeft && my <= y + barRight) :
1793                             (mx >= x + barLeft && mx <= x + barRight &&
1794                              my >= Math.min(b, y) && my <= Math.max(b, y)))
1795                                 item = [i, j / ps];
1796                     }
1797                 }
1798             }
1799 
1800             if (item) {
1801                 i = item[0];
1802                 j = item[1];
1803                 
1804                 return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
1805                          dataIndex: j,
1806                          series: series[i],
1807                          seriesIndex: i };
1808             }
1809             
1810             return null;
1811         }
1812 
1813         function onMouseMove(e) {
1814             lastMousePos.pageX = e.pageX;
1815             lastMousePos.pageY = e.pageY;
1816             
1817             if (options.grid.hoverable)
1818                 triggerClickHoverEvent("plothover", lastMousePos,
1819                                        function (s) { return s["hoverable"] != false; });
1820 
1821             if (selection.active) {
1822                 target.trigger("plotselecting", [ getSelection() ]);
1823 
1824                 updateSelection(lastMousePos);
1825             }
1826         }
1827         
1828         function onMouseDown(e) {
1829             if (e.which != 1)  // only accept left-click
1830                 return;
1831             
1832             // cancel out any text selections
1833             document.body.focus();
1834 
1835             // prevent text selection and drag in old-school browsers
1836             if (document.onselectstart !== undefined && workarounds.onselectstart == null) {
1837                 workarounds.onselectstart = document.onselectstart;
1838                 document.onselectstart = function () { return false; };
1839             }
1840             if (document.ondrag !== undefined && workarounds.ondrag == null) {
1841                 workarounds.ondrag = document.ondrag;
1842                 document.ondrag = function () { return false; };
1843             }
1844             
1845             setSelectionPos(selection.first, e);
1846                 
1847             lastMousePos.pageX = null;
1848             selection.active = true;
1849             $(document).one("mouseup", onSelectionMouseUp);
1850         }
1851 
1852         function onClick(e) {
1853             if (clickIsMouseUp) {
1854                 clickIsMouseUp = false;
1855                 return;
1856             }
1857 
1858             triggerClickHoverEvent("plotclick", e,
1859                                    function (s) { return s["clickable"] != false; });
1860         }
1861 
1862         // trigger click or hover event (they send the same parameters
1863         // so we share their code)
1864         function triggerClickHoverEvent(eventname, event, seriesFilter) {
1865             var offset = eventHolder.offset(),
1866                 pos = { pageX: event.pageX, pageY: event.pageY },
1867                 canvasX = event.pageX - offset.left - plotOffset.left,
1868                 canvasY = event.pageY - offset.top - plotOffset.top;
1869 
1870             if (axes.xaxis.used)
1871                 pos.x = axes.xaxis.c2p(canvasX);
1872             if (axes.yaxis.used)
1873                 pos.y = axes.yaxis.c2p(canvasY);
1874             if (axes.x2axis.used)
1875                 pos.x2 = axes.x2axis.c2p(canvasX);
1876             if (axes.y2axis.used)
1877                 pos.y2 = axes.y2axis.c2p(canvasY);
1878 
1879             var item = findNearbyItem(canvasX, canvasY, seriesFilter);
1880 
1881             if (item) {
1882                 // fill in mouse pos for any listeners out there
1883                 item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
1884                 item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
1885             }
1886 
1887             if (options.grid.autoHighlight) {
1888                 // clear auto-highlights
1889                 for (var i = 0; i < highlights.length; ++i) {
1890                     var h = highlights[i];
1891                     if (h.auto == eventname &&
1892                         !(item && h.series == item.series && h.point == item.datapoint))
1893                         unhighlight(h.series, h.point);
1894                 }
1895                 
1896                 if (item)
1897                     highlight(item.series, item.datapoint, eventname);
1898             }
1899             
1900             target.trigger(eventname, [ pos, item ]);
1901         }
1902 
1903         function triggerRedrawOverlay() {
1904             if (!redrawTimeout)
1905                 redrawTimeout = setTimeout(drawOverlay, 30);
1906         }
1907 
1908         function drawOverlay() {
1909             redrawTimeout = null;
1910 
1911             // draw highlights
1912             octx.save();
1913             octx.clearRect(0, 0, canvasWidth, canvasHeight);
1914             octx.translate(plotOffset.left, plotOffset.top);
1915             
1916             var i, hi;
1917             for (i = 0; i < highlights.length; ++i) {
1918                 hi = highlights[i];
1919 
1920                 if (hi.series.bars.show)
1921                     drawBarHighlight(hi.series, hi.point);
1922                 else
1923                     drawPointHighlight(hi.series, hi.point);
1924             }
1925 
1926             // draw selection
1927             if (selection.show && selectionIsSane()) {
1928                 octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
1929                 octx.lineWidth = 1;
1930                 ctx.lineJoin = "round";
1931                 octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
1932                 
1933                 var x = Math.min(selection.first.x, selection.second.x),
1934                     y = Math.min(selection.first.y, selection.second.y),
1935                     w = Math.abs(selection.second.x - selection.first.x),
1936                     h = Math.abs(selection.second.y - selection.first.y);
1937                 
1938                 octx.fillRect(x, y, w, h);
1939                 octx.strokeRect(x, y, w, h);
1940             }
1941             octx.restore();
1942             
1943             executeHooks(hooks.drawOverlay, [octx]);
1944         }
1945         
1946         function highlight(s, point, auto) {
1947             if (typeof s == "number")
1948                 s = series[s];
1949 
1950             if (typeof point == "number")
1951                 point = s.data[point];
1952 
1953             var i = indexOfHighlight(s, point);
1954             if (i == -1) {
1955                 highlights.push({ series: s, point: point, auto: auto });
1956 
1957                 triggerRedrawOverlay();
1958             }
1959             else if (!auto)
1960                 highlights[i].auto = false;
1961         }
1962             
1963         function unhighlight(s, point) {
1964             if (s == null && point == null) {
1965                 highlights = [];
1966                 triggerRedrawOverlay();
1967             }
1968             
1969             if (typeof s == "number")
1970                 s = series[s];
1971 
1972             if (typeof point == "number")
1973                 point = s.data[point];
1974 
1975             var i = indexOfHighlight(s, point);
1976             if (i != -1) {
1977                 highlights.splice(i, 1);
1978 
1979                 triggerRedrawOverlay();
1980             }
1981         }
1982         
1983         function indexOfHighlight(s, p) {
1984             for (var i = 0; i < highlights.length; ++i) {
1985                 var h = highlights[i];
1986                 if (h.series == s && h.point[0] == p[0]
1987                     && h.point[1] == p[1])
1988                     return i;
1989             }
1990             return -1;
1991         }
1992         
1993         function drawPointHighlight(series, point) {
1994             var x = point[0], y = point[1],
1995                 axisx = series.xaxis, axisy = series.yaxis;
1996             
1997             if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1998                 return;
1999             
2000             var pointRadius = series.points.radius + series.points.lineWidth / 2;
2001             octx.lineWidth = pointRadius;
2002             octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
2003             var radius = 1.5 * pointRadius;
2004             octx.beginPath();
2005             octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
2006             octx.stroke();
2007         }
2008 
2009         function drawBarHighlight(series, point) {
2010             octx.lineWidth = series.bars.lineWidth;
2011             octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
2012             var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
2013             var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2014             drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2015                     0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
2016         }
2017 
2018         function getSelection() {
2019             if (!selectionIsSane())
2020                 return null;
2021             
2022             var x1 = Math.min(selection.first.x, selection.second.x),
2023                 x2 = Math.max(selection.first.x, selection.second.x),
2024                 y1 = Math.max(selection.first.y, selection.second.y),
2025                 y2 = Math.min(selection.first.y, selection.second.y);
2026 
2027             var r = {};
2028             if (axes.xaxis.used)
2029                 r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) };
2030             if (axes.x2axis.used)
2031                 r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) };
2032             if (axes.yaxis.used)
2033                 r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) };
2034             if (axes.y2axis.used)
2035                 r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
2036             return r;
2037         }
2038         
2039         function triggerSelectedEvent() {
2040             var r = getSelection();
2041             
2042             target.trigger("plotselected", [ r ]);
2043 
2044             // backwards-compat stuff, to be removed in future
2045             if (axes.xaxis.used && axes.yaxis.used)
2046                 target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
2047         }
2048         
2049         function onSelectionMouseUp(e) {
2050             // revert drag stuff for old-school browsers
2051             if (document.onselectstart !== undefined)
2052                 document.onselectstart = workarounds.onselectstart;
2053             if (document.ondrag !== undefined)
2054                 document.ondrag = workarounds.ondrag;
2055             
2056             // no more draggy-dee-drag
2057             selection.active = false;
2058             updateSelection(e);
2059             
2060             if (selectionIsSane()) {
2061                 triggerSelectedEvent();
2062                 clickIsMouseUp = true;
2063             }
2064             else {
2065                 // this counts as a clear
2066                 target.trigger("plotunselected", [ ]);
2067                 target.trigger("plotselecting", [ null ]);
2068             }
2069             
2070             return false;
2071         }
2072 
2073         function setSelectionPos(pos, e) {
2074             var offset = eventHolder.offset();
2075             pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth);
2076             pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight);
2077             
2078             if (options.selection.mode == "y") {
2079                 if (pos == selection.first)
2080                     pos.x = 0;
2081                 else
2082                     pos.x = plotWidth;
2083             }
2084 
2085             if (options.selection.mode == "x") {
2086                 if (pos == selection.first)
2087                     pos.y = 0;
2088                 else
2089                     pos.y = plotHeight;
2090             }
2091         }
2092 
2093         function updateSelection(pos) {
2094             if (pos.pageX == null)
2095                 return;
2096 
2097             setSelectionPos(selection.second, pos);
2098             if (selectionIsSane()) {
2099                 selection.show = true;
2100                 triggerRedrawOverlay();
2101             }
2102             else
2103                 clearSelection(true);
2104         }
2105 
2106         function clearSelection(preventEvent) {
2107             if (selection.show) {
2108                 selection.show = false;
2109                 triggerRedrawOverlay();
2110                 if (!preventEvent)
2111                     target.trigger("plotunselected", [ ]);
2112             }
2113         }
2114 
2115         function setSelection(ranges, preventEvent) {
2116             var range;
2117             
2118             if (options.selection.mode == "y") {
2119                 selection.first.x = 0;
2120                 selection.second.x = plotWidth;
2121             }
2122             else {
2123                 range = extractRange(ranges, "x");
2124                 
2125                 selection.first.x = range.axis.p2c(range.from);
2126                 selection.second.x = range.axis.p2c(range.to);
2127             }
2128             
2129             if (options.selection.mode == "x") {
2130                 selection.first.y = 0;
2131                 selection.second.y = plotHeight;
2132             }
2133             else {
2134                 range = extractRange(ranges, "y");
2135                 
2136                 selection.first.y = range.axis.p2c(range.from);
2137                 selection.second.y = range.axis.p2c(range.to);
2138             }
2139 
2140             selection.show = true;
2141             triggerRedrawOverlay();
2142             if (!preventEvent)
2143                 triggerSelectedEvent();
2144         }
2145         
2146         function selectionIsSane() {
2147             var minSize = 5;
2148             return Math.abs(selection.second.x - selection.first.x) >= minSize &&
2149                 Math.abs(selection.second.y - selection.first.y) >= minSize;
2150         }
2151         
2152         function getColorOrGradient(spec, bottom, top, defaultColor) {
2153             if (typeof spec == "string")
2154                 return spec;
2155             else {
2156                 // assume this is a gradient spec; IE currently only
2157                 // supports a simple vertical gradient properly, so that's
2158                 // what we support too
2159                 var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2160                 
2161                 for (var i = 0, l = spec.colors.length; i < l; ++i) {
2162                     var c = spec.colors[i];
2163                     gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity));
2164                 }
2165                 
2166                 return gradient;
2167             }
2168         }
2169     }
2170 
2171     $.plot = function(target, data, options) {
2172         var plot = new Plot($(target), data, options, $.plot.plugins);
2173         /*var t0 = new Date();
2174         var t1 = new Date();
2175         var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
2176         if (window.console)
2177             console.log(tstr);
2178         else
2179             alert(tstr);*/
2180         return plot;
2181     };
2182 
2183     $.plot.plugins = [];
2184 
2185     // returns a string with the date d formatted according to fmt
2186     $.plot.formatDate = function(d, fmt, monthNames) {
2187         var leftPad = function(n) {
2188             n = "" + n;
2189             return n.length == 1 ? "0" + n : n;
2190         };
2191         
2192         var r = [];
2193         var escape = false;
2194         if (monthNames == null)
2195             monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2196         for (var i = 0; i < fmt.length; ++i) {
2197             var c = fmt.charAt(i);
2198             
2199             if (escape) {
2200                 switch (c) {
2201                 case 'h': c = "" + d.getUTCHours(); break;
2202                 case 'H': c = leftPad(d.getUTCHours()); break;
2203                 case 'M': c = leftPad(d.getUTCMinutes()); break;
2204                 case 'S': c = leftPad(d.getUTCSeconds()); break;
2205                 case 'd': c = "" + d.getUTCDate(); break;
2206                 case 'm': c = "" + (d.getUTCMonth() + 1); break;
2207                 case 'y': c = "" + d.getUTCFullYear(); break;
2208                 case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2209                 }
2210                 r.push(c);
2211                 escape = false;
2212             }
2213             else {
2214                 if (c == "%")
2215                     escape = true;
2216                 else
2217                     r.push(c);
2218             }
2219         }
2220         return r.join("");
2221     };
2222     
2223     // round to nearby lower multiple of base
2224     function floorInBase(n, base) {
2225         return base * Math.floor(n / base);
2226     }
2227     
2228     function clamp(min, value, max) {
2229         if (value < min)
2230             return min;
2231         else if (value > max)
2232             return max;
2233         else
2234             return value;
2235     }
2236     
2237     // color helpers, inspiration from the jquery color animation
2238     // plugin by John Resig
2239     function Color (r, g, b, a) {
2240        
2241         var rgba = ['r','g','b','a'];
2242         var x = 4; //rgba.length
2243        
2244         while (-1<--x) {
2245             this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
2246         }
2247        
2248         this.toString = function() {
2249             if (this.a >= 1.0) {
2250                 return "rgb("+[this.r,this.g,this.b].join(",")+")";
2251             } else {
2252                 return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
2253             }
2254         };
2255 
2256         this.scale = function(rf, gf, bf, af) {
2257             x = 4; //rgba.length
2258             while (-1<--x) {
2259                 if (arguments[x] != null)
2260                     this[rgba[x]] *= arguments[x];
2261             }
2262             return this.normalize();
2263         };
2264 
2265         this.adjust = function(rd, gd, bd, ad) {
2266             x = 4; //rgba.length
2267             while (-1<--x) {
2268                 if (arguments[x] != null)
2269                     this[rgba[x]] += arguments[x];
2270             }
2271             return this.normalize();
2272         };
2273 
2274         this.clone = function() {
2275             return new Color(this.r, this.b, this.g, this.a);
2276         };
2277 
2278         var limit = function(val,minVal,maxVal) {
2279             return Math.max(Math.min(val, maxVal), minVal);
2280         };
2281 
2282         this.normalize = function() {
2283             this.r = clamp(0, parseInt(this.r), 255);
2284             this.g = clamp(0, parseInt(this.g), 255);
2285             this.b = clamp(0, parseInt(this.b), 255);
2286             this.a = clamp(0, this.a, 1);
2287             return this;
2288         };
2289 
2290         this.normalize();
2291     }
2292     
2293     var lookupColors = {
2294         aqua:[0,255,255],
2295         azure:[240,255,255],
2296         beige:[245,245,220],
2297         black:[0,0,0],
2298         blue:[0,0,255],
2299         brown:[165,42,42],
2300         cyan:[0,255,255],
2301         darkblue:[0,0,139],
2302         darkcyan:[0,139,139],
2303         darkgrey:[169,169,169],
2304         darkgreen:[0,100,0],
2305         darkkhaki:[189,183,107],
2306         darkmagenta:[139,0,139],
2307         darkolivegreen:[85,107,47],
2308         darkorange:[255,140,0],
2309         darkorchid:[153,50,204],
2310         darkred:[139,0,0],
2311         darksalmon:[233,150,122],
2312         darkviolet:[148,0,211],
2313         fuchsia:[255,0,255],
2314         gold:[255,215,0],
2315         green:[0,128,0],
2316         indigo:[75,0,130],
2317         khaki:[240,230,140],
2318         lightblue:[173,216,230],
2319         lightcyan:[224,255,255],
2320         lightgreen:[144,238,144],
2321         lightgrey:[211,211,211],
2322         lightpink:[255,182,193],
2323         lightyellow:[255,255,224],
2324         lime:[0,255,0],
2325         magenta:[255,0,255],
2326         maroon:[128,0,0],
2327         navy:[0,0,128],
2328         olive:[128,128,0],
2329         orange:[255,165,0],
2330         pink:[255,192,203],
2331         purple:[128,0,128],
2332         violet:[128,0,128],
2333         red:[255,0,0],
2334         silver:[192,192,192],
2335         white:[255,255,255],
2336         yellow:[255,255,0]
2337     };    
2338 
2339     function extractColor(element) {
2340         var color, elem = element;
2341         do {
2342             color = elem.css("background-color").toLowerCase();
2343             // keep going until we find an element that has color, or
2344             // we hit the body
2345             if (color != '' && color != 'transparent')
2346                 break;
2347             elem = elem.parent();
2348         } while (!$.nodeName(elem.get(0), "body"));
2349 
2350         // catch Safari's way of signalling transparent
2351         if (color == "rgba(0, 0, 0, 0)")
2352             return "transparent";
2353         
2354         return color;
2355     }
2356     
2357     // parse string, returns Color
2358     function parseColor(str) {
2359         var result;
2360 
2361         // Look for rgb(num,num,num)
2362         if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
2363             return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
2364         
2365         // Look for rgba(num,num,num,num)
2366         if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
2367             return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
2368             
2369         // Look for rgb(num%,num%,num%)
2370         if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
2371             return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
2372 
2373         // Look for rgba(num%,num%,num%,num)
2374         if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
2375             return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
2376         
2377         // Look for #a0b1c2
2378         if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
2379             return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
2380 
2381         // Look for #fff
2382         if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
2383             return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
2384 
2385         // Otherwise, we're most likely dealing with a named color
2386         var name = $.trim(str).toLowerCase();
2387         if (name == "transparent")
2388             return new Color(255, 255, 255, 0);
2389         else {
2390             result = lookupColors[name];
2391             return new Color(result[0], result[1], result[2]);
2392         }
2393     }
2394         
2395 })(jQuery);