html5 boiler plate
[scannr.git] / js / flotr2 / flotr2.amd.js
blob:a/js/flotr2/flotr2.amd.js -> blob:b/js/flotr2/flotr2.amd.js
--- a/js/flotr2/flotr2.amd.js
+++ b/js/flotr2/flotr2.amd.js
@@ -1,1 +1,5642 @@
-
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define(['bean', 'underscore'], function (bean, _) {
+            // Also create a global in case some scripts
+            // that are loaded still are looking for
+            // a global even when an AMD loader is in use.
+            return (root.Flotr = factory(bean, _));
+        });
+    } else {
+        // Browser globals
+        root.Flotr = factory(root.bean, root._);
+    }
+}(this, function (bean, _) {
+
+/**
+ * Flotr2 (c) 2012 Carl Sutherland
+ * MIT License
+ * Special thanks to:
+ * Flotr: http://code.google.com/p/flotr/ (fork)
+ * Flot: https://github.com/flot/flot (original fork)
+ */
+(function () {
+
+var
+  global = this,
+  previousFlotr = this.Flotr,
+  Flotr;
+
+Flotr = {
+  _: _,
+  bean: bean,
+  isIphone: /iphone/i.test(navigator.userAgent),
+  isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false),
+  
+  /**
+   * An object of the registered graph types. Use Flotr.addType(type, object)
+   * to add your own type.
+   */
+  graphTypes: {},
+  
+  /**
+   * The list of the registered plugins
+   */
+  plugins: {},
+  
+  /**
+   * Can be used to add your own chart type. 
+   * @param {String} name - Type of chart, like 'pies', 'bars' etc.
+   * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
+   */
+  addType: function(name, graphType){
+    Flotr.graphTypes[name] = graphType;
+    Flotr.defaultOptions[name] = graphType.options || {};
+    Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
+  },
+  
+  /**
+   * Can be used to add a plugin
+   * @param {String} name - The name of the plugin
+   * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
+   */
+  addPlugin: function(name, plugin){
+    Flotr.plugins[name] = plugin;
+    Flotr.defaultOptions[name] = plugin.options || {};
+  },
+  
+  /**
+   * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
+   * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
+   * @param {Element} el - element to insert the graph into
+   * @param {Object} data - an array or object of dataseries
+   * @param {Object} options - an object containing options
+   * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
+   * @return {Object} returns a new graph object and of course draws the graph.
+   */
+  draw: function(el, data, options, GraphKlass){  
+    GraphKlass = GraphKlass || Flotr.Graph;
+    return new GraphKlass(el, data, options);
+  },
+  
+  /**
+   * Recursively merges two objects.
+   * @param {Object} src - source object (likely the object with the least properties)
+   * @param {Object} dest - destination object (optional, object with the most properties)
+   * @return {Object} recursively merged Object
+   * @TODO See if we can't remove this.
+   */
+  merge: function(src, dest){
+    var i, v, result = dest || {};
+
+    for (i in src) {
+      v = src[i];
+      if (v && typeof(v) === 'object') {
+        if (v.constructor === Array) {
+          result[i] = this._.clone(v);
+        } else if (v.constructor !== RegExp && !this._.isElement(v)) {
+          result[i] = Flotr.merge(v, (dest ? dest[i] : undefined));
+        } else {
+          result[i] = v;
+        }
+      } else {
+        result[i] = v;
+      }
+    }
+
+    return result;
+  },
+  
+  /**
+   * Recursively clones an object.
+   * @param {Object} object - The object to clone
+   * @return {Object} the clone
+   * @TODO See if we can't remove this.
+   */
+  clone: function(object){
+    return Flotr.merge(object, {});
+  },
+  
+  /**
+   * Function calculates the ticksize and returns it.
+   * @param {Integer} noTicks - number of ticks
+   * @param {Integer} min - lower bound integer value for the current axis
+   * @param {Integer} max - upper bound integer value for the current axis
+   * @param {Integer} decimals - number of decimals for the ticks
+   * @return {Integer} returns the ticksize in pixels
+   */
+  getTickSize: function(noTicks, min, max, decimals){
+    var delta = (max - min) / noTicks,
+        magn = Flotr.getMagnitude(delta),
+        tickSize = 10,
+        norm = delta / magn; // Norm is between 1.0 and 10.0.
+        
+    if(norm < 1.5) tickSize = 1;
+    else if(norm < 2.25) tickSize = 2;
+    else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5);
+    else if(norm < 7.5) tickSize = 5;
+    
+    return tickSize * magn;
+  },
+  
+  /**
+   * Default tick formatter.
+   * @param {String, Integer} val - tick value integer
+   * @param {Object} axisOpts - the axis' options
+   * @return {String} formatted tick string
+   */
+  defaultTickFormatter: function(val, axisOpts){
+    return val+'';
+  },
+  
+  /**
+   * Formats the mouse tracker values.
+   * @param {Object} obj - Track value Object {x:..,y:..}
+   * @return {String} Formatted track string
+   */
+  defaultTrackFormatter: function(obj){
+    return '('+obj.x+', '+obj.y+')';
+  }, 
+  
+  /**
+   * Utility function to convert file size values in bytes to kB, MB, ...
+   * @param value {Number} - The value to convert
+   * @param precision {Number} - The number of digits after the comma (default: 2)
+   * @param base {Number} - The base (default: 1000)
+   */
+  engineeringNotation: function(value, precision, base){
+    var sizes =         ['Y','Z','E','P','T','G','M','k',''],
+        fractionSizes = ['y','z','a','f','p','n','µ','m',''],
+        total = sizes.length;
+
+    base = base || 1000;
+    precision = Math.pow(10, precision || 2);
+
+    if (value === 0) return 0;
+
+    if (value > 1) {
+      while (total-- && (value >= base)) value /= base;
+    }
+    else {
+      sizes = fractionSizes;
+      total = sizes.length;
+      while (total-- && (value < 1)) value *= base;
+    }
+
+    return (Math.round(value * precision) / precision) + sizes[total];
+  },
+  
+  /**
+   * Returns the magnitude of the input value.
+   * @param {Integer, Float} x - integer or float value
+   * @return {Integer, Float} returns the magnitude of the input value
+   */
+  getMagnitude: function(x){
+    return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
+  },
+  toPixel: function(val){
+    return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
+  },
+  toRad: function(angle){
+    return -angle * (Math.PI/180);
+  },
+  floorInBase: function(n, base) {
+    return base * Math.floor(n / base);
+  },
+  drawText: function(ctx, text, x, y, style) {
+    if (!ctx.fillText) {
+      ctx.drawText(text, x, y, style);
+      return;
+    }
+    
+    style = this._.extend({
+      size: Flotr.defaultOptions.fontSize,
+      color: '#000000',
+      textAlign: 'left',
+      textBaseline: 'bottom',
+      weight: 1,
+      angle: 0
+    }, style);
+    
+    ctx.save();
+    ctx.translate(x, y);
+    ctx.rotate(style.angle);
+    ctx.fillStyle = style.color;
+    ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+    ctx.textAlign = style.textAlign;
+    ctx.textBaseline = style.textBaseline;
+    ctx.fillText(text, 0, 0);
+    ctx.restore();
+  },
+  getBestTextAlign: function(angle, style) {
+    style = style || {textAlign: 'center', textBaseline: 'middle'};
+    angle += Flotr.getTextAngleFromAlign(style);
+    
+    if (Math.abs(Math.cos(angle)) > 10e-3) 
+      style.textAlign    = (Math.cos(angle) > 0 ? 'right' : 'left');
+    
+    if (Math.abs(Math.sin(angle)) > 10e-3) 
+      style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');
+    
+    return style;
+  },
+  alignTable: {
+    'right middle' : 0,
+    'right top'    : Math.PI/4,
+    'center top'   : Math.PI/2,
+    'left top'     : 3*(Math.PI/4),
+    'left middle'  : Math.PI,
+    'left bottom'  : -3*(Math.PI/4),
+    'center bottom': -Math.PI/2,
+    'right bottom' : -Math.PI/4,
+    'center middle': 0
+  },
+  getTextAngleFromAlign: function(style) {
+    return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
+  },
+  noConflict : function () {
+    global.Flotr = previousFlotr;
+    return this;
+  }
+};
+
+global.Flotr = Flotr;
+
+})();
+
+/**
+ * Flotr Defaults
+ */
+Flotr.defaultOptions = {
+  colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
+  ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping
+  title: null,             // => The graph's title
+  subtitle: null,          // => The graph's subtitle
+  shadowSize: 4,           // => size of the 'fake' shadow
+  defaultType: null,       // => default series type
+  HtmlText: true,          // => wether to draw the text using HTML or on the canvas
+  fontColor: '#545454',    // => default font color
+  fontSize: 7.5,           // => canvas' text font size
+  resolution: 1,           // => resolution of the graph, to have printer-friendly graphs !
+  parseFloat: true,        // => whether to preprocess data for floats (ie. if input is string)
+  preventDefault: true,    // => preventDefault by default for mobile events.  Turn off to enable scroll.
+  xaxis: {
+    ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
+    minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
+    showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
+    showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+    labelsAngle: 0,        // => labels' angle, in degrees
+    title: null,           // => axis title
+    titleAngle: 0,         // => axis title's angle, in degrees
+    noTicks: 5,            // => number of ticks for automagically generated ticks
+    minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
+    tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+    tickDecimals: null,    // => no. of decimals, null means auto
+    min: null,             // => min. value to show, null means set automatically
+    max: null,             // => max. value to show, null means set automatically
+    autoscale: false,      // => Turns autoscaling on with true
+    autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
+    color: null,           // => color of the ticks
+    mode: 'normal',        // => can be 'time' or 'normal'
+    timeFormat: null,
+    timeMode:'UTC',        // => For UTC time ('local' for local time).
+    timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year)
+    scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
+    base: Math.E,
+    titleAlign: 'center',
+    margin: true           // => Turn off margins with false
+  },
+  x2axis: {},
+  yaxis: {
+    ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
+    minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
+    showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
+    showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+    labelsAngle: 0,        // => labels' angle, in degrees
+    title: null,           // => axis title
+    titleAngle: 90,        // => axis title's angle, in degrees
+    noTicks: 5,            // => number of ticks for automagically generated ticks
+    minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
+    tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+    tickDecimals: null,    // => no. of decimals, null means auto
+    min: null,             // => min. value to show, null means set automatically
+    max: null,             // => max. value to show, null means set automatically
+    autoscale: false,      // => Turns autoscaling on with true
+    autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
+    color: null,           // => The color of the ticks
+    scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
+    base: Math.E,
+    titleAlign: 'center',
+    margin: true           // => Turn off margins with false
+  },
+  y2axis: {
+    titleAngle: 270
+  },
+  grid: {
+    color: '#545454',      // => primary color used for outline and labels
+    backgroundColor: null, // => null for transparent, else color
+    backgroundImage: null, // => background image. String or object with src, left and top
+    watermarkAlpha: 0.4,   // => 
+    tickColor: '#DDDDDD',  // => color used for the ticks
+    labelMargin: 3,        // => margin in pixels
+    verticalLines: true,   // => whether to show gridlines in vertical direction
+    minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir.
+    horizontalLines: true, // => whether to show gridlines in horizontal direction
+    minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir.
+    outlineWidth: 1,       // => width of the grid outline/border in pixels
+    outline : 'nsew',      // => walls of the outline to display
+    circular: false        // => if set to true, the grid will be circular, must be used when radars are drawn
+  },
+  mouse: {
+    track: false,          // => true to track the mouse, no tracking otherwise
+    trackAll: false,
+    position: 'se',        // => position of the value box (default south-east)
+    relative: false,       // => next to the mouse cursor
+    trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
+    margin: 5,             // => margin in pixels of the valuebox
+    lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series
+    trackDecimals: 1,      // => decimals for the track values
+    sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value
+    trackY: true,          // => whether or not to track the mouse in the y axis
+    radius: 3,             // => radius of the track point
+    fillColor: null,       // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
+    fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill 
+  }
+};
+
+/**
+ * Flotr Color
+ */
+
+(function () {
+
+var
+  _ = Flotr._;
+
+// Constructor
+function Color (r, g, b, a) {
+  this.rgba = ['r','g','b','a'];
+  var x = 4;
+  while(-1<--x){
+    this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+  }
+  this.normalize();
+}
+
+// Constants
+var COLOR_NAMES = {
+  aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],
+  brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],
+  darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],
+  darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],
+  darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],
+  khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],
+  lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],
+  maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],
+  violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]
+};
+
+Color.prototype = {
+  scale: function(rf, gf, bf, af){
+    var x = 4;
+    while (-1 < --x) {
+      if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x];
+    }
+    return this.normalize();
+  },
+  alpha: function(alpha) {
+    if (!_.isUndefined(alpha) && !_.isNull(alpha)) {
+      this.a = alpha;
+    }
+    return this.normalize();
+  },
+  clone: function(){
+    return new Color(this.r, this.b, this.g, this.a);
+  },
+  limit: function(val,minVal,maxVal){
+    return Math.max(Math.min(val, maxVal), minVal);
+  },
+  normalize: function(){
+    var limit = this.limit;
+    this.r = limit(parseInt(this.r, 10), 0, 255);
+    this.g = limit(parseInt(this.g, 10), 0, 255);
+    this.b = limit(parseInt(this.b, 10), 0, 255);
+    this.a = limit(this.a, 0, 1);
+    return this;
+  },
+  distance: function(color){
+    if (!color) return;
+    color = new Color.parse(color);
+    var dist = 0, x = 3;
+    while(-1<--x){
+      dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
+    }
+    return dist;
+  },
+  toString: function(){
+    return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
+  },
+  contrast: function () {
+    var
+      test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
+    return (test < 0.5 ? '#000000' : '#ffffff');
+  }
+};
+
+_.extend(Color, {
+  /**
+   * Parses a color string and returns a corresponding Color.
+   * The different tests are in order of probability to improve speed.
+   * @param {String, Color} str - string thats representing a color
+   * @return {Color} returns a Color object or false
+   */
+  parse: function(color){
+    if (color instanceof Color) return color;
+
+    var result;
+
+    // #a0b1c2
+    if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
+      return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
+
+    // rgb(num,num,num)
+    if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
+      return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
+  
+    // #fff
+    if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
+      return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
+  
+    // rgba(num,num,num,num)
+    if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
+      return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
+      
+    // rgb(num%,num%,num%)
+    if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
+      return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+  
+    // rgba(num%,num%,num%,num)
+    if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
+      return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+
+    // Otherwise, we're most likely dealing with a named color.
+    var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase();
+    if(name == 'transparent'){
+      return new Color(255, 255, 255, 0);
+    }
+    return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
+  },
+
+  /**
+   * Process color and options into color style.
+   */
+  processColor: function(color, options) {
+
+    var opacity = options.opacity;
+    if (!color) return 'rgba(0, 0, 0, 0)';
+    if (color instanceof Color) return color.alpha(opacity).toString();
+    if (_.isString(color)) return Color.parse(color).alpha(opacity).toString();
+    
+    var grad = color.colors ? color : {colors: color};
+    
+    if (!options.ctx) {
+      if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
+      return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString();
+    }
+    grad = _.extend({start: 'top', end: 'bottom'}, grad); 
+    
+    if (/top/i.test(grad.start))  options.x1 = 0;
+    if (/left/i.test(grad.start)) options.y1 = 0;
+    if (/bottom/i.test(grad.end)) options.x2 = 0;
+    if (/right/i.test(grad.end))  options.y2 = 0;
+
+    var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
+    for (i = 0; i < grad.colors.length; i++) {
+      c = grad.colors[i];
+      if (_.isArray(c)) {
+        stop = c[0];
+        c = c[1];
+      }
+      else stop = i / (grad.colors.length-1);
+      gradient.addColorStop(stop, Color.parse(c).alpha(opacity));
+    }
+    return gradient;
+  }
+});
+
+Flotr.Color = Color;
+
+})();
+
+/**
+ * Flotr Date
+ */
+Flotr.Date = {
+
+  set : function (date, name, mode, value) {
+    mode = mode || 'UTC';
+    name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name;
+    date[name](value);
+  },
+
+  get : function (date, name, mode) {
+    mode = mode || 'UTC';
+    name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name;
+    return date[name]();
+  },
+
+  format: function(d, format, mode) {
+    if (!d) return;
+
+    // We should maybe use an "official" date format spec, like PHP date() or ColdFusion 
+    // http://fr.php.net/manual/en/function.date.php
+    // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
+    var
+      get = this.get,
+      tokens = {
+        h: get(d, 'Hours', mode).toString(),
+        H: leftPad(get(d, 'Hours', mode)),
+        M: leftPad(get(d, 'Minutes', mode)),
+        S: leftPad(get(d, 'Seconds', mode)),
+        s: get(d, 'Milliseconds', mode),
+        d: get(d, 'Date', mode).toString(),
+        m: (get(d, 'Month', mode) + 1).toString(),
+        y: get(d, 'FullYear', mode).toString(),
+        b: Flotr.Date.monthNames[get(d, 'Month', mode)]
+      };
+
+    function leftPad(n){
+      n += '';
+      return n.length == 1 ? "0" + n : n;
+    }
+    
+    var r = [], c,
+        escape = false;
+    
+    for (var i = 0; i < format.length; ++i) {
+      c = format.charAt(i);
+      
+      if (escape) {
+        r.push(tokens[c] || c);
+        escape = false;
+      }
+      else if (c == "%")
+        escape = true;
+      else
+        r.push(c);
+    }
+    return r.join('');
+  },
+  getFormat: function(time, span) {
+    var tu = Flotr.Date.timeUnits;
+         if (time < tu.second) return "%h:%M:%S.%s";
+    else if (time < tu.minute) return "%h:%M:%S";
+    else if (time < tu.day)    return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M";
+    else if (time < tu.month)  return "%b %d";
+    else if (time < tu.year)   return (span < tu.year) ? "%b" : "%b %y";
+    else                       return "%y";
+  },
+  formatter: function (v, axis) {
+    var
+      options = axis.options,
+      scale = Flotr.Date.timeUnits[options.timeUnit],
+      d = new Date(v * scale);
+
+    // first check global format
+    if (axis.options.timeFormat)
+      return Flotr.Date.format(d, options.timeFormat, options.timeMode);
+    
+    var span = (axis.max - axis.min) * scale,
+        t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit];
+
+    return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode);
+  },
+  generator: function(axis) {
+
+     var
+      set       = this.set,
+      get       = this.get,
+      timeUnits = this.timeUnits,
+      spec      = this.spec,
+      options   = axis.options,
+      mode      = options.timeMode,
+      scale     = timeUnits[options.timeUnit],
+      min       = axis.min * scale,
+      max       = axis.max * scale,
+      delta     = (max - min) / options.noTicks,
+      ticks     = [],
+      tickSize  = axis.tickSize,
+      tickUnit,
+      formatter, i;
+
+    // Use custom formatter or time tick formatter
+    formatter = (options.tickFormatter === Flotr.defaultTickFormatter ?
+      this.formatter : options.tickFormatter
+    );
+
+    for (i = 0; i < spec.length - 1; ++i) {
+      var d = spec[i][0] * timeUnits[spec[i][1]];
+      if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize)
+        break;
+    }
+    tickSize = spec[i][0];
+    tickUnit = spec[i][1];
+
+    // special-case the possibility of several years
+    if (tickUnit == "year") {
+      tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0);
+
+      // Fix for 0.5 year case
+      if (tickSize == 0.5) {
+        tickUnit = "month";
+        tickSize = 6;
+      }
+    }
+
+    axis.tickUnit = tickUnit;
+    axis.tickSize = tickSize;
+
+    var step = tickSize * timeUnits[tickUnit];
+    d = new Date(min);
+
+    function setTick (name) {
+      set(d, name, mode, Flotr.floorInBase(
+        get(d, name, mode), tickSize
+      ));
+    }
+
+    switch (tickUnit) {
+      case "millisecond": setTick('Milliseconds'); break;
+      case "second": setTick('Seconds'); break;
+      case "minute": setTick('Minutes'); break;
+      case "hour": setTick('Hours'); break;
+      case "month": setTick('Month'); break;
+      case "year": setTick('FullYear'); break;
+    }
+    
+    // reset smaller components
+    if (step >= timeUnits.second)  set(d, 'Milliseconds', mode, 0);
+    if (step >= timeUnits.minute)  set(d, 'Seconds', mode, 0);
+    if (step >= timeUnits.hour)    set(d, 'Minutes', mode, 0);
+    if (step >= timeUnits.day)     set(d, 'Hours', mode, 0);
+    if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1);
+    if (step >= timeUnits.year)    set(d, 'Month', mode, 0);
+
+    var carry = 0, v = NaN, prev;
+    do {
+      prev = v;
+      v = d.getTime();
+      ticks.push({ v: v / scale, label: formatter(v / scale, axis) });
+      if (tickUnit == "month") {
+        if (tickSize < 1) {
+          /* a bit complicated - we'll divide the month up but we need to take care of fractions
+           so we don't end up in the middle of a day */
+          set(d, 'Date', mode, 1);
+          var start = d.getTime();
+          set(d, 'Month', mode, get(d, 'Month', mode) + 1);
+          var end = d.getTime();
+          d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize);
+          carry = get(d, 'Hours', mode);
+          set(d, 'Hours', mode, 0);
+        }
+        else
+          set(d, 'Month', mode, get(d, 'Month', mode) + tickSize);
+      }
+      else if (tickUnit == "year") {
+        set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize);
+      }
+      else
+        d.setTime(v + step);
+
+    } while (v < max && v != prev);
+
+    return ticks;
+  },
+  timeUnits: {
+    millisecond: 1,
+    second: 1000,
+    minute: 1000 * 60,
+    hour:   1000 * 60 * 60,
+    day:    1000 * 60 * 60 * 24,
+    month:  1000 * 60 * 60 * 24 * 30,
+    year:   1000 * 60 * 60 * 24 * 365.2425
+  },
+  // the allowed tick sizes, after 1 year we use an integer algorithm
+  spec: [
+    [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], 
+    [1, "second"],   [2, "second"],  [5, "second"], [10, "second"], [30, "second"], 
+    [1, "minute"],   [2, "minute"],  [5, "minute"], [10, "minute"], [30, "minute"], 
+    [1, "hour"],     [2, "hour"],    [4, "hour"],   [8, "hour"],    [12, "hour"],
+    [1, "day"],      [2, "day"],     [3, "day"],
+    [0.25, "month"], [0.5, "month"], [1, "month"],  [2, "month"],   [3, "month"], [6, "month"],
+    [1, "year"]
+  ],
+  monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+};
+
+(function () {
+
+var _ = Flotr._;
+
+Flotr.DOM = {
+  addClass: function(element, name){
+    var classList = (element.className ? element.className : '');
+      if (_.include(classList.split(/\s+/g), name)) return;
+    element.className = (classList ? classList + ' ' : '') + name;
+  },
+  /**
+   * Create an element.
+   */
+  create: function(tag){
+    return document.createElement(tag);
+  },
+  node: function(html) {
+    var div = Flotr.DOM.create('div'), n;
+    div.innerHTML = html;
+    n = div.children[0];
+    div.innerHTML = '';
+    return n;
+  },
+  /**
+   * Remove all children.
+   */
+  empty: function(element){
+    element.innerHTML = '';
+    /*
+    if (!element) return;
+    _.each(element.childNodes, function (e) {
+      Flotr.DOM.empty(e);
+      element.removeChild(e);
+    });
+    */
+  },
+  hide: function(element){
+    Flotr.DOM.setStyles(element, {display:'none'});
+  },
+  /**
+   * Insert a child.
+   * @param {Element} element
+   * @param {Element|String} Element or string to be appended.
+   */
+  insert: function(element, child){
+    if(_.isString(child))
+      element.innerHTML += child;
+    else if (_.isElement(child))
+      element.appendChild(child);
+  },
+  // @TODO find xbrowser implementation
+  opacity: function(element, opacity) {
+    element.style.opacity = opacity;
+  },
+  position: function(element, p){
+    if (!element.offsetParent)
+      return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)};
+
+    p = this.position(element.offsetParent);
+    p.left  += element.offsetLeft;
+    p.top   += element.offsetTop;
+    return p;
+  },
+  removeClass: function(element, name) {
+    var classList = (element.className ? element.className : '');
+    element.className = _.filter(classList.split(/\s+/g), function (c) {
+      if (c != name) return true; }
+    ).join(' ');
+  },
+  setStyles: function(element, o) {
+    _.each(o, function (value, key) {
+      element.style[key] = value;
+    });
+  },
+  show: function(element){
+    Flotr.DOM.setStyles(element, {display:''});
+  },
+  /**
+   * Return element size.
+   */
+  size: function(element){
+    return {
+      height : element.offsetHeight,
+      width : element.offsetWidth };
+  }
+};
+
+})();
+
+/**
+ * Flotr Event Adapter
+ */
+(function () {
+var
+  F = Flotr,
+  bean = F.bean;
+F.EventAdapter = {
+  observe: function(object, name, callback) {
+    bean.add(object, name, callback);
+    return this;
+  },
+  fire: function(object, name, args) {
+    bean.fire(object, name, args);
+    if (typeof(Prototype) != 'undefined')
+      Event.fire(object, name, args);
+    // @TODO Someone who uses mootools, add mootools adapter for existing applciations.
+    return this;
+  },
+  stopObserving: function(object, name, callback) {
+    bean.remove(object, name, callback);