JAVASCRIPT 11
Jquery.flot.js Guest on 13th October 2020 07:53:49 AM
  1. /*! Javascript plotting library for jQuery, v. 0.7.
  2.  *
  3.  * Released under the MIT license by IOLA, December 2007.
  4.  *
  5.  */
  6.  
  7. // first an inline dependency, jquery.colorhelpers.js, we inline it here
  8. // for convenience
  9.  
  10. /* Plugin for jQuery for working with colors.
  11.  *
  12.  * Version 1.1.
  13.  *
  14.  * Inspiration from jQuery color animation plugin by John Resig.
  15.  *
  16.  * Released under the MIT license by Ole Laursen, October 2009.
  17.  *
  18.  * Examples:
  19.  *
  20.  *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
  21.  *   var c = $.color.extract($("#mydiv"), 'background-color');
  22.  *   console.log(c.r, c.g, c.b, c.a);
  23.  *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
  24.  *
  25.  * Note that .scale() and .add() return the same modified object
  26.  * instead of making a new one.
  27.  *
  28.  * V. 1.1: Fix error handling so e.g. parsing an empty string does
  29.  * produce a color rather than just crashing.
  30.  */
  31. (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
  32.  
  33. // the actual Flot code
  34. (function($) {
  35.     function Plot(placeholder, data_, options_, plugins) {
  36.         // data is on the form:
  37.         //   [ series1, series2 ... ]
  38.         // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
  39.         // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
  40.        
  41.         var series = [],
  42.             options = {
  43.                 // the color theme used for graphs
  44.                 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
  45.                 legend: {
  46.                     show: true,
  47.                     noColumns: 1, // number of colums in legend table
  48.                     labelFormatter: null, // fn: string -> string
  49.                     labelBoxBorderColor: "#ccc", // border color for the little label boxes
  50.                     container: null, // container (as jQuery object) to put legend in, null means default on top of graph
  51.                     position: "ne", // position of default legend container within plot
  52.                     margin: 5, // distance from grid edge to default legend container within plot
  53.                     backgroundColor: null, // null means auto-detect
  54.                     backgroundOpacity: 0.85 // set to 0 to avoid background
  55.                 },
  56.                 xaxis: {
  57.                     show: null, // null = auto-detect, true = always, false = never
  58.                     position: "bottom", // or "top"
  59.                     mode: null, // null or "time"
  60.                     color: null, // base color, labels, ticks
  61.                     tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
  62.                     transform: null, // null or f: number -> number to transform axis
  63.                     inverseTransform: null, // if transform is set, this should be the inverse function
  64.                     min: null, // min. value to show, null means set automatically
  65.                     max: null, // max. value to show, null means set automatically
  66.                     autoscaleMargin: null, // margin in % to add if auto-setting min/max
  67.                     ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
  68.                     tickFormatter: null, // fn: number -> string
  69.                     labelWidth: null, // size of tick labels in pixels
  70.                     labelHeight: null,
  71.                     reserveSpace: null, // whether to reserve space even if axis isn't shown
  72.                     tickLength: null, // size in pixels of ticks, or "full" for whole line
  73.                     alignTicksWithAxis: null, // axis number or null for no sync
  74.                    
  75.                     // mode specific options
  76.                     tickDecimals: null, // no. of decimals, null means auto
  77.                     tickSize: null, // number or [number, "unit"]
  78.                     minTickSize: null, // number or [number, "unit"]
  79.                     monthNames: null, // list of names of months
  80.                     timeformat: null, // format string to use
  81.                     twelveHourClock: false // 12 or 24 time in time mode
  82.                 },
  83.                 yaxis: {
  84.                     autoscaleMargin: 0.02,
  85.                     position: "left" // or "right"
  86.                 },
  87.                 xaxes: [],
  88.                 yaxes: [],
  89.                 series: {
  90.                     points: {
  91.                         show: false,
  92.                         radius: 3,
  93.                         lineWidth: 2, // in pixels
  94.                         fill: true,
  95.                         fillColor: "#ffffff",
  96.                         symbol: "circle" // or callback
  97.                     },
  98.                     lines: {
  99.                         // we don't put in show: false so we can see
  100.                         // whether lines were actively disabled
  101.                         lineWidth: 2, // in pixels
  102.                         fill: false,
  103.                         fillColor: null,
  104.                         steps: false
  105.                     },
  106.                     bars: {
  107.                         show: false,
  108.                         lineWidth: 2, // in pixels
  109.                         barWidth: 1, // in units of the x axis
  110.                         fill: true,
  111.                         fillColor: null,
  112.                         align: "left", // or "center"
  113.                         horizontal: false
  114.                     },
  115.                     shadowSize: 3
  116.                 },
  117.                 grid: {
  118.                     show: true,
  119.                     aboveData: false,
  120.                     color: "#545454", // primary color used for outline and labels
  121.                     backgroundColor: null, // null for transparent, else color
  122.                     borderColor: null, // set if different from the grid color
  123.                     tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
  124.                     labelMargin: 5, // in pixels
  125.                     axisMargin: 8, // in pixels
  126.                     borderWidth: 2, // in pixels
  127.                     minBorderMargin: null, // in pixels, null means taken from points radius
  128.                     markings: null, // array of ranges or fn: axes -> array of ranges
  129.                     markingsColor: "#f4f4f4",
  130.                     markingsLineWidth: 2,
  131.                     // interactive stuff
  132.                     clickable: false,
  133.                     hoverable: false,
  134.                     autoHighlight: true, // highlight in case mouse is near
  135.                     mouseActiveRadius: 10 // how far the mouse can be away to activate an item
  136.                 },
  137.                 hooks: {}
  138.             },
  139.         canvas = null,      // the canvas for the plot itself
  140.         overlay = null,     // canvas for interactive stuff on top of plot
  141.         eventHolder = null, // jQuery object that events should be bound to
  142.         ctx = null, octx = null,
  143.         xaxes = [], yaxes = [],
  144.         plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
  145.         canvasWidth = 0, canvasHeight = 0,
  146.         plotWidth = 0, plotHeight = 0,
  147.         hooks = {
  148.             processOptions: [],
  149.             processRawData: [],
  150.             processDatapoints: [],
  151.             drawSeries: [],
  152.             draw: [],
  153.             bindEvents: [],
  154.             drawOverlay: [],
  155.             shutdown: []
  156.         },
  157.         plot = this;
  158.  
  159.         // public functions
  160.         plot.setData = setData;
  161.         plot.setupGrid = setupGrid;
  162.         plot.draw = draw;
  163.         plot.getPlaceholder = function() { return placeholder; };
  164.         plot.getCanvas = function() { return canvas; };
  165.         plot.getPlotOffset = function() { return plotOffset; };
  166.         plot.width = function () { return plotWidth; };
  167.         plot.height = function () { return plotHeight; };
  168.         plot.offset = function () {
  169.             var o = eventHolder.offset();
  170.             o.left += plotOffset.left;
  171.             o.top += plotOffset.top;
  172.             return o;
  173.         };
  174.         plot.getData = function () { return series; };
  175.         plot.getAxes = function () {
  176.             var res = {}, i;
  177.             $.each(xaxes.concat(yaxes), function (_, axis) {
  178.                 if (axis)
  179.                     res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
  180.             });
  181.             return res;
  182.         };
  183.         plot.getXAxes = function () { return xaxes; };
  184.         plot.getYAxes = function () { return yaxes; };
  185.         plot.c2p = canvasToAxisCoords;
  186.         plot.p2c = axisToCanvasCoords;
  187.         plot.getOptions = function () { return options; };
  188.         plot.highlight = highlight;
  189.         plot.unhighlight = unhighlight;
  190.         plot.triggerRedrawOverlay = triggerRedrawOverlay;
  191.         plot.pointOffset = function(point) {
  192.             return {
  193.                 left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
  194.                 top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
  195.             };
  196.         };
  197.         plot.shutdown = shutdown;
  198.         plot.resize = function () {
  199.             getCanvasDimensions();
  200.             resizeCanvas(canvas);
  201.             resizeCanvas(overlay);
  202.         };
  203.  
  204.         // public attributes
  205.         plot.hooks = hooks;
  206.        
  207.         // initialize
  208.         initPlugins(plot);
  209.         parseOptions(options_);
  210.         setupCanvases();
  211.         setData(data_);
  212.         setupGrid();
  213.         draw();
  214.         bindEvents();
  215.  
  216.  
  217.         function executeHooks(hook, args) {
  218.             args = [plot].concat(args);
  219.             for (var i = 0; i < hook.length; ++i)
  220.                 hook[i].apply(this, args);
  221.         }
  222.  
  223.         function initPlugins() {
  224.             for (var i = 0; i < plugins.length; ++i) {
  225.                 var p = plugins[i];
  226.                 p.init(plot);
  227.                 if (p.options)
  228.                     $.extend(true, options, p.options);
  229.             }
  230.         }
  231.        
  232.         function parseOptions(opts) {
  233.             var i;
  234.            
  235.             $.extend(true, options, opts);
  236.            
  237.             if (options.xaxis.color == null)
  238.                 options.xaxis.color = options.grid.color;
  239.             if (options.yaxis.color == null)
  240.                 options.yaxis.color = options.grid.color;
  241.            
  242.             if (options.xaxis.tickColor == null) // backwards-compatibility
  243.                 options.xaxis.tickColor = options.grid.tickColor;
  244.             if (options.yaxis.tickColor == null) // backwards-compatibility
  245.                 options.yaxis.tickColor = options.grid.tickColor;
  246.  
  247.             if (options.grid.borderColor == null)
  248.                 options.grid.borderColor = options.grid.color;
  249.             if (options.grid.tickColor == null)
  250.                 options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  251.            
  252.             // fill in defaults in axes, copy at least always the
  253.             // first as the rest of the code assumes it'll be there
  254.             for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
  255.                 options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
  256.             for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
  257.                 options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
  258.  
  259.             // backwards compatibility, to be removed in future
  260.             if (options.xaxis.noTicks && options.xaxis.ticks == null)
  261.                 options.xaxis.ticks = options.xaxis.noTicks;
  262.             if (options.yaxis.noTicks && options.yaxis.ticks == null)
  263.                 options.yaxis.ticks = options.yaxis.noTicks;
  264.             if (options.x2axis) {
  265.                 options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
  266.                 options.xaxes[1].position = "top";
  267.             }
  268.             if (options.y2axis) {
  269.                 options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
  270.                 options.yaxes[1].position = "right";
  271.             }
  272.             if (options.grid.coloredAreas)
  273.                 options.grid.markings = options.grid.coloredAreas;
  274.             if (options.grid.coloredAreasColor)
  275.                 options.grid.markingsColor = options.grid.coloredAreasColor;
  276.             if (options.lines)
  277.                 $.extend(true, options.series.lines, options.lines);
  278.             if (options.points)
  279.                 $.extend(true, options.series.points, options.points);
  280.             if (options.bars)
  281.                 $.extend(true, options.series.bars, options.bars);
  282.             if (options.shadowSize != null)
  283.                 options.series.shadowSize = options.shadowSize;
  284.  
  285.             // save options on axes for future reference
  286.             for (i = 0; i < options.xaxes.length; ++i)
  287.                 getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
  288.             for (i = 0; i < options.yaxes.length; ++i)
  289.                 getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
  290.  
  291.             // add hooks from options
  292.             for (var n in hooks)
  293.                 if (options.hooks[n] && options.hooks[n].length)
  294.                     hooks[n] = hooks[n].concat(options.hooks[n]);
  295.  
  296.             executeHooks(hooks.processOptions, [options]);
  297.         }
  298.  
  299.         function setData(d) {
  300.             series = parseData(d);
  301.             fillInSeriesOptions();
  302.             processData();
  303.         }
  304.        
  305.         function parseData(d) {
  306.             var res = [];
  307.             for (var i = 0; i < d.length; ++i) {
  308.                 var s = $.extend(true, {}, options.series);
  309.  
  310.                 if (d[i].data != null) {
  311.                     s.data = d[i].data; // move the data instead of deep-copy
  312.                     delete d[i].data;
  313.  
  314.                     $.extend(true, s, d[i]);
  315.  
  316.                     d[i].data = s.data;
  317.                 }
  318.                 else
  319.                     s.data = d[i];
  320.                 res.push(s);
  321.             }
  322.  
  323.             return res;
  324.         }
  325.        
  326.         function axisNumber(obj, coord) {
  327.             var a = obj[coord + "axis"];
  328.             if (typeof a == "object") // if we got a real axis, extract number
  329.                 a = a.n;
  330.             if (typeof a != "number")
  331.                 a = 1; // default to first axis
  332.             return a;
  333.         }
  334.  
  335.         function allAxes() {
  336.             // return flat array without annoying null entries
  337.             return $.grep(xaxes.concat(yaxes), function (a) { return a; });
  338.         }
  339.        
  340.         function canvasToAxisCoords(pos) {
  341.             // return an object with x/y corresponding to all used axes
  342.             var res = {}, i, axis;
  343.             for (i = 0; i < xaxes.length; ++i) {
  344.                 axis = xaxes[i];
  345.                 if (axis && axis.used)
  346.                     res["x" + axis.n] = axis.c2p(pos.left);
  347.             }
  348.  
  349.             for (i = 0; i < yaxes.length; ++i) {
  350.                 axis = yaxes[i];
  351.                 if (axis && axis.used)
  352.                     res["y" + axis.n] = axis.c2p(pos.top);
  353.             }
  354.            
  355.             if (res.x1 !== undefined)
  356.                 res.x = res.x1;
  357.             if (res.y1 !== undefined)
  358.                 res.y = res.y1;
  359.  
  360.             return res;
  361.         }
  362.        
  363.         function axisToCanvasCoords(pos) {
  364.             // get canvas coords from the first pair of x/y found in pos
  365.             var res = {}, i, axis, key;
  366.  
  367.             for (i = 0; i < xaxes.length; ++i) {
  368.                 axis = xaxes[i];
  369.                 if (axis && axis.used) {
  370.                     key = "x" + axis.n;
  371.                     if (pos[key] == null && axis.n == 1)
  372.                         key = "x";
  373.  
  374.                     if (pos[key] != null) {
  375.                         res.left = axis.p2c(pos[key]);
  376.                         break;
  377.                     }
  378.                 }
  379.             }
  380.            
  381.             for (i = 0; i < yaxes.length; ++i) {
  382.                 axis = yaxes[i];
  383.                 if (axis && axis.used) {
  384.                     key = "y" + axis.n;
  385.                     if (pos[key] == null && axis.n == 1)
  386.                         key = "y";
  387.  
  388.                     if (pos[key] != null) {
  389.                         res.top = axis.p2c(pos[key]);
  390.                         break;
  391.                     }
  392.                 }
  393.             }
  394.            
  395.             return res;
  396.         }
  397.        
  398.         function getOrCreateAxis(axes, number) {
  399.             if (!axes[number - 1])
  400.                 axes[number - 1] = {
  401.                     n: number, // save the number for future reference
  402.                     direction: axes == xaxes ? "x" : "y",
  403.                     options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
  404.                 };
  405.                
  406.             return axes[number - 1];
  407.         }
  408.  
  409.         function fillInSeriesOptions() {
  410.             var i;
  411.            
  412.             // collect what we already got of colors
  413.             var neededColors = series.length,
  414.                 usedColors = [],
  415.                 assignedColors = [];
  416.             for (i = 0; i < series.length; ++i) {
  417.                 var sc = series[i].color;
  418.                 if (sc != null) {
  419.                     --neededColors;
  420.                     if (typeof sc == "number")
  421.                         assignedColors.push(sc);
  422.                     else
  423.                         usedColors.push($.color.parse(series[i].color));
  424.                 }
  425.             }
  426.            
  427.             // we might need to generate more colors if higher indices
  428.             // are assigned
  429.             for (i = 0; i < assignedColors.length; ++i) {
  430.                 neededColors = Math.max(neededColors, assignedColors[i] + 1);
  431.             }
  432.  
  433.             // produce colors as needed
  434.             var colors = [], variation = 0;
  435.             i = 0;
  436.             while (colors.length < neededColors) {
  437.                 var c;
  438.                 if (options.colors.length == i) // check degenerate case
  439.                     c = $.color.make(100, 100, 100);
  440.                 else
  441.                     c = $.color.parse(options.colors[i]);
  442.  
  443.                 // vary color if needed
  444.                 var sign = variation % 2 == 1 ? -1 : 1;
  445.                 c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
  446.  
  447.                 // FIXME: if we're getting to close to something else,
  448.                 // we should probably skip this one
  449.                 colors.push(c);
  450.                
  451.                 ++i;
  452.                 if (i >= options.colors.length) {
  453.                     i = 0;
  454.                     ++variation;
  455.                 }
  456.             }
  457.  
  458.             // fill in the options
  459.             var colori = 0, s;
  460.             for (i = 0; i < series.length; ++i) {
  461.                 s = series[i];
  462.                
  463.                 // assign colors
  464.                 if (s.color == null) {
  465.                     s.color = colors[colori].toString();
  466.                     ++colori;
  467.                 }
  468.                 else if (typeof s.color == "number")
  469.                     s.color = colors[s.color].toString();
  470.  
  471.                 // turn on lines automatically in case nothing is set
  472.                 if (s.lines.show == null) {
  473.                     var v, show = true;
  474.                     for (v in s)
  475.                         if (s[v] && s[v].show) {
  476.                             show = false;
  477.                             break;
  478.                         }
  479.                     if (show)
  480.                         s.lines.show = true;
  481.                 }
  482.  
  483.                 // setup axes
  484.                 s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
  485.                 s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
  486.             }
  487.         }
  488.        
  489.         function processData() {
  490.             var topSentry = Number.POSITIVE_INFINITY,
  491.                 bottomSentry = Number.NEGATIVE_INFINITY,
  492.                 fakeInfinity = Number.MAX_VALUE,
  493.                 i, j, k, m, length,
  494.                 s, points, ps, x, y, axis, val, f, p;
  495.  
  496.             function updateAxis(axis, min, max) {
  497.                 if (min < axis.datamin && min != -fakeInfinity)
  498.                     axis.datamin = min;
  499.                 if (max > axis.datamax && max != fakeInfinity)
  500.                     axis.datamax = max;
  501.             }
  502.  
  503.             $.each(allAxes(), function (_, axis) {
  504.                 // init axis
  505.                 axis.datamin = topSentry;
  506.                 axis.datamax = bottomSentry;
  507.                 axis.used = false;
  508.             });
  509.            
  510.             for (i = 0; i < series.length; ++i) {
  511.                 s = series[i];
  512.                 s.datapoints = { points: [] };
  513.                
  514.                 executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
  515.             }
  516.            
  517.             // first pass: clean and copy data
  518.             for (i = 0; i < series.length; ++i) {
  519.                 s = series[i];
  520.  
  521.                 var data = s.data, format = s.datapoints.format;
  522.  
  523.                 if (!format) {
  524.                     format = [];
  525.                     // find out how to copy
  526.                     format.push({ x: true, number: true, required: true });
  527.                     format.push({ y: true, number: true, required: true });
  528.  
  529.                     if (s.bars.show || (s.lines.show && s.lines.fill)) {
  530.                         format.push({ y: true, number: true, required: false, defaultValue: 0 });
  531.                         if (s.bars.horizontal) {
  532.                             delete format[format.length - 1].y;
  533.                             format[format.length - 1].x = true;
  534.                         }
  535.                     }
  536.                    
  537.                     s.datapoints.format = format;
  538.                 }
  539.  
  540.                 if (s.datapoints.pointsize != null)
  541.                     continue; // already filled in
  542.  
  543.                 s.datapoints.pointsize = format.length;
  544.                
  545.                 ps = s.datapoints.pointsize;
  546.                 points = s.datapoints.points;
  547.  
  548.                 insertSteps = s.lines.show && s.lines.steps;
  549.                 s.xaxis.used = s.yaxis.used = true;
  550.                
  551.                 for (j = k = 0; j < data.length; ++j, k += ps) {
  552.                     p = data[j];
  553.  
  554.                     var nullify = p == null;
  555.                     if (!nullify) {
  556.                         for (m = 0; m < ps; ++m) {
  557.                             val = p[m];
  558.                             f = format[m];
  559.  
  560.                             if (f) {
  561.                                 if (f.number && val != null) {
  562.                                     val = +val; // convert to number
  563.                                     if (isNaN(val))
  564.                                         val = null;
  565.                                     else if (val == Infinity)
  566.                                         val = fakeInfinity;
  567.                                     else if (val == -Infinity)
  568.                                         val = -fakeInfinity;
  569.                                 }
  570.  
  571.                                 if (val == null) {
  572.                                     if (f.required)
  573.                                         nullify = true;
  574.                                    
  575.                                     if (f.defaultValue != null)
  576.                                         val = f.defaultValue;
  577.                                 }
  578.                             }
  579.                            
  580.                             points[k + m] = val;
  581.                         }
  582.                     }
  583.                    
  584.                     if (nullify) {
  585.                         for (m = 0; m < ps; ++m) {
  586.                             val = points[k + m];
  587.                             if (val != null) {
  588.                                 f = format[m];
  589.                                 // extract min/max info
  590.                                 if (f.x)
  591.                                     updateAxis(s.xaxis, val, val);
  592.                                 if (f.y)
  593.                                     updateAxis(s.yaxis, val, val);
  594.                             }
  595.                             points[k + m] = null;
  596.                         }
  597.                     }
  598.                     else {
  599.                         // a little bit of line specific stuff that
  600.                         // perhaps shouldn't be here, but lacking
  601.                         // better means...
  602.                         if (insertSteps && k > 0
  603.                             && points[k - ps] != null
  604.                             && points[k - ps] != points[k]
  605.                             && points[k - ps + 1] != points[k + 1]) {
  606.                             // copy the point to make room for a middle point
  607.                             for (m = 0; m < ps; ++m)
  608.                                 points[k + ps + m] = points[k + m];
  609.  
  610.                             // middle point has same y
  611.                             points[k + 1] = points[k - ps + 1];
  612.  
  613.                             // we've added a point, better reflect that
  614.                             k += ps;
  615.                         }
  616.                     }
  617.                 }
  618.             }
  619.  
  620.             // give the hooks a chance to run
  621.             for (i = 0; i < series.length; ++i) {
  622.                 s = series[i];
  623.                
  624.                 executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
  625.             }
  626.  
  627.             // second pass: find datamax/datamin for auto-scaling
  628.             for (i = 0; i < series.length; ++i) {
  629.                 s = series[i];
  630.                 points = s.datapoints.points,
  631.                 ps = s.datapoints.pointsize;
  632.  
  633.                 var xmin = topSentry, ymin = topSentry,
  634.                     xmax = bottomSentry, ymax = bottomSentry;
  635.                
  636.                 for (j = 0; j < points.length; j += ps) {
  637.                     if (points[j] == null)
  638.                         continue;
  639.  
  640.                     for (m = 0; m < ps; ++m) {
  641.                         val = points[j + m];
  642.                         f = format[m];
  643.                         if (!f || val == fakeInfinity || val == -fakeInfinity)
  644.                             continue;
  645.                        
  646.                         if (f.x) {
  647.                             if (val < xmin)
  648.                                 xmin = val;
  649.                             if (val > xmax)
  650.                                 xmax = val;
  651.                         }
  652.                         if (f.y) {
  653.                             if (val < ymin)
  654.                                 ymin = val;
  655.                             if (val > ymax)
  656.                                 ymax = val;
  657.                         }
  658.                     }
  659.                 }
  660.                
  661.                 if (s.bars.show) {
  662.                     // make sure we got room for the bar on the dancing floor
  663.                     var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
  664.                     if (s.bars.horizontal) {
  665.                         ymin += delta;
  666.                         ymax += delta + s.bars.barWidth;
  667.                     }
  668.                     else {
  669.                         xmin += delta;
  670.                         xmax += delta + s.bars.barWidth;
  671.                     }
  672.                 }
  673.                
  674.                 updateAxis(s.xaxis, xmin, xmax);
  675.                 updateAxis(s.yaxis, ymin, ymax);
  676.             }
  677.  
  678.             $.each(allAxes(), function (_, axis) {
  679.                 if (axis.datamin == topSentry)
  680.                     axis.datamin = null;
  681.                 if (axis.datamax == bottomSentry)
  682.                     axis.datamax = null;
  683.             });
  684.         }
  685.  
  686.         function makeCanvas(skipPositioning, cls) {
  687.             var c = document.createElement('canvas');
  688.             c.className = cls;
  689.             c.width = canvasWidth;
  690.             c.height = canvasHeight;
  691.                    
  692.             if (!skipPositioning)
  693.                 $(c).css({ position: 'absolute', left: 0, top: 0 });
  694.                
  695.             $(c).appendTo(placeholder);
  696.                
  697.             if (!c.getContext) // excanvas hack
  698.                 c = window.G_vmlCanvasManager.initElement(c);
  699.  
  700.             // used for resetting in case we get replotted
  701.             c.getContext("2d").save();
  702.            
  703.             return c;
  704.         }
  705.  
  706.         function getCanvasDimensions() {
  707.             canvasWidth = placeholder.width();
  708.             canvasHeight = placeholder.height();
  709.            
  710.             if (canvasWidth <= 0 || canvasHeight <= 0)
  711.                 throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
  712.         }
  713.  
  714.         function resizeCanvas(c) {
  715.             // resizing should reset the state (excanvas seems to be
  716.             // buggy though)
  717.             if (c.width != canvasWidth)
  718.                 c.width = canvasWidth;
  719.  
  720.             if (c.height != canvasHeight)
  721.                 c.height = canvasHeight;
  722.  
  723.             // so try to get back to the initial state (even if it's
  724.             // gone now, this should be safe according to the spec)
  725.             var cctx = c.getContext("2d");
  726.             cctx.restore();
  727.  
  728.             // and save again
  729.             cctx.save();
  730.         }
  731.        
  732.         function setupCanvases() {
  733.             var reused,
  734.                 existingCanvas = placeholder.children("canvas.base"),
  735.                 existingOverlay = placeholder.children("canvas.overlay");
  736.  
  737.             if (existingCanvas.length == 0 || existingOverlay == 0) {
  738.                 // init everything
  739.                
  740.                 placeholder.html(""); // make sure placeholder is clear
  741.            
  742.                 placeholder.css({ padding: 0 }); // padding messes up the positioning
  743.                
  744.                 if (placeholder.css("position") == 'static')
  745.                     placeholder.css("position", "relative"); // for positioning labels and overlay
  746.  
  747.                 getCanvasDimensions();
  748.                
  749.                 canvas = makeCanvas(true, "base");
  750.                 overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
  751.  
  752.                 reused = false;
  753.             }
  754.             else {
  755.                 // reuse existing elements
  756.  
  757.                 canvas = existingCanvas.get(0);
  758.                 overlay = existingOverlay.get(0);
  759.  
  760.                 reused = true;
  761.             }
  762.  
  763.             ctx = canvas.getContext("2d");
  764.             octx = overlay.getContext("2d");
  765.  
  766.             // we include the canvas in the event holder too, because IE 7
  767.             // sometimes has trouble with the stacking order
  768.             eventHolder = $([overlay, canvas]);
  769.  
  770.             if (reused) {
  771.                 // run shutdown in the old plot object
  772.                 placeholder.data("plot").shutdown();
  773.  
  774.                 // reset reused canvases
  775.                 plot.resize();
  776.                
  777.                 // make sure overlay pixels are cleared (canvas is cleared when we redraw)
  778.                 octx.clearRect(0, 0, canvasWidth, canvasHeight);
  779.                
  780.                 // then whack any remaining obvious garbage left
  781.                 eventHolder.unbind();
  782.                 placeholder.children().not([canvas, overlay]).remove();
  783.             }
  784.  
  785.             // save in case we get replotted
  786.             placeholder.data("plot", plot);
  787.         }
  788.  
  789.         function bindEvents() {
  790.             // bind events
  791.             if (options.grid.hoverable) {
  792.                 eventHolder.mousemove(onMouseMove);
  793.                 eventHolder.mouseleave(onMouseLeave);
  794.             }
  795.  
  796.             if (options.grid.clickable)
  797.                 eventHolder.click(onClick);
  798.  
  799.             executeHooks(hooks.bindEvents, [eventHolder]);
  800.         }
  801.  
  802.         function shutdown() {
  803.             if (redrawTimeout)
  804.                 clearTimeout(redrawTimeout);
  805.            
  806.             eventHolder.unbind("mousemove", onMouseMove);
  807.             eventHolder.unbind("mouseleave", onMouseLeave);
  808.             eventHolder.unbind("click", onClick);
  809.            
  810.             executeHooks(hooks.shutdown, [eventHolder]);
  811.         }
  812.  
  813.         function setTransformationHelpers(axis) {
  814.             // set helper functions on the axis, assumes plot area
  815.             // has been computed already
  816.            
  817.             function identity(x) { return x; }
  818.            
  819.             var s, m, t = axis.options.transform || identity,
  820.                 it = axis.options.inverseTransform;
  821.            
  822.             // precompute how much the axis is scaling a point
  823.             // in canvas space
  824.             if (axis.direction == "x") {
  825.                 s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
  826.                 m = Math.min(t(axis.max), t(axis.min));
  827.             }
  828.             else {
  829.                 s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
  830.                 s = -s;
  831.                 m = Math.max(t(axis.max), t(axis.min));
  832.             }
  833.  
  834.             // data point to canvas coordinate
  835.             if (t == identity) // slight optimization
  836.                 axis.p2c = function (p) { return (p - m) * s; };
  837.             else
  838.                 axis.p2c = function (p) { return (t(p) - m) * s; };
  839.             // canvas coordinate to data point
  840.             if (!it)
  841.                 axis.c2p = function (c) { return m + c / s; };
  842.             else
  843.                 axis.c2p = function (c) { return it(m + c / s); };
  844.         }
  845.  
  846.         function measureTickLabels(axis) {
  847.             var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
  848.                 l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
  849.  
  850.             function makeDummyDiv(labels, width) {
  851.                 return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
  852.                          '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
  853.                          + labels.join("") + '</div></div>')
  854.                     .appendTo(placeholder);
  855.             }
  856.            
  857.             if (axis.direction == "x") {
  858.                 // to avoid measuring the widths of the labels (it's slow), we
  859.                 // construct fixed-size boxes and put the labels inside
  860.                 // them, we don't need the exact figures and the
  861.                 // fixed-size box content is easy to center
  862.                 if (w == null)
  863.                     w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
  864.  
  865.                 // measure x label heights
  866.                 if (h == null) {
  867.                     labels = [];
  868.                     for (i = 0; i < ticks.length; ++i) {
  869.                         l = ticks[i].label;
  870.                         if (l)
  871.                             labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
  872.                     }
  873.  
  874.                     if (labels.length > 0) {
  875.                         // stick them all in the same div and measure
  876.                         // collective height
  877.                         labels.push('<div style="clear:left"></div>');
  878.                         dummyDiv = makeDummyDiv(labels, "width:10000px;");
  879.                         h = dummyDiv.height();
  880.                         dummyDiv.remove();
  881.                     }
  882.                 }
  883.             }
  884.             else if (w == null || h == null) {
  885.                 // calculate y label dimensions
  886.                 for (i = 0; i < ticks.length; ++i) {
  887.                     l = ticks[i].label;
  888.                     if (l)
  889.                         labels.push('<div class="tickLabel">' + l + '</div>');
  890.                 }
  891.                
  892.                 if (labels.length > 0) {
  893.                     dummyDiv = makeDummyDiv(labels, "");
  894.                     if (w == null)
  895.                         w = dummyDiv.children().width();
  896.                     if (h == null)
  897.                         h = dummyDiv.find("div.tickLabel").height();
  898.                     dummyDiv.remove();
  899.                 }
  900.             }
  901.  
  902.             if (w == null)
  903.                 w = 0;
  904.             if (h == null)
  905.                 h = 0;
  906.  
  907.             axis.labelWidth = w;
  908.             axis.labelHeight = h;
  909.         }
  910.  
  911.         function allocateAxisBoxFirstPhase(axis) {
  912.             // find the bounding box of the axis by looking at label
  913.             // widths/heights and ticks, make room by diminishing the
  914.             // plotOffset
  915.  
  916.             var lw = axis.labelWidth,
  917.                 lh = axis.labelHeight,
  918.                 pos = axis.options.position,
  919.                 tickLength = axis.options.tickLength,
  920.                 axismargin = options.grid.axisMargin,
  921.                 padding = options.grid.labelMargin,
  922.                 all = axis.direction == "x" ? xaxes : yaxes,
  923.                 index;
  924.  
  925.             // determine axis margin
  926.             var samePosition = $.grep(all, function (a) {
  927.                 return a && a.options.position == pos && a.reserveSpace;
  928.             });
  929.             if ($.inArray(axis, samePosition) == samePosition.length - 1)
  930.                 axismargin = 0; // outermost
  931.  
  932.             // determine tick length - if we're innermost, we can use "full"
  933.             if (tickLength == null)
  934.                 tickLength = "full";
  935.  
  936.             var sameDirection = $.grep(all, function (a) {
  937.                 return a && a.reserveSpace;
  938.             });
  939.  
  940.             var innermost = $.inArray(axis, sameDirection) == 0;
  941.             if (!innermost && tickLength == "full")
  942.                 tickLength = 5;
  943.                
  944.             if (!isNaN(+tickLength))
  945.                 padding += +tickLength;
  946.  
  947.             // compute box
  948.             if (axis.direction == "x") {
  949.                 lh += padding;
  950.                
  951.                 if (pos == "bottom") {
  952.                     plotOffset.bottom += lh + axismargin;
  953.                     axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
  954.                 }
  955.                 else {
  956.                     axis.box = { top: plotOffset.top + axismargin, height: lh };
  957.                     plotOffset.top += lh + axismargin;
  958.                 }
  959.             }
  960.             else {
  961.                 lw += padding;
  962.                
  963.                 if (pos == "left") {
  964.                     axis.box = { left: plotOffset.left + axismargin, width: lw };
  965.                     plotOffset.left += lw + axismargin;
  966.                 }
  967.                 else {
  968.                     plotOffset.right += lw + axismargin;
  969.                     axis.box = { left: canvasWidth - plotOffset.right, width: lw };
  970.                 }
  971.             }
  972.  
  973.              // save for future reference
  974.             axis.position = pos;
  975.             axis.tickLength = tickLength;
  976.             axis.box.padding = padding;
  977.             axis.innermost = innermost;
  978.         }
  979.  
  980.         function allocateAxisBoxSecondPhase(axis) {
  981.             // set remaining bounding box coordinates
  982.             if (axis.direction == "x") {
  983.                 axis.box.left = plotOffset.left;
  984.                 axis.box.width = plotWidth;
  985.             }
  986.             else {
  987.                 axis.box.top = plotOffset.top;
  988.                 axis.box.height = plotHeight;
  989.             }
  990.         }
  991.        
  992.         function setupGrid() {
  993.             var i, axes = allAxes();
  994.  
  995.             // first calculate the plot and axis box dimensions
  996.  
  997.             $.each(axes, function (_, axis) {
  998.                 axis.show = axis.options.show;
  999.                 if (axis.show == null)
  1000.                     axis.show = axis.used; // by default an axis is visible if it's got data
  1001.                
  1002.                 axis.reserveSpace = axis.show || axis.options.reserveSpace;
  1003.  
  1004.                 setRange(axis);
  1005.             });
  1006.  
  1007.             allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
  1008.  
  1009.             plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
  1010.             if (options.grid.show) {
  1011.                 $.each(allocatedAxes, function (_, axis) {
  1012.                     // make the ticks
  1013.                     setupTickGeneration(axis);
  1014.                     setTicks(axis);
  1015.                     snapRangeToTicks(axis, axis.ticks);
  1016.  
  1017.                     // find labelWidth/Height for axis
  1018.                     measureTickLabels(axis);
  1019.                 });
  1020.  
  1021.                 // with all dimensions in house, we can compute the
  1022.                 // axis boxes, start from the outside (reverse order)
  1023.                 for (i = allocatedAxes.length - 1; i >= 0; --i)
  1024.                     allocateAxisBoxFirstPhase(allocatedAxes[i]);
  1025.  
  1026.                 // make sure we've got enough space for things that
  1027.                 // might stick out
  1028.                 var minMargin = options.grid.minBorderMargin;
  1029.                 if (minMargin == null) {
  1030.                     minMargin = 0;
  1031.                     for (i = 0; i < series.length; ++i)
  1032.                         minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
  1033.                 }
  1034.                    
  1035.                 for (var a in plotOffset) {
  1036.                     plotOffset[a] += options.grid.borderWidth;
  1037.                     plotOffset[a] = Math.max(minMargin, plotOffset[a]);
  1038.                 }
  1039.             }
  1040.            
  1041.             plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
  1042.             plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
  1043.  
  1044.             // now we got the proper plotWidth/Height, we can compute the scaling
  1045.             $.each(axes, function (_, axis) {
  1046.                 setTransformationHelpers(axis);
  1047.             });
  1048.  
  1049.             if (options.grid.show) {
  1050.                 $.each(allocatedAxes, function (_, axis) {
  1051.                     allocateAxisBoxSecondPhase(axis);
  1052.                 });
  1053.  
  1054.                 insertAxisLabels();
  1055.             }
  1056.            
  1057.             insertLegend();
  1058.         }
  1059.        
  1060.         function setRange(axis) {
  1061.             var opts = axis.options,
  1062.                 min = +(opts.min != null ? opts.min : axis.datamin),
  1063.                 max = +(opts.max != null ? opts.max : axis.datamax),
  1064.                 delta = max - min;
  1065.  
  1066.             if (delta == 0.0) {
  1067.                 // degenerate case
  1068.                 var widen = max == 0 ? 1 : 0.01;
  1069.  
  1070.                 if (opts.min == null)
  1071.                     min -= widen;
  1072.                 // always widen max if we couldn't widen min to ensure we
  1073.                 // don't fall into min == max which doesn't work
  1074.                 if (opts.max == null || opts.min != null)
  1075.                     max += widen;
  1076.             }
  1077.             else {
  1078.                 // consider autoscaling
  1079.                 var margin = opts.autoscaleMargin;
  1080.                 if (margin != null) {
  1081.                     if (opts.min == null) {
  1082.                         min -= delta * margin;
  1083.                         // make sure we don't go below zero if all values
  1084.                         // are positive
  1085.                         if (min < 0 && axis.datamin != null && axis.datamin >= 0)
  1086.                             min = 0;
  1087.                     }
  1088.                     if (opts.max == null) {
  1089.                         max += delta * margin;
  1090.                         if (max > 0 && axis.datamax != null && axis.datamax <= 0)
  1091.                             max = 0;
  1092.                     }
  1093.                 }
  1094.             }
  1095.             axis.min = min;
  1096.             axis.max = max;
  1097.         }
  1098.  
  1099.         function setupTickGeneration(axis) {
  1100.             var opts = axis.options;
  1101.                
  1102.             // estimate number of ticks
  1103.             var noTicks;
  1104.             if (typeof opts.ticks == "number" && opts.ticks > 0)
  1105.                 noTicks = opts.ticks;
  1106.             else
  1107.                 // heuristic based on the model a*sqrt(x) fitted to
  1108.                 // some data points that seemed reasonable
  1109.                 noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
  1110.  
  1111.             var delta = (axis.max - axis.min) / noTicks,
  1112.                 size, generator, unit, formatter, i, magn, norm;
  1113.  
  1114.             if (opts.mode == "time") {
  1115.                 // pretty handling of time
  1116.                
  1117.                 // map of app. size of time units in milliseconds
  1118.                 var timeUnitSize = {
  1119.                     "second": 1000,
  1120.                     "minute": 60 * 1000,
  1121.                     "hour": 60 * 60 * 1000,
  1122.                     "day": 24 * 60 * 60 * 1000,
  1123.                     "month": 30 * 24 * 60 * 60 * 1000,
  1124.                     "year": 365.2425 * 24 * 60 * 60 * 1000
  1125.                 };
  1126.  
  1127.  
  1128.                 // the allowed tick sizes, after 1 year we use
  1129.                 // an integer algorithm
  1130.                 var spec = [
  1131.                     [1, "second"], [2, "second"], [5, "second"], [10, "second"],
  1132.                     [30, "second"],
  1133.                     [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
  1134.                     [30, "minute"],
  1135.                     [1, "hour"], [2, "hour"], [4, "hour"],
  1136.                     [8, "hour"], [12, "hour"],
  1137.                     [1, "day"], [2, "day"], [3, "day"],
  1138.                     [0.25, "month"], [0.5, "month"], [1, "month"],
  1139.                     [2, "month"], [3, "month"], [6, "month"],
  1140.                     [1, "year"]
  1141.                 ];
  1142.  
  1143.                 var minSize = 0;
  1144.                 if (opts.minTickSize != null) {
  1145.                     if (typeof opts.tickSize == "number")
  1146.                         minSize = opts.tickSize;
  1147.                     else
  1148.                         minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
  1149.                 }
  1150.  
  1151.                 for (var i = 0; i < spec.length - 1; ++i)
  1152.                     if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
  1153.                                  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
  1154.                        && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
  1155.                         break;
  1156.                 size = spec[i][0];
  1157.                 unit = spec[i][1];
  1158.                
  1159.                 // special-case the possibility of several years
  1160.                 if (unit == "year") {
  1161.                     magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
  1162.                     norm = (delta / timeUnitSize.year) / magn;
  1163.                     if (norm < 1.5)
  1164.                         size = 1;
  1165.                     else if (norm < 3)
  1166.                         size = 2;
  1167.                     else if (norm < 7.5)
  1168.                         size = 5;
  1169.                     else
  1170.                         size = 10;
  1171.  
  1172.                     size *= magn;
  1173.                 }
  1174.  
  1175.                 axis.tickSize = opts.tickSize || [size, unit];
  1176.                
  1177.                 generator = function(axis) {
  1178.                     var ticks = [],
  1179.                         tickSize = axis.tickSize[0], unit = axis.tickSize[1],
  1180.                         d = new Date(axis.min);
  1181.                    
  1182.                     var step = tickSize * timeUnitSize[unit];
  1183.  
  1184.                     if (unit == "second")
  1185.                         d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
  1186.                     if (unit == "minute")
  1187.                         d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
  1188.                     if (unit == "hour")
  1189.                         d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
  1190.                     if (unit == "month")
  1191.                         d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
  1192.                     if (unit == "year")
  1193.                         d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
  1194.                    
  1195.                     // reset smaller components
  1196.                     d.setUTCMilliseconds(0);
  1197.                     if (step >= timeUnitSize.minute)
  1198.                         d.setUTCSeconds(0);
  1199.                     if (step >= timeUnitSize.hour)
  1200.                         d.setUTCMinutes(0);
  1201.                     if (step >= timeUnitSize.day)
  1202.                         d.setUTCHours(0);
  1203.                     if (step >= timeUnitSize.day * 4)
  1204.                         d.setUTCDate(1);
  1205.                     if (step >= timeUnitSize.year)
  1206.                         d.setUTCMonth(0);
  1207.  
  1208.  
  1209.                     var carry = 0, v = Number.NaN, prev;
  1210.                     do {
  1211.                         prev = v;
  1212.                         v = d.getTime();
  1213.                         ticks.push(v);
  1214.                         if (unit == "month") {
  1215.                             if (tickSize < 1) {
  1216.                                 // a bit complicated - we'll divide the month
  1217.                                 // up but we need to take care of fractions
  1218.                                 // so we don't end up in the middle of a day
  1219.                                 d.setUTCDate(1);
  1220.                                 var start = d.getTime();
  1221.                                 d.setUTCMonth(d.getUTCMonth() + 1);
  1222.                                 var end = d.getTime();
  1223.                                 d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
  1224.                                 carry = d.getUTCHours();
  1225.                                 d.setUTCHours(0);
  1226.                             }
  1227.                             else
  1228.                                 d.setUTCMonth(d.getUTCMonth() + tickSize);
  1229.                         }
  1230.                         else if (unit == "year") {
  1231.                             d.setUTCFullYear(d.getUTCFullYear() + tickSize);
  1232.                         }
  1233.                         else
  1234.                             d.setTime(v + step);
  1235.                     } while (v < axis.max && v != prev);
  1236.  
  1237.                     return ticks;
  1238.                 };
  1239.  
  1240.                 formatter = function (v, axis) {
  1241.                     var d = new Date(v);
  1242.  
  1243.                     // first check global format
  1244.                     if (opts.timeformat != null)
  1245.                         return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
  1246.                    
  1247.                     var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
  1248.                     var span = axis.max - axis.min;
  1249.                     var suffix = (opts.twelveHourClock) ? " %p" : "";
  1250.                    
  1251.                     if (t < timeUnitSize.minute)
  1252.                         fmt = "%h:%M:%S" + suffix;
  1253.                     else if (t < timeUnitSize.day) {
  1254.                         if (span < 2 * timeUnitSize.day)
  1255.                             fmt = "%h:%M" + suffix;
  1256.                         else
  1257.                             fmt = "%b %d %h:%M" + suffix;
  1258.                     }
  1259.                     else if (t < timeUnitSize.month)
  1260.                         fmt = "%b %d";
  1261.                     else if (t < timeUnitSize.year) {
  1262.                         if (span < timeUnitSize.year)
  1263.                             fmt = "%b";
  1264.                         else
  1265.                             fmt = "%b %y";
  1266.                     }
  1267.                     else
  1268.                         fmt = "%y";
  1269.                    
  1270.                     return $.plot.formatDate(d, fmt, opts.monthNames);
  1271.                 };
  1272.             }
  1273.             else {
  1274.                 // pretty rounding of base-10 numbers
  1275.                 var maxDec = opts.tickDecimals;
  1276.                 var dec = -Math.floor(Math.log(delta) / Math.LN10);
  1277.                 if (maxDec != null && dec > maxDec)
  1278.                     dec = maxDec;
  1279.  
  1280.                 magn = Math.pow(10, -dec);
  1281.                 norm = delta / magn; // norm is between 1.0 and 10.0
  1282.                
  1283.                 if (norm < 1.5)
  1284.                     size = 1;
  1285.                 else if (norm < 3) {
  1286.                     size = 2;
  1287.                     // special case for 2.5, requires an extra decimal
  1288.                     if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
  1289.                         size = 2.5;
  1290.                         ++dec;
  1291.                     }
  1292.                 }
  1293.                 else if (norm < 7.5)
  1294.                     size = 5;
  1295.                 else
  1296.                     size = 10;
  1297.  
  1298.                 size *= magn;
  1299.                
  1300.                 if (opts.minTickSize != null && size < opts.minTickSize)
  1301.                     size = opts.minTickSize;
  1302.  
  1303.                 axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
  1304.                 axis.tickSize = opts.tickSize || size;
  1305.  
  1306.                 generator = function (axis) {
  1307.                     var ticks = [];
  1308.  
  1309.                     // spew out all possible ticks
  1310.                     var start = floorInBase(axis.min, axis.tickSize),
  1311.                         i = 0, v = Number.NaN, prev;
  1312.                     do {
  1313.                         prev = v;
  1314.                         v = start + i * axis.tickSize;
  1315.                         ticks.push(v);
  1316.                         ++i;
  1317.                     } while (v < axis.max && v != prev);
  1318.                     return ticks;
  1319.                 };
  1320.  
  1321.                 formatter = function (v, axis) {
  1322.                     return v.toFixed(axis.tickDecimals);
  1323.                 };
  1324.             }
  1325.  
  1326.             if (opts.alignTicksWithAxis != null) {
  1327.                 var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
  1328.                 if (otherAxis && otherAxis.used && otherAxis != axis) {
  1329.                     // consider snapping min/max to outermost nice ticks
  1330.                     var niceTicks = generator(axis);
  1331.                     if (niceTicks.length > 0) {
  1332.                         if (opts.min == null)
  1333.                             axis.min = Math.min(axis.min, niceTicks[0]);
  1334.                         if (opts.max == null && niceTicks.length > 1)
  1335.                             axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
  1336.                     }
  1337.                    
  1338.                     generator = function (axis) {
  1339.                         // copy ticks, scaled to this axis
  1340.                         var ticks = [], v, i;
  1341.                         for (i = 0; i < otherAxis.ticks.length; ++i) {
  1342.                             v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
  1343.                             v = axis.min + v * (axis.max - axis.min);
  1344.                             ticks.push(v);
  1345.                         }
  1346.                         return ticks;
  1347.                     };
  1348.                    
  1349.                     // we might need an extra decimal since forced
  1350.                     // ticks don't necessarily fit naturally
  1351.                     if (axis.mode != "time" && opts.tickDecimals == null) {
  1352.                         var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
  1353.                             ts = generator(axis);
  1354.  
  1355.                         // only proceed if the tick interval rounded
  1356.                         // with an extra decimal doesn't give us a
  1357.                         // zero at end
  1358.                         if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
  1359.                             axis.tickDecimals = extraDec;
  1360.                     }
  1361.                 }
  1362.             }
  1363.  
  1364.             axis.tickGenerator = generator;
  1365.             if ($.isFunction(opts.tickFormatter))
  1366.                 axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
  1367.             else
  1368.                 axis.tickFormatter = formatter;
  1369.         }
  1370.        
  1371.         function setTicks(axis) {
  1372.             var oticks = axis.options.ticks, ticks = [];
  1373.             if (oticks == null || (typeof oticks == "number" && oticks > 0))
  1374.                 ticks = axis.tickGenerator(axis);
  1375.             else if (oticks) {
  1376.                 if ($.isFunction(oticks))
  1377.                     // generate the ticks
  1378.                     ticks = oticks({ min: axis.min, max: axis.max });
  1379.                 else
  1380.                     ticks = oticks;
  1381.             }
  1382.  
  1383.             // clean up/labelify the supplied ticks, copy them over
  1384.             var i, v;
  1385.             axis.ticks = [];
  1386.             for (i = 0; i < ticks.length; ++i) {
  1387.                 var label = null;
  1388.                 var t = ticks[i];
  1389.                 if (typeof t == "object") {
  1390.                     v = +t[0];
  1391.                     if (t.length > 1)
  1392.                         label = t[1];
  1393.                 }
  1394.                 else
  1395.                     v = +t;
  1396.                 if (label == null)
  1397.                     label = axis.tickFormatter(v, axis);
  1398.                 if (!isNaN(v))
  1399.                     axis.ticks.push({ v: v, label: label });
  1400.             }
  1401.         }
  1402.  
  1403.         function snapRangeToTicks(axis, ticks) {
  1404.             if (axis.options.autoscaleMargin && ticks.length > 0) {
  1405.                 // snap to ticks
  1406.                 if (axis.options.min == null)
  1407.                     axis.min = Math.min(axis.min, ticks[0].v);
  1408.                 if (axis.options.max == null && ticks.length > 1)
  1409.                     axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
  1410.             }
  1411.         }
  1412.      
  1413.         function draw() {
  1414.             ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  1415.  
  1416.             var grid = options.grid;
  1417.  
  1418.             // draw background, if any
  1419.             if (grid.show && grid.backgroundColor)
  1420.                 drawBackground();
  1421.            
  1422.             if (grid.show && !grid.aboveData)
  1423.                 drawGrid();
  1424.  
  1425.             for (var i = 0; i < series.length; ++i) {
  1426.                 executeHooks(hooks.drawSeries, [ctx, series[i]]);
  1427.                 drawSeries(series[i]);
  1428.             }
  1429.  
  1430.             executeHooks(hooks.draw, [ctx]);
  1431.            
  1432.             if (grid.show && grid.aboveData)
  1433.                 drawGrid();
  1434.         }
  1435.  
  1436.         function extractRange(ranges, coord) {
  1437.             var axis, from, to, key, axes = allAxes();
  1438.  
  1439.             for (i = 0; i < axes.length; ++i) {
  1440.                 axis = axes[i];
  1441.                 if (axis.direction == coord) {
  1442.                     key = coord + axis.n + "axis";
  1443.                     if (!ranges[key] && axis.n == 1)
  1444.                         key = coord + "axis"; // support x1axis as xaxis
  1445.                     if (ranges[key]) {
  1446.                         from = ranges[key].from;
  1447.                         to = ranges[key].to;
  1448.                         break;
  1449.                     }
  1450.                 }
  1451.             }
  1452.  
  1453.             // backwards-compat stuff - to be removed in future
  1454.             if (!ranges[key]) {
  1455.                 axis = coord == "x" ? xaxes[0] : yaxes[0];
  1456.                 from = ranges[coord + "1"];
  1457.                 to = ranges[coord + "2"];
  1458.             }
  1459.  
  1460.             // auto-reverse as an added bonus
  1461.             if (from != null && to != null && from > to) {
  1462.                 var tmp = from;
  1463.                 from = to;
  1464.                 to = tmp;
  1465.             }
  1466.            
  1467.             return { from: from, to: to, axis: axis };
  1468.         }
  1469.        
  1470.         function drawBackground() {
  1471.             ctx.save();
  1472.             ctx.translate(plotOffset.left, plotOffset.top);
  1473.  
  1474.             ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
  1475.             ctx.fillRect(0, 0, plotWidth, plotHeight);
  1476.             ctx.restore();
  1477.         }
  1478.  
  1479.         function drawGrid() {
  1480.             var i;
  1481.            
  1482.             ctx.save();
  1483.             ctx.translate(plotOffset.left, plotOffset.top);
  1484.  
  1485.             // draw markings
  1486.             var markings = options.grid.markings;
  1487.             if (markings) {
  1488.                 if ($.isFunction(markings)) {
  1489.                     var axes = plot.getAxes();
  1490.                     // xmin etc. is backwards compatibility, to be
  1491.                     // removed in the future
  1492.                     axes.xmin = axes.xaxis.min;
  1493.                     axes.xmax = axes.xaxis.max;
  1494.                     axes.ymin = axes.yaxis.min;
  1495.                     axes.ymax = axes.yaxis.max;
  1496.                    
  1497.                     markings = markings(axes);
  1498.                 }
  1499.  
  1500.                 for (i = 0; i < markings.length; ++i) {
  1501.                     var m = markings[i],
  1502.                         xrange = extractRange(m, "x"),
  1503.                         yrange = extractRange(m, "y");
  1504.  
  1505.                     // fill in missing
  1506.                     if (xrange.from == null)
  1507.                         xrange.from = xrange.axis.min;
  1508.                     if (xrange.to == null)
  1509.                         xrange.to = xrange.axis.max;
  1510.                     if (yrange.from == null)
  1511.                         yrange.from = yrange.axis.min;
  1512.                     if (yrange.to == null)
  1513.                         yrange.to = yrange.axis.max;
  1514.  
  1515.                     // clip
  1516.                     if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
  1517.                         yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
  1518.                         continue;
  1519.  
  1520.                     xrange.from = Math.max(xrange.from, xrange.axis.min);
  1521.                     xrange.to = Math.min(xrange.to, xrange.axis.max);
  1522.                     yrange.from = Math.max(yrange.from, yrange.axis.min);
  1523.                     yrange.to = Math.min(yrange.to, yrange.axis.max);
  1524.  
  1525.                     if (xrange.from == xrange.to && yrange.from == yrange.to)
  1526.                         continue;
  1527.  
  1528.                     // then draw
  1529.                     xrange.from = xrange.axis.p2c(xrange.from);
  1530.                     xrange.to = xrange.axis.p2c(xrange.to);
  1531.                     yrange.from = yrange.axis.p2c(yrange.from);
  1532.                     yrange.to = yrange.axis.p2c(yrange.to);
  1533.                    
  1534.                     if (xrange.from == xrange.to || yrange.from == yrange.to) {
  1535.                         // draw line
  1536.                         ctx.beginPath();
  1537.                         ctx.strokeStyle = m.color || options.grid.markingsColor;
  1538.                         ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
  1539.                         ctx.moveTo(xrange.from, yrange.from);
  1540.                         ctx.lineTo(xrange.to, yrange.to);
  1541.                         ctx.stroke();
  1542.                     }
  1543.                     else {
  1544.                         // fill area
  1545.                         ctx.fillStyle = m.color || options.grid.markingsColor;
  1546.                         ctx.fillRect(xrange.from, yrange.to,
  1547.                                      xrange.to - xrange.from,
  1548.                                      yrange.from - yrange.to);
  1549.                     }
  1550.                 }
  1551.             }
  1552.            
  1553.             // draw the ticks
  1554.             var axes = allAxes(), bw = options.grid.borderWidth;
  1555.  
  1556.             for (var j = 0; j < axes.length; ++j) {
  1557.                 var axis = axes[j], box = axis.box,
  1558.                     t = axis.tickLength, x, y, xoff, yoff;
  1559.                 if (!axis.show || axis.ticks.length == 0)
  1560.                     continue
  1561.                
  1562.                 ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
  1563.                 ctx.lineWidth = 1;
  1564.  
  1565.                 // find the edges
  1566.                 if (axis.direction == "x") {
  1567.                     x = 0;
  1568.                     if (t == "full")
  1569.                         y = (axis.position == "top" ? 0 : plotHeight);
  1570.                     else
  1571.                         y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
  1572.                 }
  1573.                 else {
  1574.                     y = 0;
  1575.                     if (t == "full")
  1576.                         x = (axis.position == "left" ? 0 : plotWidth);
  1577.                     else
  1578.                         x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
  1579.                 }
  1580.                
  1581.                 // draw tick bar
  1582.                 if (!axis.innermost) {
  1583.                     ctx.beginPath();
  1584.                     xoff = yoff = 0;
  1585.                     if (axis.direction == "x")
  1586.                         xoff = plotWidth;
  1587.                     else
  1588.                         yoff = plotHeight;
  1589.                    
  1590.                     if (ctx.lineWidth == 1) {
  1591.                         x = Math.floor(x) + 0.5;
  1592.                         y = Math.floor(y) + 0.5;
  1593.                     }
  1594.  
  1595.                     ctx.moveTo(x, y);
  1596.                     ctx.lineTo(x + xoff, y + yoff);
  1597.                     ctx.stroke();
  1598.                 }
  1599.  
  1600.                 // draw ticks
  1601.                 ctx.beginPath();
  1602.                 for (i = 0; i < axis.ticks.length; ++i) {
  1603.                     var v = axis.ticks[i].v;
  1604.                    
  1605.                     xoff = yoff = 0;
  1606.  
  1607.                     if (v < axis.min || v > axis.max
  1608.                         // skip those lying on the axes if we got a border
  1609.                         || (t == "full" && bw > 0
  1610.                             && (v == axis.min || v == axis.max)))
  1611.                         continue;
  1612.  
  1613.                     if (axis.direction == "x") {
  1614.                         x = axis.p2c(v);
  1615.                         yoff = t == "full" ? -plotHeight : t;
  1616.                        
  1617.                         if (axis.position == "top")
  1618.                             yoff = -yoff;
  1619.                     }
  1620.                     else {
  1621.                         y = axis.p2c(v);
  1622.                         xoff = t == "full" ? -plotWidth : t;
  1623.                        
  1624.                         if (axis.position == "left")
  1625.                             xoff = -xoff;
  1626.                     }
  1627.  
  1628.                     if (ctx.lineWidth == 1) {
  1629.                         if (axis.direction == "x")
  1630.                             x = Math.floor(x) + 0.5;
  1631.                         else
  1632.                             y = Math.floor(y) + 0.5;
  1633.                     }
  1634.  
  1635.                     ctx.moveTo(x, y);
  1636.                     ctx.lineTo(x + xoff, y + yoff);
  1637.                 }
  1638.                
  1639.                 ctx.stroke();
  1640.             }
  1641.            
  1642.            
  1643.             // draw border
  1644.             if (bw) {
  1645.                 ctx.lineWidth = bw;
  1646.                 ctx.strokeStyle = options.grid.borderColor;
  1647.                 ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
  1648.             }
  1649.  
  1650.             ctx.restore();
  1651.         }
  1652.  
  1653.         function insertAxisLabels() {
  1654.             placeholder.find(".tickLabels").remove();
  1655.            
  1656.             var html = ['<div class="tickLabels" style="font-size:smaller">'];
  1657.  
  1658.             var axes = allAxes();
  1659.             for (var j = 0; j < axes.length; ++j) {
  1660.                 var axis = axes[j], box = axis.box;
  1661.                 if (!axis.show)
  1662.                     continue;
  1663.                 //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width +  'px;height:' + box.height + 'px"></div>')
  1664.                 html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
  1665.                 for (var i = 0; i < axis.ticks.length; ++i) {
  1666.                     var tick = axis.ticks[i];
  1667.                     if (!tick.label || tick.v < axis.min || tick.v > axis.max)
  1668.                         continue;
  1669.  
  1670.                     var pos = {}, align;
  1671.                    
  1672.                     if (axis.direction == "x") {
  1673.                         align = "center";
  1674.                         pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2);
  1675.                         if (axis.position == "bottom")
  1676.                             pos.top = box.top + box.padding;
  1677.                         else
  1678.                             pos.bottom = canvasHeight - (box.top + box.height - box.padding);
  1679.                     }
  1680.                     else {
  1681.                         pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2);
  1682.                         if (axis.position == "left") {
  1683.                             pos.right = canvasWidth - (box.left + box.width - box.padding)
  1684.                             align = "right";
  1685.                         }
  1686.                         else {
  1687.                             pos.left = box.left + box.padding;
  1688.                             align = "left";
  1689.                         }
  1690.                     }
  1691.  
  1692.                     pos.width = axis.labelWidth;
  1693.  
  1694.                     var style = ["position:absolute", "text-align:" + align ];
  1695.                     for (var a in pos)
  1696.                         style.push(a + ":" + pos[a] + "px")
  1697.                    
  1698.                     html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
  1699.                 }
  1700.                 html.push('</div>');
  1701.             }
  1702.  
  1703.             html.push('</div>');
  1704.  
  1705.             placeholder.append(html.join(""));
  1706.         }
  1707.  
  1708.         function drawSeries(series) {
  1709.             if (series.lines.show)
  1710.                 drawSeriesLines(series);
  1711.             if (series.bars.show)
  1712.                 drawSeriesBars(series);
  1713.             if (series.points.show)
  1714.                 drawSeriesPoints(series);
  1715.         }
  1716.        
  1717.         function drawSeriesLines(series) {
  1718.             function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
  1719.                 var points = datapoints.points,
  1720.                     ps = datapoints.pointsize,
  1721.                     prevx = null, prevy = null;
  1722.                
  1723.                 ctx.beginPath();
  1724.                 for (var i = ps; i < points.length; i += ps) {
  1725.                     var x1 = points[i - ps], y1 = points[i - ps + 1],
  1726.                         x2 = points[i], y2 = points[i + 1];
  1727.                    
  1728.                     if (x1 == null || x2 == null)
  1729.                         continue;
  1730.  
  1731.                     // clip with ymin
  1732.                     if (y1 <= y2 && y1 < axisy.min) {
  1733.                         if (y2 < axisy.min)
  1734.                             continue;   // line segment is outside
  1735.                         // compute new intersection point
  1736.                         x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1737.                         y1 = axisy.min;
  1738.                     }
  1739.                     else if (y2 <= y1 && y2 < axisy.min) {
  1740.                         if (y1 < axisy.min)
  1741.                             continue;
  1742.                         x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1743.                         y2 = axisy.min;
  1744.                     }
  1745.  
  1746.                     // clip with ymax
  1747.                     if (y1 >= y2 && y1 > axisy.max) {
  1748.                         if (y2 > axisy.max)
  1749.                             continue;
  1750.                         x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1751.                         y1 = axisy.max;
  1752.                     }
  1753.                     else if (y2 >= y1 && y2 > axisy.max) {
  1754.                         if (y1 > axisy.max)
  1755.                             continue;
  1756.                         x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1757.                         y2 = axisy.max;
  1758.                     }
  1759.  
  1760.                     // clip with xmin
  1761.                     if (x1 <= x2 && x1 < axisx.min) {
  1762.                         if (x2 < axisx.min)
  1763.                             continue;
  1764.                         y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1765.                         x1 = axisx.min;
  1766.                     }
  1767.                     else if (x2 <= x1 && x2 < axisx.min) {
  1768.                         if (x1 < axisx.min)
  1769.                             continue;
  1770.                         y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1771.                         x2 = axisx.min;
  1772.                     }
  1773.  
  1774.                     // clip with xmax
  1775.                     if (x1 >= x2 && x1 > axisx.max) {
  1776.                         if (x2 > axisx.max)
  1777.                             continue;
  1778.                         y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1779.                         x1 = axisx.max;
  1780.                     }
  1781.                     else if (x2 >= x1 && x2 > axisx.max) {
  1782.                         if (x1 > axisx.max)
  1783.                             continue;
  1784.                         y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1785.                         x2 = axisx.max;
  1786.                     }
  1787.  
  1788.                     if (x1 != prevx || y1 != prevy)
  1789.                         ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
  1790.                    
  1791.                     prevx = x2;
  1792.                     prevy = y2;
  1793.                     ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
  1794.                 }
  1795.                 ctx.stroke();
  1796.             }
  1797.  
  1798.             function plotLineArea(datapoints, axisx, axisy) {
  1799.                 var points = datapoints.points,
  1800.                     ps = datapoints.pointsize,
  1801.                     bottom = Math.min(Math.max(0, axisy.min), axisy.max),
  1802.                     i = 0, top, areaOpen = false,
  1803.                     ypos = 1, segmentStart = 0, segmentEnd = 0;
  1804.  
  1805.                 // we process each segment in two turns, first forward
  1806.                 // direction to sketch out top, then once we hit the
  1807.                 // end we go backwards to sketch the bottom
  1808.                 while (true) {
  1809.                     if (ps > 0 && i > points.length + ps)
  1810.                         break;
  1811.  
  1812.                     i += ps; // ps is negative if going backwards
  1813.  
  1814.                     var x1 = points[i - ps],
  1815.                         y1 = points[i - ps + ypos],
  1816.                         x2 = points[i], y2 = points[i + ypos];
  1817.  
  1818.                     if (areaOpen) {
  1819.                         if (ps > 0 && x1 != null && x2 == null) {
  1820.                             // at turning point
  1821.                             segmentEnd = i;
  1822.                             ps = -ps;
  1823.                             ypos = 2;
  1824.                             continue;
  1825.                         }
  1826.  
  1827.                         if (ps < 0 && i == segmentStart + ps) {
  1828.                             // done with the reverse sweep
  1829.                             ctx.fill();
  1830.                             areaOpen = false;
  1831.                             ps = -ps;
  1832.                             ypos = 1;
  1833.                             i = segmentStart = segmentEnd + ps;
  1834.                             continue;
  1835.                         }
  1836.                     }
  1837.  
  1838.                     if (x1 == null || x2 == null)
  1839.                         continue;
  1840.  
  1841.                     // clip x values
  1842.                    
  1843.                     // clip with xmin
  1844.                     if (x1 <= x2 && x1 < axisx.min) {
  1845.                         if (x2 < axisx.min)
  1846.                             continue;
  1847.                         y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1848.                         x1 = axisx.min;
  1849.                     }
  1850.                     else if (x2 <= x1 && x2 < axisx.min) {
  1851.                         if (x1 < axisx.min)
  1852.                             continue;
  1853.                         y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1854.                         x2 = axisx.min;
  1855.                     }
  1856.  
  1857.                     // clip with xmax
  1858.                     if (x1 >= x2 && x1 > axisx.max) {
  1859.                         if (x2 > axisx.max)
  1860.                             continue;
  1861.                         y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1862.                         x1 = axisx.max;
  1863.                     }
  1864.                     else if (x2 >= x1 && x2 > axisx.max) {
  1865.                         if (x1 > axisx.max)
  1866.                             continue;
  1867.                         y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1868.                         x2 = axisx.max;
  1869.                     }
  1870.  
  1871.                     if (!areaOpen) {
  1872.                         // open area
  1873.                         ctx.beginPath();
  1874.                         ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
  1875.                         areaOpen = true;
  1876.                     }
  1877.                    
  1878.                     // now first check the case where both is outside
  1879.                     if (y1 >= axisy.max && y2 >= axisy.max) {
  1880.                         ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
  1881.                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
  1882.                         continue;
  1883.                     }
  1884.                     else if (y1 <= axisy.min && y2 <= axisy.min) {
  1885.                         ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
  1886.                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
  1887.                         continue;
  1888.                     }
  1889.                    
  1890.                     // else it's a bit more complicated, there might
  1891.                     // be a flat maxed out rectangle first, then a
  1892.                     // triangular cutout or reverse; to find these
  1893.                     // keep track of the current x values
  1894.                     var x1old = x1, x2old = x2;
  1895.  
  1896.                     // clip the y values, without shortcutting, we
  1897.                     // go through all cases in turn
  1898.                    
  1899.                     // clip with ymin
  1900.                     if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
  1901.                         x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1902.                         y1 = axisy.min;
  1903.                     }
  1904.                     else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
  1905.                         x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1906.                         y2 = axisy.min;
  1907.                     }
  1908.  
  1909.                     // clip with ymax
  1910.                     if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
  1911.                         x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1912.                         y1 = axisy.max;
  1913.                     }
  1914.                     else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
  1915.                         x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1916.                         y2 = axisy.max;
  1917.                     }
  1918.  
  1919.                     // if the x value was changed we got a rectangle
  1920.                     // to fill
  1921.                     if (x1 != x1old) {
  1922.                         ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
  1923.                         // it goes to (x1, y1), but we fill that below
  1924.                     }
  1925.                    
  1926.                     // fill triangular section, this sometimes result
  1927.                     // in redundant points if (x1, y1) hasn't changed
  1928.                     // from previous line to, but we just ignore that
  1929.                     ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
  1930.                     ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  1931.  
  1932.                     // fill the other rectangle if it's there
  1933.                     if (x2 != x2old) {
  1934.                         ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  1935.                         ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
  1936.                     }
  1937.                 }
  1938.             }
  1939.  
  1940.             ctx.save();
  1941.             ctx.translate(plotOffset.left, plotOffset.top);
  1942.             ctx.lineJoin = "round";
  1943.  
  1944.             var lw = series.lines.lineWidth,
  1945.                 sw = series.shadowSize;
  1946.             // FIXME: consider another form of shadow when filling is turned on
  1947.             if (lw > 0 && sw > 0) {
  1948.                 // draw shadow as a thick and thin line with transparency
  1949.                 ctx.lineWidth = sw;
  1950.                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
  1951.                 // position shadow at angle from the mid of line
  1952.                 var angle = Math.PI/18;
  1953.                 plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
  1954.                 ctx.lineWidth = sw/2;
  1955.                 plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
  1956.             }
  1957.  
  1958.             ctx.lineWidth = lw;
  1959.             ctx.strokeStyle = series.color;
  1960.             var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
  1961.             if (fillStyle) {
  1962.                 ctx.fillStyle = fillStyle;
  1963.                 plotLineArea(series.datapoints, series.xaxis, series.yaxis);
  1964.             }
  1965.  
  1966.             if (lw > 0)
  1967.                 plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
  1968.             ctx.restore();
  1969.         }
  1970.  
  1971.         function drawSeriesPoints(series) {
  1972.             function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
  1973.                 var points = datapoints.points, ps = datapoints.pointsize;
  1974.  
  1975.                 for (var i = 0; i < points.length; i += ps) {
  1976.                     var x = points[i], y = points[i + 1];
  1977.                     if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
  1978.                         continue;
  1979.                    
  1980.                     ctx.beginPath();
  1981.                     x = axisx.p2c(x);
  1982.                     y = axisy.p2c(y) + offset;
  1983.                     if (symbol == "circle")
  1984.                         ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
  1985.                     else
  1986.                         symbol(ctx, x, y, radius, shadow);
  1987.                     ctx.closePath();
  1988.                    
  1989.                     if (fillStyle) {
  1990.                         ctx.fillStyle = fillStyle;
  1991.                         ctx.fill();
  1992.                     }
  1993.                     ctx.stroke();
  1994.                 }
  1995.             }
  1996.            
  1997.             ctx.save();
  1998.             ctx.translate(plotOffset.left, plotOffset.top);
  1999.  
  2000.             var lw = series.points.lineWidth,
  2001.                 sw = series.shadowSize,
  2002.                 radius = series.points.radius,
  2003.                 symbol = series.points.symbol;
  2004.             if (lw > 0 && sw > 0) {
  2005.                 // draw shadow in two steps
  2006.                 var w = sw / 2;
  2007.                 ctx.lineWidth = w;
  2008.                 ctx.strokeStyle = "rgba(0,0,0,0.1)";
  2009.                 plotPoints(series.datapoints, radius, null, w + w/2, true,
  2010.                            series.xaxis, series.yaxis, symbol);
  2011.  
  2012.                 ctx.strokeStyle = "rgba(0,0,0,0.2)";
  2013.                 plotPoints(series.datapoints, radius, null, w/2, true,
  2014.                            series.xaxis, series.yaxis, symbol);
  2015.             }
  2016.  
  2017.             ctx.lineWidth = lw;
  2018.             ctx.strokeStyle = series.color;
  2019.             plotPoints(series.datapoints, radius,
  2020.                        getFillStyle(series.points, series.color), 0, false,
  2021.                        series.xaxis, series.yaxis, symbol);
  2022.             ctx.restore();
  2023.         }
  2024.  
  2025.         function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
  2026.             var left, right, bottom, top,
  2027.                 drawLeft, drawRight, drawTop, drawBottom,
  2028.                 tmp;
  2029.  
  2030.             // in horizontal mode, we start the bar from the left
  2031.             // instead of from the bottom so it appears to be
  2032.             // horizontal rather than vertical
  2033.             if (horizontal) {
  2034.                 drawBottom = drawRight = drawTop = true;
  2035.                 drawLeft = false;
  2036.                 left = b;
  2037.                 right = x;
  2038.                 top = y + barLeft;
  2039.                 bottom = y + barRight;
  2040.  
  2041.                 // account for negative bars
  2042.                 if (right < left) {
  2043.                     tmp = right;
  2044.                     right = left;
  2045.                     left = tmp;
  2046.                     drawLeft = true;
  2047.                     drawRight = false;
  2048.                 }
  2049.             }
  2050.             else {
  2051.                 drawLeft = drawRight = drawTop = true;
  2052.                 drawBottom = false;
  2053.                 left = x + barLeft;
  2054.                 right = x + barRight;
  2055.                 bottom = b;
  2056.                 top = y;
  2057.  
  2058.                 // account for negative bars
  2059.                 if (top < bottom) {
  2060.                     tmp = top;
  2061.                     top = bottom;
  2062.                     bottom = tmp;
  2063.                     drawBottom = true;
  2064.                     drawTop = false;
  2065.                 }
  2066.             }
  2067.            
  2068.             // clip
  2069.             if (right < axisx.min || left > axisx.max ||
  2070.                 top < axisy.min || bottom > axisy.max)
  2071.                 return;
  2072.            
  2073.             if (left < axisx.min) {
  2074.                 left = axisx.min;
  2075.                 drawLeft = false;
  2076.             }
  2077.  
  2078.             if (right > axisx.max) {
  2079.                 right = axisx.max;
  2080.                 drawRight = false;
  2081.             }
  2082.  
  2083.             if (bottom < axisy.min) {
  2084.                 bottom = axisy.min;
  2085.                 drawBottom = false;
  2086.             }
  2087.            
  2088.             if (top > axisy.max) {
  2089.                 top = axisy.max;
  2090.                 drawTop = false;
  2091.             }
  2092.  
  2093.             left = axisx.p2c(left);
  2094.             bottom = axisy.p2c(bottom);
  2095.             right = axisx.p2c(right);
  2096.             top = axisy.p2c(top);
  2097.            
  2098.             // fill the bar
  2099.             if (fillStyleCallback) {
  2100.                 c.beginPath();
  2101.                 c.moveTo(left, bottom);
  2102.                 c.lineTo(left, top);
  2103.                 c.lineTo(right, top);
  2104.                 c.lineTo(right, bottom);
  2105.                 c.fillStyle = fillStyleCallback(bottom, top);
  2106.                 c.fill();
  2107.             }
  2108.  
  2109.             // draw outline
  2110.             if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
  2111.                 c.beginPath();
  2112.  
  2113.                 // FIXME: inline moveTo is buggy with excanvas
  2114.                 c.moveTo(left, bottom + offset);
  2115.                 if (drawLeft)
  2116.                     c.lineTo(left, top + offset);
  2117.                 else
  2118.                     c.moveTo(left, top + offset);
  2119.                 if (drawTop)
  2120.                     c.lineTo(right, top + offset);
  2121.                 else
  2122.                     c.moveTo(right, top + offset);
  2123.                 if (drawRight)
  2124.                     c.lineTo(right, bottom + offset);
  2125.                 else
  2126.                     c.moveTo(right, bottom + offset);
  2127.                 if (drawBottom)
  2128.                     c.lineTo(left, bottom + offset);
  2129.                 else
  2130.                     c.moveTo(left, bottom + offset);
  2131.                 c.stroke();
  2132.             }
  2133.         }
  2134.        
  2135.         function drawSeriesBars(series) {
  2136.             function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
  2137.                 var points = datapoints.points, ps = datapoints.pointsize;
  2138.                
  2139.                 for (var i = 0; i < points.length; i += ps) {
  2140.                     if (points[i] == null)
  2141.                         continue;
  2142.                     drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
  2143.                 }
  2144.             }
  2145.  
  2146.             ctx.save();
  2147.             ctx.translate(plotOffset.left, plotOffset.top);
  2148.  
  2149.             // FIXME: figure out a way to add shadows (for instance along the right edge)
  2150.             ctx.lineWidth = series.bars.lineWidth;
  2151.             ctx.strokeStyle = series.color;
  2152.             var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
  2153.             var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
  2154.             plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
  2155.             ctx.restore();
  2156.         }
  2157.  
  2158.         function getFillStyle(filloptions, seriesColor, bottom, top) {
  2159.             var fill = filloptions.fill;
  2160.             if (!fill)
  2161.                 return null;
  2162.  
  2163.             if (filloptions.fillColor)
  2164.                 return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
  2165.            
  2166.             var c = $.color.parse(seriesColor);
  2167.             c.a = typeof fill == "number" ? fill : 0.4;
  2168.             c.normalize();
  2169.             return c.toString();
  2170.         }
  2171.        
  2172.         function insertLegend() {
  2173.             placeholder.find(".legend").remove();
  2174.  
  2175.             if (!options.legend.show)
  2176.                 return;
  2177.            
  2178.             var fragments = [], rowStarted = false,
  2179.                 lf = options.legend.labelFormatter, s, label;
  2180.             for (var i = 0; i < series.length; ++i) {
  2181.                 s = series[i];
  2182.                 label = s.label;
  2183.                 if (!label)
  2184.                     continue;
  2185.                
  2186.                 if (i % options.legend.noColumns == 0) {
  2187.                     if (rowStarted)
  2188.                         fragments.push('</tr>');
  2189.                     fragments.push('<tr>');
  2190.                     rowStarted = true;
  2191.                 }
  2192.  
  2193.                 if (lf)
  2194.                     label = lf(label, s);
  2195.                
  2196.                 fragments.push(
  2197.                     '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
  2198.                     '<td class="legendLabel">' + label + '</td>');
  2199.             }
  2200.             if (rowStarted)
  2201.                 fragments.push('</tr>');
  2202.            
  2203.             if (fragments.length == 0)
  2204.                 return;
  2205.  
  2206.             var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
  2207.             if (options.legend.container != null)
  2208.                 $(options.legend.container).html(table);
  2209.             else {
  2210.                 var pos = "",
  2211.                     p = options.legend.position,
  2212.                     m = options.legend.margin;
  2213.                 if (m[0] == null)
  2214.                     m = [m, m];
  2215.                 if (p.charAt(0) == "n")
  2216.                     pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
  2217.                 else if (p.charAt(0) == "s")
  2218.                     pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
  2219.                 if (p.charAt(1) == "e")
  2220.                     pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
  2221.                 else if (p.charAt(1) == "w")
  2222.                     pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
  2223.                 var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
  2224.                 if (options.legend.backgroundOpacity != 0.0) {
  2225.                     // put in the transparent background
  2226.                     // separately to avoid blended labels and
  2227.                     // label boxes
  2228.                     var c = options.legend.backgroundColor;
  2229.                     if (c == null) {
  2230.                         c = options.grid.backgroundColor;
  2231.                         if (c && typeof c == "string")
  2232.                             c = $.color.parse(c);
  2233.                         else
  2234.                             c = $.color.extract(legend, 'background-color');
  2235.                         c.a = 1;
  2236.                         c = c.toString();
  2237.                     }
  2238.                     var div = legend.children();
  2239.                     $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
  2240.                 }
  2241.             }
  2242.         }
  2243.  
  2244.  
  2245.         // interactive features
  2246.        
  2247.         var highlights = [],
  2248.             redrawTimeout = null;
  2249.        
  2250.         // returns the data item the mouse is over, or null if none is found
  2251.         function findNearbyItem(mouseX, mouseY, seriesFilter) {
  2252.             var maxDistance = options.grid.mouseActiveRadius,
  2253.                 smallestDistance = maxDistance * maxDistance + 1,
  2254.                 item = null, foundPoint = false, i, j;
  2255.  
  2256.             for (i = series.length - 1; i >= 0; --i) {
  2257.                 if (!seriesFilter(series[i]))
  2258.                     continue;
  2259.                
  2260.                 var s = series[i],
  2261.                     axisx = s.xaxis,
  2262.                     axisy = s.yaxis,
  2263.                     points = s.datapoints.points,
  2264.                     ps = s.datapoints.pointsize,
  2265.                     mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
  2266.                     my = axisy.c2p(mouseY),
  2267.                     maxx = maxDistance / axisx.scale,
  2268.                     maxy = maxDistance / axisy.scale;
  2269.  
  2270.                 // with inverse transforms, we can't use the maxx/maxy
  2271.                 // optimization, sadly
  2272.                 if (axisx.options.inverseTransform)
  2273.                     maxx = Number.MAX_VALUE;
  2274.                 if (axisy.options.inverseTransform)
  2275.                     maxy = Number.MAX_VALUE;
  2276.                
  2277.                 if (s.lines.show || s.points.show) {
  2278.                     for (j = 0; j < points.length; j += ps) {
  2279.                         var x = points[j], y = points[j + 1];
  2280.                         if (x == null)
  2281.                             continue;
  2282.                        
  2283.                         // For points and lines, the cursor must be within a
  2284.                         // certain distance to the data point
  2285.                         if (x - mx > maxx || x - mx < -maxx ||
  2286.                             y - my > maxy || y - my < -maxy)
  2287.                             continue;
  2288.  
  2289.                         // We have to calculate distances in pixels, not in
  2290.                         // data units, because the scales of the axes may be different
  2291.                         var dx = Math.abs(axisx.p2c(x) - mouseX),
  2292.                             dy = Math.abs(axisy.p2c(y) - mouseY),
  2293.                             dist = dx * dx + dy * dy; // we save the sqrt
  2294.  
  2295.                         // use <= to ensure last point takes precedence
  2296.                         // (last generally means on top of)
  2297.                         if (dist < smallestDistance) {
  2298.                             smallestDistance = dist;
  2299.                             item = [i, j / ps];
  2300.                         }
  2301.                     }
  2302.                 }
  2303.                    
  2304.                 if (s.bars.show && !item) { // no other point can be nearby
  2305.                     var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
  2306.                         barRight = barLeft + s.bars.barWidth;
  2307.                    
  2308.                     for (j = 0; j < points.length; j += ps) {
  2309.                         var x = points[j], y = points[j + 1], b = points[j + 2];
  2310.                         if (x == null)
  2311.                             continue;
  2312.  
  2313.                         // for a bar graph, the cursor must be inside the bar
  2314.                         if (series[i].bars.horizontal ?
  2315.                             (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
  2316.                              my >= y + barLeft && my <= y + barRight) :
  2317.                             (mx >= x + barLeft && mx <= x + barRight &&
  2318.                              my >= Math.min(b, y) && my <= Math.max(b, y)))
  2319.                                 item = [i, j / ps];
  2320.                     }
  2321.                 }
  2322.             }
  2323.  
  2324.             if (item) {
  2325.                 i = item[0];
  2326.                 j = item[1];
  2327.                 ps = series[i].datapoints.pointsize;
  2328.                
  2329.                 return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
  2330.                          dataIndex: j,
  2331.                          series: series[i],
  2332.                          seriesIndex: i };
  2333.             }
  2334.            
  2335.             return null;
  2336.         }
  2337.  
  2338.         function onMouseMove(e) {
  2339.             if (options.grid.hoverable)
  2340.                 triggerClickHoverEvent("plothover", e,
  2341.                                        function (s) { return s["hoverable"] != false; });
  2342.         }
  2343.  
  2344.         function onMouseLeave(e) {
  2345.             if (options.grid.hoverable)
  2346.                 triggerClickHoverEvent("plothover", e,
  2347.                                        function (s) { return false; });
  2348.         }
  2349.  
  2350.         function onClick(e) {
  2351.             triggerClickHoverEvent("plotclick", e,
  2352.                                    function (s) { return s["clickable"] != false; });
  2353.         }
  2354.  
  2355.         // trigger click or hover event (they send the same parameters
  2356.         // so we share their code)
  2357.         function triggerClickHoverEvent(eventname, event, seriesFilter) {
  2358.             var offset = eventHolder.offset(),
  2359.                 canvasX = event.pageX - offset.left - plotOffset.left,
  2360.                 canvasY = event.pageY - offset.top - plotOffset.top,
  2361.             pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
  2362.  
  2363.             pos.pageX = event.pageX;
  2364.             pos.pageY = event.pageY;
  2365.  
  2366.             var item = findNearbyItem(canvasX, canvasY, seriesFilter);
  2367.  
  2368.             if (item) {
  2369.                 // fill in mouse pos for any listeners out there
  2370.                 item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
  2371.                 item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
  2372.             }
  2373.  
  2374.             if (options.grid.autoHighlight) {
  2375.                 // clear auto-highlights
  2376.                 for (var i = 0; i < highlights.length; ++i) {
  2377.                     var h = highlights[i];
  2378.                     if (h.auto == eventname &&
  2379.                         !(item && h.series == item.series &&
  2380.                           h.point[0] == item.datapoint[0] &&
  2381.                           h.point[1] == item.datapoint[1]))
  2382.                         unhighlight(h.series, h.point);
  2383.                 }
  2384.                
  2385.                 if (item)
  2386.                     highlight(item.series, item.datapoint, eventname);
  2387.             }
  2388.            
  2389.             placeholder.trigger(eventname, [ pos, item ]);
  2390.         }
  2391.  
  2392.         function triggerRedrawOverlay() {
  2393.             if (!redrawTimeout)
  2394.                 redrawTimeout = setTimeout(drawOverlay, 30);
  2395.         }
  2396.  
  2397.         function drawOverlay() {
  2398.             redrawTimeout = null;
  2399.  
  2400.             // draw highlights
  2401.             octx.save();
  2402.             octx.clearRect(0, 0, canvasWidth, canvasHeight);
  2403.             octx.translate(plotOffset.left, plotOffset.top);
  2404.            
  2405.             var i, hi;
  2406.             for (i = 0; i < highlights.length; ++i) {
  2407.                 hi = highlights[i];
  2408.  
  2409.                 if (hi.series.bars.show)
  2410.                     drawBarHighlight(hi.series, hi.point);
  2411.                 else
  2412.                     drawPointHighlight(hi.series, hi.point);
  2413.             }
  2414.             octx.restore();
  2415.            
  2416.             executeHooks(hooks.drawOverlay, [octx]);
  2417.         }
  2418.        
  2419.         function highlight(s, point, auto) {
  2420.             if (typeof s == "number")
  2421.                 s = series[s];
  2422.  
  2423.             if (typeof point == "number") {
  2424.                 var ps = s.datapoints.pointsize;
  2425.                 point = s.datapoints.points.slice(ps * point, ps * (point + 1));
  2426.             }
  2427.  
  2428.             var i = indexOfHighlight(s, point);
  2429.             if (i == -1) {
  2430.                 highlights.push({ series: s, point: point, auto: auto });
  2431.  
  2432.                 triggerRedrawOverlay();
  2433.             }
  2434.             else if (!auto)
  2435.                 highlights[i].auto = false;
  2436.         }
  2437.            
  2438.         function unhighlight(s, point) {
  2439.             if (s == null && point == null) {
  2440.                 highlights = [];
  2441.                 triggerRedrawOverlay();
  2442.             }
  2443.            
  2444.             if (typeof s == "number")
  2445.                 s = series[s];
  2446.  
  2447.             if (typeof point == "number")
  2448.                 point = s.data[point];
  2449.  
  2450.             var i = indexOfHighlight(s, point);
  2451.             if (i != -1) {
  2452.                 highlights.splice(i, 1);
  2453.  
  2454.                 triggerRedrawOverlay();
  2455.             }
  2456.         }
  2457.        
  2458.         function indexOfHighlight(s, p) {
  2459.             for (var i = 0; i < highlights.length; ++i) {
  2460.                 var h = highlights[i];
  2461.                 if (h.series == s && h.point[0] == p[0]
  2462.                     && h.point[1] == p[1])
  2463.                     return i;
  2464.             }
  2465.             return -1;
  2466.         }
  2467.        
  2468.         function drawPointHighlight(series, point) {
  2469.             var x = point[0], y = point[1],
  2470.                 axisx = series.xaxis, axisy = series.yaxis;
  2471.            
  2472.             if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
  2473.                 return;
  2474.            
  2475.             var pointRadius = series.points.radius + series.points.lineWidth / 2;
  2476.             octx.lineWidth = pointRadius;
  2477.             octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
  2478.             var radius = 1.5 * pointRadius,
  2479.                 x = axisx.p2c(x),
  2480.                 y = axisy.p2c(y);
  2481.            
  2482.             octx.beginPath();
  2483.             if (series.points.symbol == "circle")
  2484.                 octx.arc(x, y, radius, 0, 2 * Math.PI, false);
  2485.             else
  2486.                 series.points.symbol(octx, x, y, radius, false);
  2487.             octx.closePath();
  2488.             octx.stroke();
  2489.         }
  2490.  
  2491.         function drawBarHighlight(series, point) {
  2492.             octx.lineWidth = series.bars.lineWidth;
  2493.             octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
  2494.             var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
  2495.             var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
  2496.             drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
  2497.                     0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
  2498.         }
  2499.  
  2500.         function getColorOrGradient(spec, bottom, top, defaultColor) {
  2501.             if (typeof spec == "string")
  2502.                 return spec;
  2503.             else {
  2504.                 // assume this is a gradient spec; IE currently only
  2505.                 // supports a simple vertical gradient properly, so that's
  2506.                 // what we support too
  2507.                 var gradient = ctx.createLinearGradient(0, top, 0, bottom);
  2508.                
  2509.                 for (var i = 0, l = spec.colors.length; i < l; ++i) {
  2510.                     var c = spec.colors[i];
  2511.                     if (typeof c != "string") {
  2512.                         var co = $.color.parse(defaultColor);
  2513.                         if (c.brightness != null)
  2514.                             co = co.scale('rgb', c.brightness)
  2515.                         if (c.opacity != null)
  2516.                             co.a *= c.opacity;
  2517.                         c = co.toString();
  2518.                     }
  2519.                     gradient.addColorStop(i / (l - 1), c);
  2520.                 }
  2521.                
  2522.                 return gradient;
  2523.             }
  2524.         }
  2525.     }
  2526.  
  2527.     $.plot = function(placeholder, data, options) {
  2528.         //var t0 = new Date();
  2529.         var plot = new Plot($(placeholder), data, options, $.plot.plugins);
  2530.         //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
  2531.         return plot;
  2532.     };
  2533.  
  2534.     $.plot.version = "0.7";
  2535.    
  2536.     $.plot.plugins = [];
  2537.  
  2538.     // returns a string with the date d formatted according to fmt
  2539.     $.plot.formatDate = function(d, fmt, monthNames) {
  2540.         var leftPad = function(n) {
  2541.             n = "" + n;
  2542.             return n.length == 1 ? "0" + n : n;
  2543.         };
  2544.        
  2545.         var r = [];
  2546.         var escape = false, padNext = false;
  2547.         var hours = d.getUTCHours();
  2548.         var isAM = hours < 12;
  2549.         if (monthNames == null)
  2550.             monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  2551.  
  2552.         if (fmt.search(/%p|%P/) != -1) {
  2553.             if (hours > 12) {
  2554.                 hours = hours - 12;
  2555.             } else if (hours == 0) {
  2556.                 hours = 12;
  2557.             }
  2558.         }
  2559.         for (var i = 0; i < fmt.length; ++i) {
  2560.             var c = fmt.charAt(i);
  2561.            
  2562.             if (escape) {
  2563.                 switch (c) {
  2564.                 case 'h': c = "" + hours; break;
  2565.                 case 'H': c = leftPad(hours); break;
  2566.                 case 'M': c = leftPad(d.getUTCMinutes()); break;
  2567.                 case 'S': c = leftPad(d.getUTCSeconds()); break;
  2568.                 case 'd': c = "" + d.getUTCDate(); break;
  2569.                 case 'm': c = "" + (d.getUTCMonth() + 1); break;
  2570.                 case 'y': c = "" + d.getUTCFullYear(); break;
  2571.                 case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
  2572.                 case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
  2573.                 case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
  2574.                 case '0': c = ""; padNext = true; break;
  2575.                 }
  2576.                 if (c && padNext) {
  2577.                     c = leftPad(c);
  2578.                     padNext = false;
  2579.                 }
  2580.                 r.push(c);
  2581.                 if (!padNext)
  2582.                     escape = false;
  2583.             }
  2584.             else {
  2585.                 if (c == "%")
  2586.                     escape = true;
  2587.                 else
  2588.                     r.push(c);
  2589.             }
  2590.         }
  2591.         return r.join("");
  2592.     };
  2593.    
  2594.     // round to nearby lower multiple of base
  2595.     function floorInBase(n, base) {
  2596.         return base * Math.floor(n / base);
  2597.     }
  2598.    
  2599. })(jQuery);

Paste is for source code and general debugging text.

Login or Register to edit, delete and keep track of your pastes and more.

Raw Paste

Login or Register to edit or fork this paste. It's free.