PageRenderTime 889ms CodeModel.GetById 222ms app.highlight 428ms RepoModel.GetById 135ms app.codeStats 1ms

/Demo/jquery/jquery-flot.js

http://github.com/mbebenita/Broadway
JavaScript | 2599 lines | 1973 code | 387 blank | 239 comment | 790 complexity | 1ee6ae398666c2f6f3a82a40c06ebc24 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   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 s…

Large files files are truncated, but you can click here to view the full file