text: /*!
    * bean.js - copyright Jacob Thornton 2011
    * https://github.com/fat/bean
    * MIT License
    * special thanks to:
    * dean edwards: http://dean.edwards.name/
    * dperini: https://github.com/dperini/nwevents
    * the entire mootools team: github.com/mootools/mootools-core
    */
  /*global module:true, define:true*/
  !function (name, context, definition) {
    if (typeof module !== 'undefined') module.exports = definition(name, context);
    else if (typeof define === 'function' && typeof define.amd  === 'object') define(definition);
    else context[name] = definition(name, context);
  }('bean', this, function (name, context) {
    var win = window
      , old = context[name]
      , overOut = /over|out/
      , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
      , nameRegex = /\..*/
      , addEvent = 'addEventListener'
      , attachEvent = 'attachEvent'
      , removeEvent = 'removeEventListener'
      , detachEvent = 'detachEvent'
      , doc = document || {}
      , root = doc.documentElement || {}
      , W3C_MODEL = root[addEvent]
      , eventSupport = W3C_MODEL ? addEvent : attachEvent
      , slice = Array.prototype.slice
      , mouseTypeRegex = /click|mouse|menu|drag|drop/i
      , touchTypeRegex = /^touch|^gesture/i
      , ONE = { one: 1 } // singleton for quick matching making add() do one()
  
      , nativeEvents = (function (hash, events, i) {
          for (i = 0; i < events.length; i++)
            hash[events[i]] = 1
          return hash
        })({}, (
            'click dblclick mouseup mousedown contextmenu ' +                  // mouse buttons
            'mousewheel DOMMouseScroll ' +                                     // mouse wheel
            'mouseover mouseout mousemove selectstart selectend ' +            // mouse movement
            'keydown keypress keyup ' +                                        // keyboard
            'orientationchange ' +                                             // mobile
            'focus blur change reset select submit ' +                         // form elements
            'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window
            'error abort scroll ' +                                            // misc
            (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
                         // that doesn't actually exist, so make sure we only do these on newer browsers
              'show ' +                                                          // mouse buttons
              'input invalid ' +                                                 // form elements
              'touchstart touchmove touchend touchcancel ' +                     // touch
              'gesturestart gesturechange gestureend ' +                         // gesture
              'message readystatechange pageshow pagehide popstate ' +           // window
              'hashchange offline online ' +                                     // window
              'afterprint beforeprint ' +                                        // printing
              'dragstart dragenter dragover dragleave drag drop dragend ' +      // dnd
              'loadstart progress suspend emptied stalled loadmetadata ' +       // media
              'loadeddata canplay canplaythrough playing waiting seeking ' +     // media
              'seeked ended durationchange timeupdate play pause ratechange ' +  // media
              'volumechange cuechange ' +                                        // media
              'checking noupdate downloading cached updateready obsolete ' +     // appcache
              '' : '')
          ).split(' ')
        )
  
      , customEvents = (function () {
          function isDescendant(parent, node) {
            while ((node = node.parentNode) !== null) {
              if (node === parent) return true
            }
            return false
          }
  
          function check(event) {
            var related = event.relatedTarget
            if (!related) return related === null
            return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related))
          }
  
          return {
              mouseenter: { base: 'mouseover', condition: check }
            , mouseleave: { base: 'mouseout', condition: check }
            , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
          }
        })()
  
      , fixEvent = (function () {
          var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ')
            , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' '))
            , keyProps = commonProps.concat('char charCode key keyCode'.split(' '))
            , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' '))
            , preventDefault = 'preventDefault'
            , createPreventDefault = function (event) {
                return function () {
                  if (event[preventDefault])
                    event[preventDefault]()
                  else
                    event.returnValue = false
                }
              }
            , stopPropagation = 'stopPropagation'
            , createStopPropagation = function (event) {
                return function () {
                  if (event[stopPropagation])
                    event[stopPropagation]()
                  else
                    event.cancelBubble = true
                }
              }
            , createStop = function (synEvent) {
                return function () {
                  synEvent[preventDefault]()
                  synEvent[stopPropagation]()
                  synEvent.stopped = true
                }
              }
            , copyProps = function (event, result, props) {
                var i, p
                for (i = props.length; i--;) {
                  p = props[i]
                  if (!(p in result) && p in event) result[p] = event[p]
                }
              }
  
          return function (event, isNative) {
            var result = { originalEvent: event, isNative: isNative }
            if (!event)
              return result
  
            var props
              , type = event.type
              , target = event.target || event.srcElement
  
            result[preventDefault] = createPreventDefault(event)
            result[stopPropagation] = createStopPropagation(event)
            result.stop = createStop(result)
            result.target = target && target.nodeType === 3 ? target.parentNode : target
  
            if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive
              if (type.indexOf('key') !== -1) {
                props = keyProps
                result.keyCode = event.which || event.keyCode
              } else if (mouseTypeRegex.test(type)) {
                props = mouseProps
                result.rightClick = event.which === 3 || event.button === 2
                result.pos = { x: 0, y: 0 }
                if (event.pageX || event.pageY) {
                  result.clientX = event.pageX
                  result.clientY = event.pageY
                } else if (event.clientX || event.clientY) {
                  result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
                  result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
                }
                if (overOut.test(type))
                  result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element']
              } else if (touchTypeRegex.test(type)) {
                props = touchProps
              }
              copyProps(event, result, props || commonProps)
            }
            return result
          }
        })()
  
        // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
      , targetElement = function (element, isNative) {
          return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
        }
  
        // we use one of these per listener, of any type
      , RegEntry = (function () {
          function entry(element, type, handler, original, namespaces) {
            this.element = element
            this.type = type
            this.handler = handler
            this.original = original
            this.namespaces = namespaces
            this.custom = customEvents[type]
            this.isNative = nativeEvents[type] && element[eventSupport]
            this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'
            this.customType = !W3C_MODEL && !this.isNative && type
            this.target = targetElement(element, this.isNative)
            this.eventSupport = this.target[eventSupport]
          }
  
          entry.prototype = {
              // given a list of namespaces, is our entry in any of them?
              inNamespaces: function (checkNamespaces) {
                var i, j
                if (!checkNamespaces)
                  return true
                if (!this.namespaces)
                  return false
                for (i = checkNamespaces.length; i--;) {
                  for (j = this.namespaces.length; j--;) {
                    if (checkNamespaces[i] === this.namespaces[j])
                      return true
                  }
                }
                return false
              }
  
              // match by element, original fn (opt), handler fn (opt)
            , matches: function (checkElement, checkOriginal, checkHandler) {
                return this.element === checkElement &&
                  (!checkOriginal || this.original === checkOriginal) &&
                  (!checkHandler || this.handler === checkHandler)
              }
          }
  
          return entry
        })()
  
      , registry = (function () {
          // our map stores arrays by event type, just because it's better than storing
          // everything in a single array. uses '$' as a prefix for the keys for safety
          var map = {}
  
            // generic functional search of our registry for matching listeners,
            // `fn` returns false to break out of the loop
            , forAll = function (element, type, original, handler, fn) {
                if (!type || type === '*') {
                  // search the whole registry
                  for (var t in map) {
                    if (t.charAt(0) === '$')
                      forAll(element, t.substr(1), original, handler, fn)
                  }
                } else {
                  var i = 0, l, list = map['$' + type], all = element === '*'
                  if (!list)
                    return
                  for (l = list.length; i < l; i++) {
                    if (all || list[i].matches(element, original, handler))
                      if (!fn(list[i], list, i, type))
                        return
                  }
                }
              }
  
            , has = function (element, type, original) {
                // we're not using forAll here simply because it's a bit slower and this
                // needs to be fast
                var i, list = map['$' + type]
                if (list) {
                  for (i = list.length; i--;) {
                    if (list[i].matches(element, original, null))
                      return true
                  }
                }
                return false
              }
  
            , get = function (element, type, original) {
                var entries = []
                forAll(element, type, original, null, function (entry) { return entries.push(entry) })
                return entries
              }
  
            , put = function (entry) {
                (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry)
                return entry
              }
  
            , del = function (entry) {
                forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) {
                  list.splice(i, 1)
                  if (list.length === 0)
                    delete map['$' + entry.type]
                  return false
                })
              }
  
              // dump all entries, used for onunload
            , entries = function () {
                var t, entries = []
                for (t in map) {
                  if (t.charAt(0) === '$')
                    entries = entries.concat(map[t])
                }
                return entries
              }
  
          return { has: has, get: get, put: put, del: del, entries: entries }
        })()
  
        // add and remove listeners to DOM elements
      , listener = W3C_MODEL ? function (element, type, fn, add) {
          element[add ? addEvent : removeEvent](type, fn, false)
        } : function (element, type, fn, add, custom) {
          if (custom && add && element['_on' + custom] === null)
            element['_on' + custom] = 0
          element[add ? attachEvent : detachEvent]('on' + type, fn)
        }
  
      , nativeHandler = function (element, fn, args) {
          return function (event) {
            event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true)
            return fn.apply(element, [event].concat(args))
          }
        }
  
      , customHandler = function (element, fn, type, condition, args, isNative) {
          return function (event) {
            if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) {
              if (event)
                event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative)
              fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args))
            }
          }
        }
  
      , once = function (rm, element, type, fn, originalFn) {
          // wrap the handler in a handler that does a remove as well
          return function () {
            rm(element, type, originalFn)
            fn.apply(this, arguments)
          }
        }
  
      , removeListener = function (element, orgType, handler, namespaces) {
          var i, l, entry
            , type = (orgType && orgType.replace(nameRegex, ''))
            , handlers = registry.get(element, type, handler)
  
          for (i = 0, l = handlers.length; i < l; i++) {
            if (handlers[i].inNamespaces(namespaces)) {
              if ((entry = handlers[i]).eventSupport)
                listener(entry.target, entry.eventType, entry.handler, false, entry.type)
              // TODO: this is problematic, we have a registry.get() and registry.del() that
              // both do registry searches so we waste cycles doing this. Needs to be rolled into
              // a single registry.forAll(fn) that removes while finding, but the catch is that
              // we'll be splicing the arrays that we're iterating over. Needs extra tests to
              // make sure we don't screw it up. @rvagg
              registry.del(entry)
            }
          }
        }
  
      , addListener = function (element, orgType, fn, originalFn, args) {
          var entry
            , type = orgType.replace(nameRegex, '')
            , namespaces = orgType.replace(namespaceRegex, '').split('.')
  
          if (registry.has(element, type, fn))
            return element // no dupe
          if (type === 'unload')
            fn = once(removeListener, element, type, fn, originalFn) // self clean-up
          if (customEvents[type]) {
            if (customEvents[type].condition)
              fn = customHandler(element, fn, type, customEvents[type].condition, true)
            type = customEvents[type].base || type
          }
          entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces))
          entry.handler = entry.isNative ?
            nativeHandler(element, entry.handler, args) :
            customHandler(element, entry.handler, type, false, args, false)
          if (entry.eventSupport)
            listener(entry.target, entry.eventType, entry.handler, true, entry.customType)
        }
  
      , del = function (selector, fn, $) {
          return function (e) {
            var target, i, array = typeof selector === 'string' ? $(selector, this) : selector
            for (target = e.target; target && target !== this; target = target.parentNode) {
              for (i = array.length; i--;) {
                if (array[i] === target) {
                  return fn.apply(target, arguments)
                }
              }
            }
          }
        }
  
      , remove = function (element, typeSpec, fn) {
          var k, m, type, namespaces, i
            , rm = removeListener
            , isString = typeSpec && typeof typeSpec === 'string'
  
          if (isString && typeSpec.indexOf(' ') > 0) {
            // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
            typeSpec = typeSpec.split(' ')
            for (i = typeSpec.length; i--;)
              remove(element, typeSpec[i], fn)
            return element
          }
          type = isString && typeSpec.replace(nameRegex, '')
          if (type && customEvents[type])
            type = customEvents[type].type
          if (!typeSpec || isString) {
            // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
            if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
              namespaces = namespaces.split('.')
            rm(element, type, fn, namespaces)
          } else if (typeof typeSpec === 'function') {
            // remove(el, fn)
            rm(element, null, typeSpec)
          } else {
            // remove(el, { t1: fn1, t2, fn2 })
            for (k in typeSpec) {
              if (typeSpec.hasOwnProperty(k))
                remove(element, k, typeSpec[k])
            }
          }
          return element
        }
  
      , add = function (element, events, fn, delfn, $) {
          var type, types, i, args
            , originalFn = fn
            , isDel = fn && typeof fn === 'string'
  
          if (events && !fn && typeof events === 'object') {
            for (type in events) {
              if (events.hasOwnProperty(type))
                add.apply(this, [ element, type, events[type] ])
            }
          } else {
            args = arguments.length > 3 ? slice.call(arguments, 3) : []
            types = (isDel ? fn : events).split(' ')
            isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
            // special case for one()
            this === ONE && (fn = once(remove, element, events, fn, originalFn))
            for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
          }
          return element
        }
  
      , one = function () {
          return add.apply(ONE, arguments)
        }
  
      , fireListener = W3C_MODEL ? function (isNative, type, element) {
          var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
          evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
          element.dispatchEvent(evt)
        } : function (isNative, type, element) {
          element = targetElement(element, isNative)
          // if not-native then we're using onpropertychange so we just increment a custom property
          isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
        }
  
      , fire = function (element, type, args) {
          var i, j, l, names, handlers
            , types = type.split(' ')
  
          for (i = types.length; i--;) {
            type = types[i].replace(nameRegex, '')
            if (names = types[i].replace(namespaceRegex, ''))
              names = names.split('.')
            if (!names && !args && element[eventSupport]) {
              fireListener(nativeEvents[type], type, element)
            } else {
              // non-native event, either because of a namespace, arguments or a non DOM element
              // iterate over all listeners and manually 'fire'
              handlers = registry.get(element, type)
              args = [false].concat(args)
              for (j = 0, l = handlers.length; j < l; j++) {
                if (handlers[j].inNamespaces(names))
                  handlers[j].handler.apply(element, args)
              }
            }
          }
          return element
        }
  
      , clone = function (element, from, type) {
          var i = 0
            , handlers = registry.get(from, type)
            , l = handlers.length
  
          for (;i < l; i++)
            handlers[i].original && add(element, handlers[i].type, handlers[i].original)
          return element
        }
  
      , bean = {
            add: add
          , one: one
          , remove: remove
          , clone: clone
          , fire: fire
          , noConflict: function () {
              context[name] = old
              return this
            }
        }
  
    if (win[attachEvent]) {
      // for IE, clean up on unload to avoid leaks
      var cleanup = function () {
        var i, entries = registry.entries()
        for (i in entries) {
          if (entries[i].type && entries[i].type !== 'unload')
            remove(entries[i].element, entries[i].type)
        }
        win[detachEvent]('onunload', cleanup)
        win.CollectGarbage && win.CollectGarbage()
      }
      win[attachEvent]('onunload', cleanup)
    }
  
    return bean
  });
  //     Underscore.js 1.1.7
  //     (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
  //     Underscore is freely distributable under the MIT license.
  //     Portions of Underscore are inspired or borrowed from Prototype,
  //     Oliver Steele's Functional, and John Resig's Micro-Templating.
  //     For all details and documentation:
  //     http://documentcloud.github.com/underscore
  
  (function() {
  
    // Baseline setup
    // --------------
  
    // Establish the root object, `window` in the browser, or `global` on the server.
    var root = this;
  
    // Save the previous value of the `_` variable.
    var previousUnderscore = root._;
  
    // Establish the object that gets returned to break out of a loop iteration.
    var breaker = {};
  
    // Save bytes in the minified (but not gzipped) version:
    var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
  
    // Create quick reference variables for speed access to core prototypes.
    var slice            = ArrayProto.slice,
        unshift          = ArrayProto.unshift,
        toString         = ObjProto.toString,
        hasOwnProperty   = ObjProto.hasOwnProperty;
  
    // All **ECMAScript 5** native function implementations that we hope to use
    // are declared here.
    var
      nativeForEach      = ArrayProto.forEach,
      nativeMap          = ArrayProto.map,
      nativeReduce       = ArrayProto.reduce,
      nativeReduceRight  = ArrayProto.reduceRight,
      nativeFilter       = ArrayProto.filter,
      nativeEvery        = ArrayProto.every,
      nativeSome         = ArrayProto.some,
      nativeIndexOf      = ArrayProto.indexOf,
      nativeLastIndexOf  = ArrayProto.lastIndexOf,
      nativeIsArray      = Array.isArray,
      nativeKeys         = Object.keys,
      nativeBind         = FuncProto.bind;
  
    // Create a safe reference to the Underscore object for use below.
    var _ = function(obj) { return new wrapper(obj); };
  
    // Export the Underscore object for **CommonJS**, with backwards-compatibility
    // for the old `require()` API. If we're not in CommonJS, add `_` to the
    // global object.
    if (typeof module !== 'undefined' && module.exports) {
      module.exports = _;
      _._ = _;
    } else {
      // Exported as a string, for Closure Compiler "advanced" mode.
      root['_'] = _;
    }
  
    // Current version.
    _.VERSION = '1.1.7';
  
    // Collection Functions
    // --------------------
  
    // The cornerstone, an `each` implementation, aka `forEach`.
    // Handles objects with the built-in `forEach`, arrays, and raw objects.
    // Delegates to **ECMAScript 5**'s native `forEach` if available.
    var each = _.each = _.forEach = function(obj, iterator, context) {
      if (obj == null) return;
      if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator, context);
      } else if (obj.length === +obj.length) {
        for (var i = 0, l = obj.length; i < l; i++) {
          if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
        }
      } else {
        for (var key in obj) {
          if (hasOwnProperty.call(obj, key)) {
            if (iterator.call(context, obj[key], key, obj) === breaker) return;
          }
        }
      }
    };
  
    // Return the results of applying the iterator to each element.
    // Delegates to **ECMAScript 5**'s native `map` if available.
    _.map = function(obj, iterator, context) {
      var results = [];
      if (obj == null) return results;
      if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
      each(obj, function(value, index, list) {
        results[results.length] = iterator.call(context, value, index, list);
      });
      return results;
    };
  
    // **Reduce** builds up a single result from a list of values, aka `inject`,
    // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
    _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
      var initial = memo !== void 0;
      if (obj == null) obj = [];
      if (nativeReduce && obj.reduce === nativeReduce) {
        if (context) iterator = _.bind(iterator, context);
        return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
      }
      each(obj, function(value, index, list) {
        if (!initial) {
          memo = value;
          initial = true;
        } else {
          memo = iterator.call(context, memo, value, index, list);
        }
      });
      if (!initial) throw new TypeError("Reduce of empty array with no initial value");
      return memo;
    };
  
    // The right-associative version of reduce, also known as `foldr`.
    // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
    _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
      if (obj == null) obj = [];
      if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
        if (context) iterator = _.bind(iterator, context);
        return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
      }
      var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
      return _.reduce(reversed, iterator, memo, context);
    };
  
    // Return the first value which passes a truth test. Aliased as `detect`.
    _.find = _.detect = function(obj, iterator, context) {
      var result;
      any(obj, function(value, index, list) {
        if (iterator.call(context, value, index, list)) {
          result = value;
          return true;
        }
      });
      return result;
    };
  
    // Return all the elements that pass a truth test.
    // Delegates to **ECMAScript 5**'s native `filter` if available.
    // Aliased as `select`.
    _.filter = _.select = function(obj, iterator, context) {
      var results = [];
      if (obj == null) return results;
      if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
      each(obj, function(value, index, list) {
        if (iterator.call(context, value, index, list)) results[results.length] = value;
      });
      return results;
    };
  
    // Return all the elements for which a truth test fails.
    _.reject = function(obj, iterator, context) {
      var results = [];
      if (obj == null) return results;
      each(obj, function(value, index, list) {
        if (!iterator.call(context, value, index, list)) results[results.length] = value;
      });
      return results;
    };
  
    // Determine whether all of the elements match a truth test.
    // Delegates to **ECMAScript 5**'s native `every` if available.
    // Aliased as `all`.
    _.every = _.all = function(obj, iterator, context) {
      var result = true;
      if (obj == null) return result;
      if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
      each(obj, function(value, index, list) {
        if (!(result = result && iterator.call(context, value, index, list))) return breaker;
      });
      return result;
    };
  
    // Determine if at least one element in the object matches a truth test.
    // Delegates to **ECMAScript 5**'s native `some` if available.
    // Aliased as `any`.
    var any = _.some = _.any = function(obj, iterator, context) {
      iterator = iterator || _.identity;
      var result = false;
      if (obj == null) return result;
      if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
      each(obj, function(value, index, list) {
        if (result |= iterator.call(context, value, index, list)) return breaker;
      });
      return !!result;
    };
  
    // Determine if a given value is included in the array or object using `===`.
    // Aliased as `contains`.
    _.include = _.contains = function(obj, target) {
      var found = false;
      if (obj == null) return found;
      if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
      any(obj, function(value) {
        if (found = value === target) return true;
      });
      return found;
    };
  
    // Invoke a method (with arguments) on every item in a collection.
    _.invoke = function(obj, method) {
      var args = slice.call(arguments, 2);
      return _.map(obj, function(value) {
        return (method.call ? method || value : value[method]).apply(value, args);
      });
    };
  
    // Convenience version of a common use case of `map`: fetching a property.
    _.pluck = function(obj, key) {
      return _.map(obj, function(value){ return value[key]; });
    };
  
    // Return the maximum element or (element-based computation).
    _.max = function(obj, iterator, context) {
      if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
      var result = {computed : -Infinity};
      each(obj, function(value, index, list) {
        var computed = iterator ? iterator.call(context, value, index, list) : value;
        computed >= result.computed && (result = {value : value, computed : computed});
      });
      return result.value;
    };
  
    // Return the minimum element (or element-based computation).
    _.min = function(obj, iterator, context) {
      if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
      var result = {computed : Infinity};
      each(obj, function(value, index, list) {
        var computed = iterator ? iterator.call(context, value, index, list) : value;
        computed < result.computed && (result = {value : value, computed : computed});
      });
      return result.value;
    };
  
    // Sort the object's values by a criterion produced by an iterator.
    _.sortBy = function(obj, iterator, context) {
      return _.pluck(_.map(obj, function(value, index, list) {
        return {
          value : value,
          criteria : iterator.call(context, value, index, list)
        };
      }).sort(function(left, right) {
        var a = left.criteria, b = right.criteria;
        return a < b ? -1 : a > b ? 1 : 0;
      }), 'value');
    };
  
    // Groups the object's values by a criterion produced by an iterator
    _.groupBy = function(obj, iterator) {
      var result = {};
      each(obj, function(value, index) {
        var key = iterator(value, index);
        (result[key] || (result[key] = [])).push(value);
      });
      return result;
    };
  
    // Use a comparator function to figure out at what index an object should
    // be inserted so as to maintain order. Uses binary search.
    _.sortedIndex = function(array, obj, iterator) {
      iterator || (iterator = _.identity);
      var low = 0, high = array.length;
      while (low < high) {
        var mid = (low + high) >> 1;
        iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
      }
      return low;
    };
  
    // Safely convert anything iterable into a real, live array.
    _.toArray = function(iterable) {
      if (!iterable)                return [];
      if (iterable.toArray)         return iterable.toArray();
      if (_.isArray(iterable))      return slice.call(iterable);
      if (_.isArguments(iterable))  return slice.call(iterable);
      return _.values(iterable);
    };
  
    // Return the number of elements in an object.
    _.size = function(obj) {
      return _.toArray(obj).length;
    };
  
    // Array Functions
    // ---------------
  
    // Get the first element of an array. Passing **n** will return the first N
    // values in the array. Aliased as `head`. The **guard** check allows it to work
    // with `_.map`.
    _.first = _.head = function(array, n, guard) {
      return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
    };
  
    // Returns everything but the first entry of the array. Aliased as `tail`.
    // Especially useful on the arguments object. Passing an **index** will return
    // the rest of the values in the array from that index onward. The **guard**
    // check allows it to work with `_.map`.
    _.rest = _.tail = function(array, index, guard) {
      return slice.call(array, (index == null) || guard ? 1 : index);
    };
  
    // Get the last element of an array.
    _.last = function(array) {
      return array[array.length - 1];
    };
  
    // Trim out all falsy values from an array.
    _.compact = function(array) {
      return _.filter(array, function(value){ return !!value; });
    };
  
    // Return a completely flattened version of an array.
    _.flatten = function(array) {
      return _.reduce(array, function(memo, value) {
        if (_.isArray(value)) return memo.concat(_.flatten(value));
        memo[memo.length] = value;
        return memo;
      }, []);
    };
  
    // Return a version of the array that does not contain the specified value(s).
    _.without = function(array) {
      return _.difference(array, slice.call(arguments, 1));
    };
  
    // Produce a duplicate-free version of the array. If the array has already
    // been sorted, you have the option of using a faster algorithm.
    // Aliased as `unique`.
    _.uniq = _.unique = function(array, isSorted) {
      return _.reduce(array, function(memo, el, i) {
        if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
        return memo;
      }, []);
    };
  
    // Produce an array that contains the union: each distinct element from all of
    // the passed-in arrays.
    _.union = function() {
      return _.uniq(_.flatten(arguments));
    };
  
    // Produce an array that contains every item shared between all the
    // passed-in arrays. (Aliased as "intersect" for back-compat.)
    _.intersection = _.intersect = function(array) {
      var rest = slice.call(arguments, 1);
      return _.filter(_.uniq(array), function(item) {
        return _.every(rest, function(other) {
          return _.indexOf(other, item) >= 0;
        });
      });
    };
  
    // Take the difference between one array and another.
    // Only the elements present in just the first array will remain.
    _.difference = function(array, other) {
      return _.filter(array, function(value){ return !_.include(other, value); });
    };
  
    // Zip together multiple lists into a single array -- elements that share
    // an index go together.
    _.zip = function() {
      var args = slice.call(arguments);
      var length = _.max(_.pluck(args, 'length'));
      var results = new Array(length);
      for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
      return results;
    };
  
    // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
    // we need this function. Return the position of the first occurrence of an
    // item in an array, or -1 if the item is not included in the array.
    // Delegates to **ECMAScript 5**'s native `indexOf` if available.
    // If the array is large and already in sort order, pass `true`
    // for **isSorted** to use binary search.
    _.indexOf = function(array, item, isSorted) {
      if (array == null) return -1;
      var i, l;
      if (isSorted) {
        i = _.sortedIndex(array, item);
        return array[i] === item ? i : -1;
      }
      if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
      for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
      return -1;
    };
  
  
    // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
    _.lastIndexOf = function(array, item) {
      if (array == null) return -1;
      if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
      var i = array.length;
      while (i--) if (array[i] === item) return i;
      return -1;
    };
  
    // Generate an integer Array containing an arithmetic progression. A port of
    // the native Python `range()` function. See
    // [the Python documentation](http://docs.python.org/library/functions.html#range).
    _.range = function(start, stop, step) {
      if (arguments.length <= 1) {
        stop = start || 0;
        start = 0;
      }
      step = arguments[2] || 1;
  
      var len = Math.max(Math.ceil((stop - start) / step), 0);
      var idx = 0;
      var range = new Array(len);
  
      while(idx < len) {
        range[idx++] = start;
        start += step;
      }
  
      return range;
    };
  
    // Function (ahem) Functions
    // ------------------
  
    // Create a function bound to a given object (assigning `this`, and arguments,
    // optionally). Binding with arguments is also known as `curry`.
    // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
    // We check for `func.bind` first, to fail fast when `func` is undefined.
    _.bind = function(func, obj) {
      if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
      var args = slice.call(arguments, 2);
      return function() {
        return func.apply(obj, args.concat(slice.call(arguments)));
      };
    };
  
    // Bind all of an object's methods to that object. Useful for ensuring that
    // all callbacks defined on an object belong to it.
    _.bindAll = function(obj) {
      var funcs = slice.call(arguments, 1);
      if (funcs.length == 0) funcs = _.functions(obj);
      each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
      return obj;
    };
  
    // Memoize an expensive function by storing its results.
    _.memoize = function(func, hasher) {
      var memo = {};
      hasher || (hasher = _.identity);
      return function() {
        var key = hasher.apply(this, arguments);
        return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
      };
    };
  
    // Delays a function for the given number of milliseconds, and then calls
    // it with the arguments supplied.
    _.delay = function(func, wait) {
      var args = slice.call(arguments, 2);
      return setTimeout(function(){ return func.apply(func, args); }, wait);
    };
  
    // Defers a function, scheduling it to run after the current call stack has
    // cleared.
    _.defer = function(func) {
      return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
    };
  
    // Internal function used to implement `_.throttle` and `_.debounce`.
    var limit = function(func, wait, debounce) {
      var timeout;
      return function() {
        var context = this, args = arguments;
        var throttler = function() {
          timeout = null;
          func.apply(context, args);
        };
        if (debounce) clearTimeout(timeout);
        if (debounce || !timeout) timeout = setTimeout(throttler, wait);
      };
    };
  
    // Returns a function, that, when invoked, will only be triggered at most once
    // during a given window of time.
    _.throttle = function(func, wait) {
      return limit(func, wait, false);
    };
  
    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds.
    _.debounce = function(func, wait) {
      return limit(func, wait, true);
    };
  
    // Returns a function that will be executed at most one time, no matter how
    // often you call it. Useful for lazy initialization.
    _.once = function(func) {
      var ran = false, memo;
      return function() {
        if (ran) return memo;
        ran = true;
        return memo = func.apply(this, arguments);
      };
    };
  
    // Returns the first function passed as an argument to the second,
    // allowing you to adjust arguments, run code before and after, and
    // conditionally execute the original function.
    _.wrap = function(func, wrapper) {
      return function() {
        var args = [func].concat(slice.call(arguments));
        return wrapper.apply(this, args);
      };
    };
  
    // Returns a function that is the composition of a list of functions, each
    // consuming the return value of the function that follows.
    _.compose = function() {
      var funcs = slice.call(arguments);
      return function() {
        var args = slice.call(arguments);
        for (var i = funcs.length - 1; i >= 0; i--) {
          args = [funcs[i].apply(this, args)];
        }
        return args[0];
      };
    };
  
    // Returns a function that will only be executed after being called N times.
    _.after = function(times, func) {
      return function() {
        if (--times < 1) { return func.apply(this, arguments); }
      };
    };
  
  
    // Object Functions
    // ----------------
  
    // Retrieve the names of an object's properties.
    // Delegates to **ECMAScript 5**'s native `Object.keys`
    _.keys = nativeKeys || function(obj) {
      if (obj !== Object(obj)) throw new TypeError('Invalid object');
      var keys = [];
      for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
      return keys;
    };
  
    // Retrieve the values of an object's properties.
    _.values = function(obj) {
      return _.map(obj, _.identity);
    };
  
    // Return a sorted list of the function names available on the object.
    // Aliased as `methods`
    _.functions = _.methods = function(obj) {
      var names = [];
      for (var key in obj) {
        if (_.isFunction(obj[key])) names.push(key);
      }
      return names.sort();
    };
  
    // Extend a given object with all the properties in passed-in object(s).
    _.extend = function(obj) {
      each(slice.call(arguments, 1), function(source) {
        for (var prop in source) {
          if (source[prop] !== void 0) obj[prop] = source[prop];
        }
      });
      return obj;
    };
  
    // Fill in a given object with default properties.
    _.defaults = function(obj) {
      each(slice.call(arguments, 1), function(source) {
        for (var prop in source) {
          if (obj[prop] == null) obj[prop] = source[prop];
        }
      });
      return obj;
    };
  
    // Create a (shallow-cloned) duplicate of an object.
    _.clone = function(obj) {
      return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    };
  
    // Invokes interceptor with the obj, and then returns obj.
    // The primary purpose of this method is to "tap into" a method chain, in
    // order to perform operations on intermediate results within the chain.
    _.tap = function(obj, interceptor) {
      interceptor(obj);
      return obj;
    };
  
    // Perform a deep comparison to check if two objects are equal.
    _.isEqual = function(a, b) {
      // Check object identity.
      if (a === b) return true;
      // Different types?
      var atype = typeof(a), btype = typeof(b);
      if (atype != btype) return false;
      // Basic equality test (watch out for coercions).
      if (a == b) return true;
      // One is falsy and the other truthy.
      if ((!a && b) || (a && !b)) return false;
      // Unwrap any wrapped objects.
      if (a._chain) a = a._wrapped;
      if (b._chain) b = b._wrapped;
      // One of them implements an isEqual()?
      if (a.isEqual) return a.isEqual(b);
      if (b.isEqual) return b.isEqual(a);
      // Check dates' integer values.
      if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
      // Both are NaN?
      if (_.isNaN(a) && _.isNaN(b)) return false;
      // Compare regular expressions.
      if (_.isRegExp(a) && _.isRegExp(b))
        return a.source     === b.source &&
               a.global     === b.global &&
               a.ignoreCase === b.ignoreCase &&
               a.multiline  === b.multiline;
      // If a is not an object by this point, we can't handle it.
      if (atype !== 'object') return false;
      // Check for different array lengths before comparing contents.
      if (a.length && (a.length !== b.length)) return false;
      // Nothing else worked, deep compare the contents.
      var aKeys = _.keys(a), bKeys = _.keys(b);
      // Different object sizes?
      if (aKeys.length != bKeys.length) return false;
      // Recursive comparison of contents.
      for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
      return true;
    };
  
    // Is a given array or object empty?
    _.isEmpty = function(obj) {
      if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
      for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
      return true;
    };
  
    // Is a given value a DOM element?
    _.isElement = function(obj) {
      return !!(obj && obj.nodeType == 1);
    };
  
    // Is a given value an array?
    // Delegates to ECMA5's native Array.isArray
    _.isArray = nativeIsArray || function(obj) {
      return toString.call(obj) === '[object Array]';
    };
  
    // Is a given variable an object?
    _.isObject = function(obj) {
      return obj === Object(obj);
    };
  
    // Is a given variable an arguments object?
    _.isArguments = function(obj) {
      return !!(obj && hasOwnProperty.call(obj, 'callee'));
    };
  
    // Is a given value a function?
    _.isFunction = function(obj) {
      return !!(obj && obj.constructor && obj.call && obj.apply);
    };
  
    // Is a given value a string?
    _.isString = function(obj) {
      return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
    };
  
    // Is a given value a number?
    _.isNumber = function(obj) {
      return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
    };
  
    // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
    // that does not equal itself.
    _.isNaN = function(obj) {
      return obj !== obj;
    };
  
    // Is a given value a boolean?
    _.isBoolean = function(obj) {
      return obj === true || obj === false;
    };
  
    // Is a given value a date?
    _.isDate = function(obj) {
      return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
    };
  
    // Is the given value a regular expression?
    _.isRegExp = function(obj) {
      return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
    };
  
    // Is a given value equal to null?
    _.isNull = function(obj) {
      return obj === null;
    };
  
    // Is a given variable undefined?
    _.isUndefined = function(obj) {
      return obj === void 0;
    };
  
    // Utility Functions
    // -----------------
  
    // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
    // previous owner. Returns a reference to the Underscore object.
    _.noConflict = function() {
      root._ = previousUnderscore;
      return this;
    };
  
    // Keep the identity function around for default iterators.
    _.identity = function(value) {
      return value;
    };
  
    // Run a function **n** times.
    _.times = function (n, iterator, context) {
      for (var i = 0; i < n; i++) iterator.call(context, i);
    };
  
    // Add your own custom functions to the Underscore object, ensuring that
    // they're correctly added to the OOP wrapper as well.
    _.mixin = function(obj) {
      each(_.functions(obj), function(name){
        addToWrapper(name, _[name] = obj[name]);
      });
    };
  
    // Generate a unique integer id (unique within the entire client session).
    // Useful for temporary DOM ids.
    var idCounter = 0;
    _.uniqueId = function(prefix) {
      var id = idCounter++;
      return prefix ? prefix + id : id;
    };
  
    // By default, Underscore uses ERB-style template delimiters, change the
    // following template settings to use alternative delimiters.
    _.templateSettings = {
      evaluate    : /<%([\s\S]+?)%>/g,
      interpolate : /<%=([\s\S]+?)%>/g
    };
  
    // JavaScript micro-templating, similar to John Resig's implementation.
    // Underscore templating handles arbitrary delimiters, preserves whitespace,
    // and correctly escapes quotes within interpolated code.
    _.template = function(str, data) {
      var c  = _.templateSettings;
      var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
        'with(obj||{}){__p.push(\'' +
        str.replace(/\\/g, '\\\\')
           .replace(/'/g, "\\'")
           .replace(c.interpolate, function(match, code) {
             return "'," + code.replace(/\\'/g, "'") + ",'";
           })
           .replace(c.evaluate || null, function(match, code) {
             return "');" + code.replace(/\\'/g, "'")
                                .replace(/[\r\n\t]/g, ' ') + "__p.push('";
           })
           .replace(/\r/g, '\\r')
           .replace(/\n/g, '\\n')
           .replace(/\t/g, '\\t')
           + "');}return __p.join('');";
      var func = new Function('obj', tmpl);
      return data ? func(data) : func;
    };
  
    // The OOP Wrapper
    // ---------------
  
    // If Underscore is called as a function, it returns a wrapped object that
    // can be used OO-style. This wrapper holds altered versions of all the
    // underscore functions. Wrapped objects may be chained.
    var wrapper = function(obj) { this._wrapped = obj; };
  
    // Expose `wrapper.prototype` as `_.prototype`
    _.prototype = wrapper.prototype;
  
    // Helper function to continue chaining intermediate results.
    var result = function(obj, chain) {
      return chain ? _(obj).chain() : obj;
    };
  
    // A method to easily add functions to the OOP wrapper.
    var addToWrapper = function(name, func) {
      wrapper.prototype[name] = function() {
        var args = slice.call(arguments);
        unshift.call(args, this._wrapped);
        return result(func.apply(_, args), this._chain);
      };
    };
  
    // Add all of the Underscore functions to the wrapper object.
    _.mixin(_);
  
    // Add all mutator Array functions to the wrapper.
    each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
      var method = ArrayProto[name];
      wrapper.prototype[name] = function() {
        method.apply(this._wrapped, arguments);
        return result(this._wrapped, this._chain);
      };
    });
  
    // Add all accessor Array functions to the wrapper.
    each(['concat', 'join', 'slice'], function(name) {
      var method = ArrayProto[name];
      wrapper.prototype[name] = function() {
        return result(method.apply(this._wrapped, arguments), this._chain);
      };
    });
  
    // Start chaining a wrapped Underscore object.
    wrapper.prototype.chain = function() {
      this._chain = true;
      return this;
    };
  
    // Extracts the result from a wrapped and chained object.
    wrapper.prototype.value = function() {
      return this._wrapped;
    };
  
  })();
  /**
   * 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) &&
              !v.jquery
          ) {
            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._;
  
  function getEl (el) {
    return (el && el.jquery) ? el[0] : el;
  }
  
  Flotr.DOM = {
    addClass: function(element, name){
      element = getEl(element);
      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 = getEl(element);
      element.innerHTML = '';
      /*
      if (!element) return;
      _.each(element.childNodes, function (e) {
        Flotr.DOM.empty(e);
        element.removeChild(e);
      });
      */
    },
    remove: function (element) {
      element = getEl(element);
      element.parentNode.removeChild(element);
    },
    hide: function(element){
      element = getEl(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){
      element = getEl(element);
      if(_.isString(child))
        element.innerHTML += child;
      else if (_.isElement(child))
        element.appendChild(child);
    },
    // @TODO find xbrowser implementation
    opacity: function(element, opacity) {
      element = getEl(element);
      element.style.opacity = opacity;
    },
    position: function(element, p){
      element = getEl(element);
      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 = getEl(element);
      element.className = _.filter(classList.split(/\s+/g), function (c) {
        if (c != name) return true; }
      ).join(' ');
    },
    setStyles: function(element, o) {
      element = getEl(element);
      _.each(o, function (value, key) {
        element.style[key] = value;
      });
    },
    show: function(element){
      element = getEl(element);
      Flotr.DOM.setStyles(element, {display:''});
    },
    /**
     * Return element size.
     */
    size: function(element){
      element = getEl(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);
      return this;
    },
    eventPointer: function(e) {
      if (!F._.isUndefined(e.touches) && e.touches.length > 0) {
        return {
          x : e.touches[0].pageX,
          y : e.touches[0].pageY
        };
      } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) {
        return {
          x : e.changedTouches[0].pageX,
          y : e.changedTouches[0].pageY
        };
      } else if (e.pageX || e.pageY) {
        return {
          x : e.pageX,
          y : e.pageY
        };
      } else if (e.clientX || e.clientY) {
        var
          d = document,
          b = d.body,
          de = d.documentElement;
        return {
          x: e.clientX + b.scrollLeft + de.scrollLeft,
          y: e.clientY + b.scrollTop + de.scrollTop
        };
      }
    }
  };
  })();
  
  /**
   * Text Utilities
   */
  (function () {
  
  var
    F = Flotr,
    D = F.DOM,
    _ = F._,
  
  Text = function (o) {
    this.o = o;
  };
  
  Text.prototype = {
  
    dimensions : function (text, canvasStyle, htmlStyle, className) {
  
      if (!text) return { width : 0, height : 0 };
      
      return (this.o.html) ?
        this.html(text, this.o.element, htmlStyle, className) : 
        this.canvas(text, canvasStyle);
    },
  
    canvas : function (text, style) {
  
      if (!this.o.textEnabled) return;
      style = style || {};
  
      var
        metrics = this.measureText(text, style),
        width = metrics.width,
        height = style.size || F.defaultOptions.fontSize,
        angle = style.angle || 0,
        cosAngle = Math.cos(angle),
        sinAngle = Math.sin(angle),
        widthPadding = 2,
        heightPadding = 6,
        bounds;
  
      bounds = {
        width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding,
        height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding
      };
  
      return bounds;
    },
  
    html : function (text, element, style, className) {
  
      var div = D.create('div');
  
      D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' });
      D.insert(div, '<div style="'+style+'" class="'+className+' flotr-dummy-div">' + text + '</div>');
      D.insert(this.o.element, div);
  
      return D.size(div);
    },
  
    measureText : function (text, style) {
  
      var
        context = this.o.ctx,
        metrics;
  
      if (!context.fillText || (F.isIphone && context.measure)) {
        return { width : context.measure(text, style)};
      }
  
      style = _.extend({
        size: F.defaultOptions.fontSize,
        weight: 1,
        angle: 0
      }, style);
  
      context.save();
      context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
      metrics = context.measureText(text);
      context.restore();
  
      return metrics;
    }
  };
  
  Flotr.Text = Text;
  
  })();
  
  /**
   * Flotr Graph class that plots a graph on creation.
   */
  (function () {
  
  var
    D     = Flotr.DOM,
    E     = Flotr.EventAdapter,
    _     = Flotr._,
    flotr = Flotr;
  /**
   * Flotr Graph constructor.
   * @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
   */
  Graph = function(el, data, options){
  // Let's see if we can get away with out this [JS]
  //  try {
      this._setEl(el);
      this._initMembers();
      this._initPlugins();
  
      E.fire(this.el, 'flotr:beforeinit', [this]);
  
      this.data = data;
      this.series = flotr.Series.getSeries(data);
      this._initOptions(options);
      this._initGraphTypes();
      this._initCanvas();
      this._text = new flotr.Text({
        element : this.el,
        ctx : this.ctx,
        html : this.options.HtmlText,
        textEnabled : this.textEnabled
      });
      E.fire(this.el, 'flotr:afterconstruct', [this]);
      this._initEvents();
  
      this.findDataRanges();
      this.calculateSpacing();
  
      this.draw(_.bind(function() {
        E.fire(this.el, 'flotr:afterinit', [this]);
      }, this));
  /*
      try {
    } catch (e) {
      try {
        console.error(e);
      } catch (e2) {}
    }*/
  };
  
  function observe (object, name, callback) {
    E.observe.apply(this, arguments);
    this._handles.push(arguments);
    return this;
  }
  
  Graph.prototype = {
  
    destroy: function () {
      E.fire(this.el, 'flotr:destroy');
      _.each(this._handles, function (handle) {
        E.stopObserving.apply(this, handle);
      });
      this._handles = [];
      this.el.graph = null;
    },
  
    observe : observe,
  
    /**
     * @deprecated
     */
    _observe : observe,
  
    processColor: function(color, options){
      var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx };
      _.extend(o, options);
      return flotr.Color.processColor(color, o);
    },
    /**
     * Function determines the min and max values for the xaxis and yaxis.
     *
     * TODO logarithmic range validation (consideration of 0)
     */
    findDataRanges: function(){
      var a = this.axes,
        xaxis, yaxis, range;
  
      _.each(this.series, function (series) {
        range = series.getRange();
        if (range) {
          xaxis = series.xaxis;
          yaxis = series.yaxis;
          xaxis.datamin = Math.min(range.xmin, xaxis.datamin);
          xaxis.datamax = Math.max(range.xmax, xaxis.datamax);
          yaxis.datamin = Math.min(range.ymin, yaxis.datamin);
          yaxis.datamax = Math.max(range.ymax, yaxis.datamax);
          xaxis.used = (xaxis.used || range.xused);
          yaxis.used = (yaxis.used || range.yused);
        }
      }, this);
  
      // Check for empty data, no data case (none used)
      if (!a.x.used && !a.x2.used) a.x.used = true;
      if (!a.y.used && !a.y2.used) a.y.used = true;
  
      _.each(a, function (axis) {
        axis.calculateRange();
      });
  
      var
        types = _.keys(flotr.graphTypes),
        drawn = false;
  
      _.each(this.series, function (series) {
        if (series.hide) return;
        _.each(types, function (type) {
          if (series[type] && series[type].show) {
            this.extendRange(type, series);
            drawn = true;
          }
        }, this);
        if (!drawn) {
          this.extendRange(this.options.defaultType, series);
        }
      }, this);
    },
  
    extendRange : function (type, series) {
      if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]);
      if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]);
      if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]);
    },
  
    /**
     * Calculates axis label sizes.
     */
    calculateSpacing: function(){
  
      var a = this.axes,
          options = this.options,
          series = this.series,
          margin = options.grid.labelMargin,
          T = this._text,
          x = a.x,
          x2 = a.x2,
          y = a.y,
          y2 = a.y2,
          maxOutset = options.grid.outlineWidth,
          i, j, l, dim;
  
      // TODO post refactor, fix this
      _.each(a, function (axis) {
        axis.calculateTicks();
        axis.calculateTextDimensions(T, options);
      });
  
      // Title height
      dim = T.dimensions(
        options.title,
        {size: options.fontSize*1.5},
        'font-size:1em;font-weight:bold;',
        'flotr-title'
      );
      this.titleHeight = dim.height;
  
      // Subtitle height
      dim = T.dimensions(
        options.subtitle,
        {size: options.fontSize},
        'font-size:smaller;',
        'flotr-subtitle'
      );
      this.subtitleHeight = dim.height;
  
      for(j = 0; j < options.length; ++j){
        if (series[j].points.show){
          maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);
        }
      }
  
      var p = this.plotOffset;
      if (x.options.margin === false) {
        p.bottom = 0;
        p.top    = 0;
      } else
      if (x.options.margin === true) {
        p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ?  (x.maxLabel.height + margin) : 0)) +
                    (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset;
  
        p.top    += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) +
                    (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset;
      } else {
        p.bottom = x.options.margin;
        p.top = x.options.margin;
      }
      if (y.options.margin === false) {
        p.left  = 0;
        p.right = 0;
      } else
      if (y.options.margin === true) {
        p.left   += (options.grid.circular ? 0 : (y.used && y.options.showLabels ?  (y.maxLabel.width + margin) : 0)) +
                    (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset;
  
        p.right  += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) +
                    (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset;
      } else {
        p.left = y.options.margin;
        p.right = y.options.margin;
      }
  
      p.top = Math.floor(p.top); // In order the outline not to be blured
  
      this.plotWidth  = this.canvasWidth - p.left - p.right;
      this.plotHeight = this.canvasHeight - p.bottom - p.top;
  
      // TODO post refactor, fix this
      x.length = x2.length = this.plotWidth;
      y.length = y2.length = this.plotHeight;
      y.offset = y2.offset = this.plotHeight;
      x.setScale();
      x2.setScale();
      y.setScale();
      y2.setScale();
    },
    /**
     * Draws grid, labels, series and outline.
     */
    draw: function(after) {
  
      var
        context = this.ctx,
        i;
  
      E.fire(this.el, 'flotr:beforedraw', [this.series, this]);
  
      if (this.series.length) {
  
        context.save();
        context.translate(this.plotOffset.left, this.plotOffset.top);
  
        for (i = 0; i < this.series.length; i++) {
          if (!this.series[i].hide) this.drawSeries(this.series[i]);
        }
  
        context.restore();
        this.clip();
      }
  
      E.fire(this.el, 'flotr:afterdraw', [this.series, this]);
      if (after) after();
    },
    /**
     * Actually draws the graph.
     * @param {Object} series - series to draw
     */
    drawSeries: function(series){
  
      function drawChart (series, typeKey) {
        var options = this.getOptions(series, typeKey);
        this[typeKey].draw(options);
      }
  
      var drawn = false;
      series = series || this.series;
  
      _.each(flotr.graphTypes, function (type, typeKey) {
        if (series[typeKey] && series[typeKey].show && this[typeKey]) {
          drawn = true;
          drawChart.call(this, series, typeKey);
        }
      }, this);
  
      if (!drawn) drawChart.call(this, series, this.options.defaultType);
    },
  
    getOptions : function (series, typeKey) {
      var
        type = series[typeKey],
        graphType = this[typeKey],
        xaxis = series.xaxis,
        yaxis = series.yaxis,
        options = {
          context     : this.ctx,
          width       : this.plotWidth,
          height      : this.plotHeight,
          fontSize    : this.options.fontSize,
          fontColor   : this.options.fontColor,
          textEnabled : this.textEnabled,
          htmlText    : this.options.HtmlText,
          text        : this._text, // TODO Is this necessary?
          element     : this.el,
          data        : series.data,
          color       : series.color,
          shadowSize  : series.shadowSize,
          xScale      : xaxis.d2p,
          yScale      : yaxis.d2p,
          xInverse    : xaxis.p2d,
          yInverse    : yaxis.p2d
        };
  
      options = flotr.merge(type, options);
  
      // Fill
      options.fillStyle = this.processColor(
        type.fillColor || series.color,
        {opacity: type.fillOpacity}
      );
  
      return options;
    },
    /**
     * Calculates the coordinates from a mouse event object.
     * @param {Event} event - Mouse Event object.
     * @return {Object} Object with coordinates of the mouse.
     */
    getEventPosition: function (e){
  
      var
        d = document,
        b = d.body,
        de = d.documentElement,
        axes = this.axes,
        plotOffset = this.plotOffset,
        lastMousePos = this.lastMousePos,
        pointer = E.eventPointer(e),
        dx = pointer.x - lastMousePos.pageX,
        dy = pointer.y - lastMousePos.pageY,
        r, rx, ry;
  
      if ('ontouchstart' in this.el) {
        r = D.position(this.overlay);
        rx = pointer.x - r.left - plotOffset.left;
        ry = pointer.y - r.top - plotOffset.top;
      } else {
        r = this.overlay.getBoundingClientRect();
        rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft;
        ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop;
      }
  
      return {
        x:  axes.x.p2d(rx),
        x2: axes.x2.p2d(rx),
        y:  axes.y.p2d(ry),
        y2: axes.y2.p2d(ry),
        relX: rx,
        relY: ry,
        dX: dx,
        dY: dy,
        absX: pointer.x,
        absY: pointer.y,
        pageX: pointer.x,
        pageY: pointer.y
      };
    },
    /**
     * Observes the 'click' event and fires the 'flotr:click' event.
     * @param {Event} event - 'click' Event object.
     */
    clickHandler: function(event){
      if(this.ignoreClick){
        this.ignoreClick = false;
        return this.ignoreClick;
      }
      E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]);
    },
    /**
     * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event.
     * @param {Event} event - 'mousemove' Event object.
     */
    mouseMoveHandler: function(event){
      if (this.mouseDownMoveHandler) return;
      var pos = this.getEventPosition(event);
      E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
      this.lastMousePos = pos;
    },
    /**
     * Observes the 'mousedown' event.
     * @param {Event} event - 'mousedown' Event object.
     */
    mouseDownHandler: function (event){
  
      /*
      // @TODO Context menu?
      if(event.isRightClick()) {
        event.stop();
  
        var overlay = this.overlay;
        overlay.hide();
  
        function cancelContextMenu () {
          overlay.show();
          E.stopObserving(document, 'mousemove', cancelContextMenu);
        }
        E.observe(document, 'mousemove', cancelContextMenu);
        return;
      }
      */
  
      if (this.mouseUpHandler) return;
      this.mouseUpHandler = _.bind(function (e) {
        E.stopObserving(document, 'mouseup', this.mouseUpHandler);
        E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler);
        this.mouseDownMoveHandler = null;
        this.mouseUpHandler = null;
        // @TODO why?
        //e.stop();
        E.fire(this.el, 'flotr:mouseup', [e, this]);
      }, this);
      this.mouseDownMoveHandler = _.bind(function (e) {
          var pos = this.getEventPosition(e);
          E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
          this.lastMousePos = pos;
      }, this);
      E.observe(document, 'mouseup', this.mouseUpHandler);
      E.observe(document, 'mousemove', this.mouseDownMoveHandler);
      E.fire(this.el, 'flotr:mousedown', [event, this]);
      this.ignoreClick = false;
    },
    drawTooltip: function(content, x, y, options) {
      var mt = this.getMouseTrack(),
          style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;',
          p = options.position,
          m = options.margin,
          plotOffset = this.plotOffset;
  
      if(x !== null && y !== null){
        if (!options.relative) { // absolute to the canvas
               if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
          else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
               if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
          else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
        }
        else { // relative to the mouse
               if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;';
          else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;';
               if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;';
          else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;';
        }
  
        mt.style.cssText = style;
        D.empty(mt);
        D.insert(mt, content);
        D.show(mt);
      }
      else {
        D.hide(mt);
      }
    },
  
    clip: function (ctx) {
  
      var
        o   = this.plotOffset,
        w   = this.canvasWidth,
        h   = this.canvasHeight;
  
      ctx = ctx || this.ctx;
  
      if (
        flotr.isIE && flotr.isIE < 9 && // IE w/o canvas
        !flotr.isFlashCanvas // But not flash canvas
      ) {
  
        // Do not clip excanvas on overlay context
        // Allow hits to overflow.
        if (ctx === this.octx) {
          return;
        }
  
        // Clipping for excanvas :-(
        ctx.save();
        ctx.fillStyle = this.processColor(this.options.ieBackgroundColor);
        ctx.fillRect(0, 0, w, o.top);
        ctx.fillRect(0, 0, o.left, h);
        ctx.fillRect(0, h - o.bottom, w, o.bottom);
        ctx.fillRect(w - o.right, 0, o.right,h);
        ctx.restore();
      } else {
        ctx.clearRect(0, 0, w, o.top);
        ctx.clearRect(0, 0, o.left, h);
        ctx.clearRect(0, h - o.bottom, w, o.bottom);
        ctx.clearRect(w - o.right, 0, o.right,h);
      }
    },
  
    _initMembers: function() {
      this._handles = [];
      this.lastMousePos = {pageX: null, pageY: null };
      this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};
      this.ignoreClick = true;
      this.prevHit = null;
    },
  
    _initGraphTypes: function() {
      _.each(flotr.graphTypes, function(handler, graphType){
        this[graphType] = flotr.clone(handler);
      }, this);
    },
  
    _initEvents: function () {
  
      var
        el = this.el,
        touchendHandler, movement, touchend;
  
      if ('ontouchstart' in el) {
  
        touchendHandler = _.bind(function (e) {
          touchend = true;
          E.stopObserving(document, 'touchend', touchendHandler);
          E.fire(el, 'flotr:mouseup', [event, this]);
          this.multitouches = null;
  
          if (!movement) {
            this.clickHandler(e);
          }
        }, this);
  
        this.observe(this.overlay, 'touchstart', _.bind(function (e) {
          movement = false;
          touchend = false;
          this.ignoreClick = false;
  
          if (e.touches && e.touches.length > 1) {
            this.multitouches = e.touches;
          }
  
          E.fire(el, 'flotr:mousedown', [event, this]);
          this.observe(document, 'touchend', touchendHandler);
        }, this));
  
        this.observe(this.overlay, 'touchmove', _.bind(function (e) {
  
          var pos = this.getEventPosition(e);
  
          if (this.options.preventDefault) {
            e.preventDefault();
          }
  
          movement = true;
  
          if (this.multitouches || (e.touches && e.touches.length > 1)) {
            this.multitouches = e.touches;
          } else {
            if (!touchend) {
              E.fire(el, 'flotr:mousemove', [event, pos, this]);
            }
          }
          this.lastMousePos = pos;
        }, this));
  
      } else {
        this.
          observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)).
          observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)).
          observe(this.overlay, 'click', _.bind(this.clickHandler, this)).
          observe(el, 'mouseout', function (e) {
            E.fire(el, 'flotr:mouseout', e);
          });
      }
    },
  
    /**
     * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use
     * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements
     * are created, the elements are inserted into the container element.
     */
    _initCanvas: function(){
      var el = this.el,
        o = this.options,
        children = el.children,
        removedChildren = [],
        child, i,
        size, style;
  
      // Empty the el
      for (i = children.length; i--;) {
        child = children[i];
        if (!this.canvas && child.className === 'flotr-canvas') {
          this.canvas = child;
        } else if (!this.overlay && child.className === 'flotr-overlay') {
          this.overlay = child;
        } else {
          removedChildren.push(child);
        }
      }
      for (i = removedChildren.length; i--;) {
        el.removeChild(removedChildren[i]);
      }
  
      D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay.
      size = {};
      size.width = el.clientWidth;
      size.height = el.clientHeight;
  
      if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){
        throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution;
      }
  
      // Main canvas for drawing graph types
      this.canvas = getCanvas(this.canvas, 'canvas');
      // Overlay canvas for interactive features
      this.overlay = getCanvas(this.overlay, 'overlay');
      this.ctx = getContext(this.canvas);
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.octx = getContext(this.overlay);
      this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height);
      this.canvasHeight = size.height;
      this.canvasWidth = size.width;
      this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions
  
      function getCanvas(canvas, name){
        if(!canvas){
          canvas = D.create('canvas');
          if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') {
            FlashCanvas.initElement(canvas);
            this.isFlashCanvas = true;
          }
          canvas.className = 'flotr-'+name;
          canvas.style.cssText = 'position:absolute;left:0px;top:0px;';
          D.insert(el, canvas);
        }
        _.each(size, function(size, attribute){
          D.show(canvas);
          if (name == 'canvas' && canvas.getAttribute(attribute) === size) {
            return;
          }
          canvas.setAttribute(attribute, size * o.resolution);
          canvas.style[attribute] = size + 'px';
        });
        canvas.context_ = null; // Reset the ExCanvas context
        return canvas;
      }
  
      function getContext(canvas){
        if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas
        var context = canvas.getContext('2d');
        if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution);
        return context;
      }
    },
  
    _initPlugins: function(){
      // TODO Should be moved to flotr and mixed in.
      _.each(flotr.plugins, function(plugin, name){
        _.each(plugin.callbacks, function(fn, c){
          this.observe(this.el, c, _.bind(fn, this));
        }, this);
        this[name] = flotr.clone(plugin);
        _.each(this[name], function(fn, p){
          if (_.isFunction(fn))
            this[name][p] = _.bind(fn, this);
        }, this);
      }, this);
    },
  
    /**
     * Sets options and initializes some variables and color specific values, used by the constructor.
     * @param {Object} opts - options object
     */
    _initOptions: function(opts){
      var options = flotr.clone(flotr.defaultOptions);
      options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis);
      options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis);
      this.options = flotr.merge(opts || {}, options);
  
      if (this.options.grid.minorVerticalLines === null &&
        this.options.xaxis.scaling === 'logarithmic') {
        this.options.grid.minorVerticalLines = true;
      }
      if (this.options.grid.minorHorizontalLines === null &&
        this.options.yaxis.scaling === 'logarithmic') {
        this.options.grid.minorHorizontalLines = true;
      }
  
      E.fire(this.el, 'flotr:afterinitoptions', [this]);
  
      this.axes = flotr.Axis.getAxes(this.options);
  
      // Initialize some variables used throughout this function.
      var assignedColors = [],
          colors = [],
          ln = this.series.length,
          neededColors = this.series.length,
          oc = this.options.colors,
          usedColors = [],
          variation = 0,
          c, i, j, s;
  
      // Collect user-defined colors from series.
      for(i = neededColors - 1; i > -1; --i){
        c = this.series[i].color;
        if(c){
          --neededColors;
          if(_.isNumber(c)) assignedColors.push(c);
          else usedColors.push(flotr.Color.parse(c));
        }
      }
  
      // Calculate the number of colors that need to be generated.
      for(i = assignedColors.length - 1; i > -1; --i)
        neededColors = Math.max(neededColors, assignedColors[i] + 1);
  
      // Generate needed number of colors.
      for(i = 0; colors.length < neededColors;){
        c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]);
  
        // Make sure each serie gets a different color.
        var sign = variation % 2 == 1 ? -1 : 1,
            factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
        c.scale(factor, factor, factor);
  
        /**
         * @todo if we're getting too close to something else, we should probably skip this one
         */
        colors.push(c);
  
        if(++i >= oc.length){
          i = 0;
          ++variation;
        }
      }
  
      // Fill the options with the generated colors.
      for(i = 0, j = 0; i < ln; ++i){
        s = this.series[i];
  
        // Assign the color.
        if (!s.color){
          s.color = colors[j++].toString();
        }else if(_.isNumber(s.color)){
          s.color = colors[s.color].toString();
        }
  
        // Every series needs an axis
        if (!s.xaxis) s.xaxis = this.axes.x;
             if (s.xaxis == 1) s.xaxis = this.axes.x;
        else if (s.xaxis == 2) s.xaxis = this.axes.x2;
  
        if (!s.yaxis) s.yaxis = this.axes.y;
             if (s.yaxis == 1) s.yaxis = this.axes.y;
        else if (s.yaxis == 2) s.yaxis = this.axes.y2;
  
        // Apply missing options to the series.
        for (var t in flotr.graphTypes){
          s[t] = _.extend(_.clone(this.options[t]), s[t]);
        }
        s.mouse = _.extend(_.clone(this.options.mouse), s.mouse);
  
        if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize;
      }
    },
  
    _setEl: function(el) {
      if (!el) throw 'The target container doesn\'t exist';
      else if (el.graph instanceof Graph) el.graph.destroy();
      else if (!el.clientWidth) throw 'The target container must be visible';
  
      el.graph = this;
      this.el = el;
    }
  };
  
  Flotr.Graph = Graph;
  
  })();
  
  /**
   * Flotr Axis Library
   */
  
  (function () {
  
  var
    _ = Flotr._,
    LOGARITHMIC = 'logarithmic';
  
  function Axis (o) {
  
    this.orientation = 1;
    this.offset = 0;
    this.datamin = Number.MAX_VALUE;
    this.datamax = -Number.MAX_VALUE;
  
    _.extend(this, o);
  }
  
  
  // Prototype
  Axis.prototype = {
  
    setScale : function () {
      var
        length = this.length,
        max = this.max,
        min = this.min,
        offset = this.offset,
        orientation = this.orientation,
        options = this.options,
        logarithmic = options.scaling === LOGARITHMIC,
        scale;
  
      if (logarithmic) {
        scale = length / (log(max, options.base) - log(min, options.base));
      } else {
        scale = length / (max - min);
      }
      this.scale = scale;
  
      // Logarithmic?
      if (logarithmic) {
        this.d2p = function (dataValue) {
          return offset + orientation * (log(dataValue, options.base) - log(min, options.base)) * scale;
        };
        this.p2d = function (pointValue) {
          return exp((offset + orientation * pointValue) / scale + log(min, options.base), options.base);
        };
      } else {
        this.d2p = function (dataValue) {
          return offset + orientation * (dataValue - min) * scale;
        };
        this.p2d = function (pointValue) {
          return (offset + orientation * pointValue) / scale + min;
        };
      }
    },
  
    calculateTicks : function () {
      var options = this.options;
  
      this.ticks = [];
      this.minorTicks = [];
      
      // User Ticks
      if(options.ticks){
        this._cleanUserTicks(options.ticks, this.ticks);
        this._cleanUserTicks(options.minorTicks || [], this.minorTicks);
      }
      else {
        if (options.mode == 'time') {
          this._calculateTimeTicks();
        } else if (options.scaling === 'logarithmic') {
          this._calculateLogTicks();
        } else {
          this._calculateTicks();
        }
      }
  
      // Ticks to strings
      _.each(this.ticks, function (tick) { tick.label += ''; });
      _.each(this.minorTicks, function (tick) { tick.label += ''; });
    },
  
    /**
     * Calculates the range of an axis to apply autoscaling.
     */
    calculateRange: function () {
  
      if (!this.used) return;
  
      var axis  = this,
        o       = axis.options,
        min     = o.min !== null ? o.min : axis.datamin,
        max     = o.max !== null ? o.max : axis.datamax,
        margin  = o.autoscaleMargin;
          
      if (o.scaling == 'logarithmic') {
        if (min <= 0) min = axis.datamin;
  
        // Let it widen later on
        if (max <= 0) max = min;
      }
  
      if (max == min) {
        var widen = max ? 0.01 : 1.00;
        if (o.min === null) min -= widen;
        if (o.max === null) max += widen;
      }
  
      if (o.scaling === 'logarithmic') {
        if (min < 0) min = max / o.base;  // Could be the result of widening
  
        var maxexp = Math.log(max);
        if (o.base != Math.E) maxexp /= Math.log(o.base);
        maxexp = Math.ceil(maxexp);
  
        var minexp = Math.log(min);
        if (o.base != Math.E) minexp /= Math.log(o.base);
        minexp = Math.ceil(minexp);
        
        axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals);
                          
        // Try to determine a suitable amount of miniticks based on the length of a decade
        if (o.minorTickFreq === null) {
          if (maxexp - minexp > 10)
            o.minorTickFreq = 0;
          else if (maxexp - minexp > 5)
            o.minorTickFreq = 2;
          else
            o.minorTickFreq = 5;
        }
      } else {
        axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
      }
  
      axis.min = min;
      axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled
  
      // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it
      if(o.min === null && o.autoscale){
        axis.min -= axis.tickSize * margin;
        // Make sure we don't go below zero if all values are positive.
        if(axis.min < 0 && axis.datamin >= 0) axis.min = 0;
        axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize);
      }
      
      if(o.max === null && o.autoscale){
        axis.max += axis.tickSize * margin;
        if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0;        
        axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize);
      }
  
      if (axis.min == axis.max) axis.max = axis.min + 1;
    },
  
    calculateTextDimensions : function (T, options) {
  
      var maxLabel = '',
        length,
        i;
  
      if (this.options.showLabels) {
        for (i = 0; i < this.ticks.length; ++i) {
          length = this.ticks[i].label.length;
          if (length > maxLabel.length){
            maxLabel = this.ticks[i].label;
          }
        }
      }
  
      this.maxLabel = T.dimensions(
        maxLabel,
        {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)},
        'font-size:smaller;',
        'flotr-grid-label'
      );
  
      this.titleSize = T.dimensions(
        this.options.title, 
        {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)},
        'font-weight:bold;',
        'flotr-axis-title'
      );
    },
  
    _cleanUserTicks : function (ticks, axisTicks) {
  
      var axis = this, options = this.options,
        v, i, label, tick;
  
      if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max});
  
      for(i = 0; i < ticks.length; ++i){
        tick = ticks[i];
        if(typeof(tick) === 'object'){
          v = tick[0];
          label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max});
        } else {
          v = tick;
          label = options.tickFormatter(v, {min : this.min, max : this.max});
        }
        axisTicks[i] = { v: v, label: label };
      }
    },
  
    _calculateTimeTicks : function () {
      this.ticks = Flotr.Date.generator(this);
    },
  
    _calculateLogTicks : function () {
  
      var axis = this,
        o = axis.options,
        v,
        decadeStart;
  
      var max = Math.log(axis.max);
      if (o.base != Math.E) max /= Math.log(o.base);
      max = Math.ceil(max);
  
      var min = Math.log(axis.min);
      if (o.base != Math.E) min /= Math.log(o.base);
      min = Math.ceil(min);
      
      for (i = min; i < max; i += axis.tickSize) {
        decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
        // Next decade begins here:
        var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize));
        var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq;
        
        axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
        for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize)
          axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})});
      }
      
      // Always show the value at the would-be start of next decade (end of this decade)
      decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
      axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
    },
  
    _calculateTicks : function () {
  
      var axis      = this,
          o         = axis.options,
          tickSize  = axis.tickSize,
          min       = axis.min,
          max       = axis.max,
          start     = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size.
          decimals,
          minorTickSize,
          v, v2,
          i, j;
      
      if (o.minorTickFreq)
        minorTickSize = tickSize / o.minorTickFreq;
                        
      // Then store all possible ticks.
      for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){
        
        // Round (this is always needed to fix numerical instability).
        decimals = o.tickDecimals;
        if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10);
        if (decimals < 0) decimals = 0;
        
        v = v.toFixed(decimals);
        axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
  
        if (o.minorTickFreq) {
          for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) {
            v = v2 + j * minorTickSize;
            axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
          }
        }
      }
  
    }
  };
  
  
  // Static Methods
  _.extend(Axis, {
    getAxes : function (options) {
      return {
        x:  new Axis({options: options.xaxis,  n: 1, length: this.plotWidth}),
        x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}),
        y:  new Axis({options: options.yaxis,  n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}),
        y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1})
      };
    }
  });
  
  
  // Helper Methods
  
  
  function log (value, base) {
    value = Math.log(Math.max(value, Number.MIN_VALUE));
    if (base !== Math.E) 
      value /= Math.log(base);
    return value;
  }
  
  function exp (value, base) {
    return (base === Math.E) ? Math.exp(value) : Math.pow(base, value);
  }
  
  Flotr.Axis = Axis;
  
  })();
  
  /**
   * Flotr Series Library
   */
  
  (function () {
  
  var
    _ = Flotr._;
  
  function Series (o) {
    _.extend(this, o);
  }
  
  Series.prototype = {
  
    getRange: function () {
  
      var
        data = this.data,
        length = data.length,
        xmin = Number.MAX_VALUE,
        ymin = Number.MAX_VALUE,
        xmax = -Number.MAX_VALUE,
        ymax = -Number.MAX_VALUE,
        xused = false,
        yused = false,
        x, y, i;
  
      if (length < 0 || this.hide) return false;
  
      for (i = 0; i < length; i++) {
        x = data[i][0];
        y = data[i][1];
        if (x !== null) {
          if (x < xmin) { xmin = x; xused = true; }
          if (x > xmax) { xmax = x; xused = true; }
        }
        if (y !== null) {
          if (y < ymin) { ymin = y; yused = true; }
          if (y > ymax) { ymax = y; yused = true; }
        }
      }
  
      return {
        xmin : xmin,
        xmax : xmax,
        ymin : ymin,
        ymax : ymax,
        xused : xused,
        yused : yused
      };
    }
  };
  
  _.extend(Series, {
    /**
     * Collects dataseries from input and parses the series into the right format. It returns an Array 
     * of Objects each having at least the 'data' key set.
     * @param {Array, Object} data - Object or array of dataseries
     * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})
     */
    getSeries: function(data){
      return _.map(data, function(s){
        var series;
        if (s.data) {
          series = new Series();
          _.extend(series, s);
        } else {
          series = new Series({data:s});
        }
        return series;
      });
    }
  });
  
  Flotr.Series = Series;
  
  })();
  
  
  /** Bars **/
  Flotr.addType('bars', {
  
    options: {
      show: false,           // => setting to true will show bars, false will hide
      lineWidth: 2,          // => in pixels
      barWidth: 1,           // => in units of the x axis
      fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
      fillColor: null,       // => fill color
      fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      horizontal: false,     // => horizontal bars (x and y inverted)
      stacked: false,        // => stacked bar charts
      centered: true,        // => center the bars to their x axis value
      topPadding: 0.1,       // => top padding in percent
      grouped: false         // => groups bars together which share x value, hit not supported.
    },
  
    stack : { 
      positive : [],
      negative : [],
      _positive : [], // Shadow
      _negative : []  // Shadow
    },
  
    draw : function (options) {
      var
        context = options.context;
  
      this.current += 1;
  
      context.save();
      context.lineJoin = 'miter';
      // @TODO linewidth not interpreted the right way.
      context.lineWidth = options.lineWidth;
      context.strokeStyle = options.color;
      if (options.fill) context.fillStyle = options.fillStyle;
      
      this.plot(options);
  
      context.restore();
    },
  
    plot : function (options) {
  
      var
        data            = options.data,
        context         = options.context,
        shadowSize      = options.shadowSize,
        i, geometry, left, top, width, height;
  
      if (data.length < 1) return;
  
      this.translate(context, options.horizontal);
  
      for (i = 0; i < data.length; i++) {
  
        geometry = this.getBarGeometry(data[i][0], data[i][1], options);
        if (geometry === null) continue;
  
        left    = geometry.left;
        top     = geometry.top;
        width   = geometry.width;
        height  = geometry.height;
  
        if (options.fill) context.fillRect(left, top, width, height);
        if (shadowSize) {
          context.save();
          context.fillStyle = 'rgba(0,0,0,0.05)';
          context.fillRect(left + shadowSize, top + shadowSize, width, height);
          context.restore();
        }
        if (options.lineWidth) {
          context.strokeRect(left, top, width, height);
        }
      }
    },
  
    translate : function (context, horizontal) {
      if (horizontal) {
        context.rotate(-Math.PI / 2);
        context.scale(-1, 1);
      }
    },
  
    getBarGeometry : function (x, y, options) {
  
      var
        horizontal    = options.horizontal,
        barWidth      = options.barWidth,
        centered      = options.centered,
        stack         = options.stacked ? this.stack : false,
        lineWidth     = options.lineWidth,
        bisection     = centered ? barWidth / 2 : 0,
        xScale        = horizontal ? options.yScale : options.xScale,
        yScale        = horizontal ? options.xScale : options.yScale,
        xValue        = horizontal ? y : x,
        yValue        = horizontal ? x : y,
        stackOffset   = 0,
        stackValue, left, right, top, bottom;
  
      if (options.grouped) {
        this.current / this.groups;
        xValue = xValue - bisection;
        barWidth = barWidth / this.groups;
        bisection = barWidth / 2;
        xValue = xValue + barWidth * this.current - bisection;
      }
  
      // Stacked bars
      if (stack) {
        stackValue          = yValue > 0 ? stack.positive : stack.negative;
        stackOffset         = stackValue[xValue] || stackOffset;
        stackValue[xValue]  = stackOffset + yValue;
      }
  
      left    = xScale(xValue - bisection);
      right   = xScale(xValue + barWidth - bisection);
      top     = yScale(yValue + stackOffset);
      bottom  = yScale(stackOffset);
  
      // TODO for test passing... probably looks better without this
      if (bottom < 0) bottom = 0;
  
      // TODO Skipping...
      // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue;
  
      return (x === null || y === null) ? null : {
        x         : xValue,
        y         : yValue,
        xScale    : xScale,
        yScale    : yScale,
        top       : top,
        left      : Math.min(left, right) - lineWidth / 2,
        width     : Math.abs(right - left) - lineWidth,
        height    : bottom - top
      };
    },
  
    hit : function (options) {
      var
        data = options.data,
        args = options.args,
        mouse = args[0],
        n = args[1],
        x = options.xInverse(mouse.relX),
        y = options.yInverse(mouse.relY),
        hitGeometry = this.getBarGeometry(x, y, options),
        width = hitGeometry.width / 2,
        left = hitGeometry.left,
        height = hitGeometry.y,
        geometry, i;
  
      for (i = data.length; i--;) {
        geometry = this.getBarGeometry(data[i][0], data[i][1], options);
        if (
          // Height:
          (
            // Positive Bars:
            (height > 0 && height < geometry.y) ||
            // Negative Bars:
            (height < 0 && height > geometry.y)
          ) &&
          // Width:
          (Math.abs(left - geometry.left) < width)
        ) {
          n.x = data[i][0];
          n.y = data[i][1];
          n.index = i;
          n.seriesIndex = options.index;
        }
      }
    },
  
    drawHit : function (options) {
      // TODO hits for stacked bars; implement using calculateStack option?
      var
        context     = options.context,
        args        = options.args,
        geometry    = this.getBarGeometry(args.x, args.y, options),
        left        = geometry.left,
        top         = geometry.top,
        width       = geometry.width,
        height      = geometry.height;
  
      context.save();
      context.strokeStyle = options.color;
      context.lineWidth = options.lineWidth;
      this.translate(context, options.horizontal);
  
      // Draw highlight
      context.beginPath();
      context.moveTo(left, top + height);
      context.lineTo(left, top);
      context.lineTo(left + width, top);
      context.lineTo(left + width, top + height);
      if (options.fill) {
        context.fillStyle = options.fillStyle;
        context.fill();
      }
      context.stroke();
      context.closePath();
  
      context.restore();
    },
  
    clearHit: function (options) {
      var
        context     = options.context,
        args        = options.args,
        geometry    = this.getBarGeometry(args.x, args.y, options),
        left        = geometry.left,
        width       = geometry.width,
        top         = geometry.top,
        height      = geometry.height,
        lineWidth   = 2 * options.lineWidth;
  
      context.save();
      this.translate(context, options.horizontal);
      context.clearRect(
        left - lineWidth,
        Math.min(top, top + height) - lineWidth,
        width + 2 * lineWidth,
        Math.abs(height) + 2 * lineWidth
      );
      context.restore();
    },
  
    extendXRange : function (axis, data, options, bars) {
      this._extendRange(axis, data, options, bars);
      this.groups = (this.groups + 1) || 1;
      this.current = 0;
    },
  
    extendYRange : function (axis, data, options, bars) {
      this._extendRange(axis, data, options, bars);
    },
    _extendRange: function (axis, data, options, bars) {
  
      var
        max = axis.options.max;
  
      if (_.isNumber(max) || _.isString(max)) return; 
  
      var
        newmin = axis.min,
        newmax = axis.max,
        horizontal = options.horizontal,
        orientation = axis.orientation,
        positiveSums = this.positiveSums || {},
        negativeSums = this.negativeSums || {},
        value, datum, index, j;
  
      // Sides of bars
      if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) {
        if (options.centered) {
          newmax = Math.max(axis.datamax + options.barWidth, newmax);
          newmin = Math.min(axis.datamin - options.barWidth, newmin);
        }
      }
  
      if (options.stacked && 
          ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){
  
        for (j = data.length; j--;) {
          value = data[j][(orientation == 1 ? 1 : 0)]+'';
          datum = data[j][(orientation == 1 ? 0 : 1)];
  
          // Positive
          if (datum > 0) {
            positiveSums[value] = (positiveSums[value] || 0) + datum;
            newmax = Math.max(newmax, positiveSums[value]);
          }
  
          // Negative
          else {
            negativeSums[value] = (negativeSums[value] || 0) + datum;
            newmin = Math.min(newmin, negativeSums[value]);
          }
        }
      }
  
      // End of bars
      if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) {
        if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) {
          newmax += options.topPadding * (newmax - newmin);
        }
      }
  
      this.stackMin = newmin;
      this.stackMax = newmax;
      this.negativeSums = negativeSums;
      this.positiveSums = positiveSums;
  
      axis.max = newmax;
      axis.min = newmin;
    }
  
  });
  
  /** Bubbles **/
  Flotr.addType('bubbles', {
    options: {
      show: false,      // => setting to true will show radar chart, false will hide
      lineWidth: 2,     // => line width in pixels
      fill: true,       // => true to fill the area from the line to the x axis, false for (transparent) no fill
      fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      baseRadius: 2     // => ratio of the radar, against the plot size
    },
    draw : function (options) {
      var
        context     = options.context,
        shadowSize  = options.shadowSize;
  
      context.save();
      context.lineWidth = options.lineWidth;
      
      // Shadows
      context.fillStyle = 'rgba(0,0,0,0.05)';
      context.strokeStyle = 'rgba(0,0,0,0.05)';
      this.plot(options, shadowSize / 2);
      context.strokeStyle = 'rgba(0,0,0,0.1)';
      this.plot(options, shadowSize / 4);
  
      // Chart
      context.strokeStyle = options.color;
      context.fillStyle = options.fillStyle;
      this.plot(options);
      
      context.restore();
    },
    plot : function (options, offset) {
  
      var
        data    = options.data,
        context = options.context,
        geometry,
        i, x, y, z;
  
      offset = offset || 0;
      
      for (i = 0; i < data.length; ++i){
  
        geometry = this.getGeometry(data[i], options);
  
        context.beginPath();
        context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true);
        context.stroke();
        if (options.fill) context.fill();
        context.closePath();
      }
    },
    getGeometry : function (point, options) {
      return {
        x : options.xScale(point[0]),
        y : options.yScale(point[1]),
        z : point[2] * options.baseRadius
      };
    },
    hit : function (options) {
      var
        data = options.data,
        args = options.args,
        mouse = args[0],
        n = args[1],
        relX = mouse.relX,
        relY = mouse.relY,
        distance,
        geometry,
        dx, dy;
  
      n.best = n.best || Number.MAX_VALUE;
  
      for (i = data.length; i--;) {
        geometry = this.getGeometry(data[i], options);
  
        dx = geometry.x - relX;
        dy = geometry.y - relY;
        distance = Math.sqrt(dx * dx + dy * dy);
  
        if (distance < geometry.z && geometry.z < n.best) {
          n.x = data[i][0];
          n.y = data[i][1];
          n.index = i;
          n.seriesIndex = options.index;
          n.best = geometry.z;
        }
      }
    },
    drawHit : function (options) {
  
      var
        context = options.context,
        geometry = this.getGeometry(options.data[options.args.index], options);
  
      context.save();
      context.lineWidth = options.lineWidth;
      context.fillStyle = options.fillStyle;
      context.strokeStyle = options.color;
      context.beginPath();
      context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true);
      context.fill();
      context.stroke();
      context.closePath();
      context.restore();
    },
    clearHit : function (options) {
  
      var
        context = options.context,
        geometry = this.getGeometry(options.data[options.args.index], options),
        offset = geometry.z + options.lineWidth;
  
      context.save();
      context.clearRect(
        geometry.x - offset, 
        geometry.y - offset,
        2 * offset,
        2 * offset
      );
      context.restore();
    }
    // TODO Add a hit calculation method (like pie)
  });
  
  /** Candles **/
  Flotr.addType('candles', {
    options: {
      show: false,           // => setting to true will show candle sticks, false will hide
      lineWidth: 1,          // => in pixels
      wickLineWidth: 1,      // => in pixels
      candleWidth: 0.6,      // => in units of the x axis
      fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
      upFillColor: '#00A8F0',// => up sticks fill color
      downFillColor: '#CB4B4B',// => down sticks fill color
      fillOpacity: 0.5,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      // TODO Test this barcharts option.
      barcharts: false       // => draw as barcharts (not standard bars but financial barcharts)
    },
  
    draw : function (options) {
  
      var
        context = options.context;
  
      context.save();
      context.lineJoin = 'miter';
      context.lineCap = 'butt';
      // @TODO linewidth not interpreted the right way.
      context.lineWidth = options.wickLineWidth || options.lineWidth;
  
      this.plot(options);
  
      context.restore();
    },
  
    plot : function (options) {
  
      var
        data          = options.data,
        context       = options.context,
        xScale        = options.xScale,
        yScale        = options.yScale,
        width         = options.candleWidth / 2,
        shadowSize    = options.shadowSize,
        lineWidth     = options.lineWidth,
        wickLineWidth = options.wickLineWidth,
        pixelOffset   = (wickLineWidth % 2) / 2,
        color,
        datum, x, y,
        open, high, low, close,
        left, right, bottom, top, bottom2, top2,
        i;
  
      if (data.length < 1) return;
  
      for (i = 0; i < data.length; i++) {
        datum   = data[i];
        x       = datum[0];
        open    = datum[1];
        high    = datum[2];
        low     = datum[3];
        close   = datum[4];
        left    = xScale(x - width);
        right   = xScale(x + width);
        bottom  = yScale(low);
        top     = yScale(high);
        bottom2 = yScale(Math.min(open, close));
        top2    = yScale(Math.max(open, close));
  
        /*
        // TODO skipping
        if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
          continue;
        */
  
        color = options[open > close ? 'downFillColor' : 'upFillColor'];
  
        // Fill the candle.
        // TODO Test the barcharts option
        if (options.fill && !options.barcharts) {
          context.fillStyle = 'rgba(0,0,0,0.05)';
          context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2);
          context.save();
          context.globalAlpha = options.fillOpacity;
          context.fillStyle = color;
          context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2);
          context.restore();
        }
  
        // Draw candle outline/border, high, low.
        if (lineWidth || wickLineWidth) {
  
          x = Math.floor((left + right) / 2) + pixelOffset;
  
          context.strokeStyle = color;
          context.beginPath();
  
          // TODO Again with the bartcharts
          if (options.barcharts) {
            
            context.moveTo(x, Math.floor(top + width));
            context.lineTo(x, Math.floor(bottom + width));
            
            y = Math.floor(open + width) + 0.5;
            context.moveTo(Math.floor(left) + pixelOffset, y);
            context.lineTo(x, y);
            
            y = Math.floor(close + width) + 0.5;
            context.moveTo(Math.floor(right) + pixelOffset, y);
            context.lineTo(x, y);
          } else {
            context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2);
            context.moveTo(x, Math.floor(top2 + lineWidth));
            context.lineTo(x, Math.floor(top + lineWidth));
            context.moveTo(x, Math.floor(bottom2 + lineWidth));
            context.lineTo(x, Math.floor(bottom + lineWidth));
          }
          
          context.closePath();
          context.stroke();
        }
      }
    },
  
    hit : function (options) {
      var
        xScale = options.xScale,
        yScale = options.yScale,
        data = options.data,
        args = options.args,
        mouse = args[0],
        width = options.candleWidth / 2,
        n = args[1],
        x = mouse.relX,
        y = mouse.relY,
        length = data.length,
        i, datum,
        high, low,
        left, right, top, bottom;
  
      for (i = 0; i < length; i++) {
        datum   = data[i],
        high    = datum[2];
        low     = datum[3];
        left    = xScale(datum[0] - width);
        right   = xScale(datum[0] + width);
        bottom  = yScale(low);
        top     = yScale(high);
  
        if (x > left && x < right && y > top && y < bottom) {
          n.x = datum[0];
          n.index = i;
          n.seriesIndex = options.index;
          return;
        }
      }
    },
  
    drawHit : function (options) {
      var
        context = options.context;
      context.save();
      this.plot(
        _.defaults({
          fill : !!options.fillColor,
          upFillColor : options.color,
          downFillColor : options.color,
          data : [options.data[options.args.index]]
        }, options)
      );
      context.restore();
    },
  
    clearHit : function (options) {
      var
        args = options.args,
        context = options.context,
        xScale = options.xScale,
        yScale = options.yScale,
        lineWidth = options.lineWidth,
        width = options.candleWidth / 2,
        bar = options.data[args.index],
        left = xScale(bar[0] - width) - lineWidth,
        right = xScale(bar[0] + width) + lineWidth,
        top = yScale(bar[2]),
        bottom = yScale(bar[3]) + lineWidth;
      context.clearRect(left, top, right - left, bottom - top);
    },
  
    extendXRange: function (axis, data, options) {
      if (axis.options.max === null) {
        axis.max = Math.max(axis.datamax + 0.5, axis.max);
        axis.min = Math.min(axis.datamin - 0.5, axis.min);
      }
    }
  });
  
  /** Gantt
   * Base on data in form [s,y,d] where:
   * y - executor or simply y value
   * s - task start value
   * d - task duration
   * **/
  Flotr.addType('gantt', {
    options: {
      show: false,           // => setting to true will show gantt, false will hide
      lineWidth: 2,          // => in pixels
      barWidth: 1,           // => in units of the x axis
      fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
      fillColor: null,       // => fill color
      fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      centered: true         // => center the bars to their x axis value
    },
    /**
     * Draws gantt series in the canvas element.
     * @param {Object} series - Series with options.gantt.show = true.
     */
    draw: function(series) {
      var ctx = this.ctx,
        bw = series.gantt.barWidth,
        lw = Math.min(series.gantt.lineWidth, bw);
      
      ctx.save();
      ctx.translate(this.plotOffset.left, this.plotOffset.top);
      ctx.lineJoin = 'miter';
  
      /**
       * @todo linewidth not interpreted the right way.
       */
      ctx.lineWidth = lw;
      ctx.strokeStyle = series.color;
      
      ctx.save();
      this.gantt.plotShadows(series, bw, 0, series.gantt.fill);
      ctx.restore();
      
      if(series.gantt.fill){
        var color = series.gantt.fillColor || series.color;
        ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity});
      }
      
      this.gantt.plot(series, bw, 0, series.gantt.fill);
      ctx.restore();
    },
    plot: function(series, barWidth, offset, fill){
      var data = series.data;
      if(data.length < 1) return;
      
      var xa = series.xaxis,
          ya = series.yaxis,
          ctx = this.ctx, i;
  
      for(i = 0; i < data.length; i++){
        var y = data[i][0],
            s = data[i][1],
            d = data[i][2],
            drawLeft = true, drawTop = true, drawRight = true;
        
        if (s === null || d === null) continue;
  
        var left = s, 
            right = s + d,
            bottom = y - (series.gantt.centered ? barWidth/2 : 0), 
            top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
        
        if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
          continue;
  
        if(left < xa.min){
          left = xa.min;
          drawLeft = false;
        }
  
        if(right > xa.max){
          right = xa.max;
          if (xa.lastSerie != series)
            drawTop = false;
        }
  
        if(bottom < ya.min)
          bottom = ya.min;
  
        if(top > ya.max){
          top = ya.max;
          if (ya.lastSerie != series)
            drawTop = false;
        }
        
        /**
         * Fill the bar.
         */
        if(fill){
          ctx.beginPath();
          ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
          ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset);
          ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset);
          ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset);
          ctx.fill();
          ctx.closePath();
        }
  
        /**
         * Draw bar outline/border.
         */
        if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){
          ctx.beginPath();
          ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
          
          ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset);
          ctx[drawTop  ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset);
          ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset);
                   
          ctx.stroke();
          ctx.closePath();
        }
      }
    },
    plotShadows: function(series, barWidth, offset){
      var data = series.data;
      if(data.length < 1) return;
      
      var i, y, s, d,
          xa = series.xaxis,
          ya = series.yaxis,
          ctx = this.ctx,
          sw = this.options.shadowSize;
      
      for(i = 0; i < data.length; i++){
        y = data[i][0];
        s = data[i][1];
        d = data[i][2];
          
        if (s === null || d === null) continue;
              
        var left = s, 
            right = s + d,
            bottom = y - (series.gantt.centered ? barWidth/2 : 0), 
            top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
   
        if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
          continue;
        
        if(left < xa.min)   left = xa.min;
        if(right > xa.max)  right = xa.max;
        if(bottom < ya.min) bottom = ya.min;
        if(top > ya.max)    top = ya.max;
        
        var width =  xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw);
        var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw );
        
        ctx.fillStyle = 'rgba(0,0,0,0.05)';
        ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height);
      }
    },
    extendXRange: function(axis) {
      if(axis.options.max === null){
        var newmin = axis.min,
            newmax = axis.max,
            i, j, x, s, g,
            stackedSumsPos = {},
            stackedSumsNeg = {},
            lastSerie = null;
  
        for(i = 0; i < this.series.length; ++i){
          s = this.series[i];
          g = s.gantt;
          
          if(g.show && s.xaxis == axis) {
              for (j = 0; j < s.data.length; j++) {
                if (g.show) {
                  y = s.data[j][0]+'';
                  stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]);
                  lastSerie = s;
                }
              }
              for (j in stackedSumsPos) {
                newmax = Math.max(stackedSumsPos[j], newmax);
              }
          }
        }
        axis.lastSerie = lastSerie;
        axis.max = newmax;
        axis.min = newmin;
      }
    },
    extendYRange: function(axis){
      if(axis.options.max === null){
        var newmax = Number.MIN_VALUE,
            newmin = Number.MAX_VALUE,
            i, j, s, g,
            stackedSumsPos = {},
            stackedSumsNeg = {},
            lastSerie = null;
                    
        for(i = 0; i < this.series.length; ++i){
          s = this.series[i];
          g = s.gantt;
          
          if (g.show && !s.hide && s.yaxis == axis) {
            var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE;
            for(j=0; j < s.data.length; j++){
              datamax = Math.max(datamax,s.data[j][0]);
              datamin = Math.min(datamin,s.data[j][0]);
            }
              
            if (g.centered) {
              newmax = Math.max(datamax + 0.5, newmax);
              newmin = Math.min(datamin - 0.5, newmin);
            }
          else {
            newmax = Math.max(datamax + 1, newmax);
              newmin = Math.min(datamin, newmin);
            }
            // For normal horizontal bars
            if (g.barWidth + datamax > newmax){
              newmax = axis.max + g.barWidth;
            }
          }
        }
        axis.lastSerie = lastSerie;
        axis.max = newmax;
        axis.min = newmin;
        axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals);
      }
    }
  });
  
  /** Markers **/
  /**
   * Formats the marker labels.
   * @param {Object} obj - Marker value Object {x:..,y:..}
   * @return {String} Formatted marker string
   */
  (function () {
  
  Flotr.defaultMarkerFormatter = function(obj){
    return (Math.round(obj.y*100)/100)+'';
  };
  
  Flotr.addType('markers', {
    options: {
      show: false,           // => setting to true will show markers, false will hide
      lineWidth: 1,          // => line width of the rectangle around the marker
      color: '#000000',      // => text color
      fill: false,           // => fill or not the marekers' rectangles
      fillColor: "#FFFFFF",  // => fill color
      fillOpacity: 0.4,      // => fill opacity
      stroke: false,         // => draw the rectangle around the markers
      position: 'ct',        // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
      verticalMargin: 0,     // => the margin between the point and the text.
      labelFormatter: Flotr.defaultMarkerFormatter,
      fontSize: Flotr.defaultOptions.fontSize,
      stacked: false,        // => true if markers should be stacked
      stackingType: 'b',     // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
      horizontal: false      // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
    },
  
    // TODO test stacked markers.
    stack : {
        positive : [],
        negative : [],
        values : []
    },
  
    draw : function (options) {
  
      var
        data            = options.data,
        context         = options.context,
        stack           = options.stacked ? options.stack : false,
        stackType       = options.stackingType,
        stackOffsetNeg,
        stackOffsetPos,
        stackOffset,
        i, x, y, label;
  
      context.save();
      context.lineJoin = 'round';
      context.lineWidth = options.lineWidth;
      context.strokeStyle = 'rgba(0,0,0,0.5)';
      context.fillStyle = options.fillStyle;
  
      function stackPos (a, b) {
        stackOffsetPos = stack.negative[a] || 0;
        stackOffsetNeg = stack.positive[a] || 0;
        if (b > 0) {
          stack.positive[a] = stackOffsetPos + b;
          return stackOffsetPos + b;
        } else {
          stack.negative[a] = stackOffsetNeg + b;
          return stackOffsetNeg + b;
        }
      }
  
      for (i = 0; i < data.length; ++i) {
      
        x = data[i][0];
        y = data[i][1];
          
        if (stack) {
          if (stackType == 'b') {
            if (options.horizontal) y = stackPos(y, x);
            else x = stackPos(x, y);
          } else if (stackType == 'a') {
            stackOffset = stack.values[x] || 0;
            stack.values[x] = stackOffset + y;
            y = stackOffset + y;
          }
        }
  
        label = options.labelFormatter({x: x, y: y, index: i, data : data});
        this.plot(options.xScale(x), options.yScale(y), label, options);
      }
      context.restore();
    },
    plot: function(x, y, label, options) {
      var context = options.context;
      if (isImage(label) && !label.complete) {
        throw 'Marker image not loaded.';
      } else {
        this._plot(x, y, label, options);
      }
    },
  
    _plot: function(x, y, label, options) {
      var context = options.context,
          margin = 2,
          left = x,
          top = y,
          dim;
  
      if (isImage(label))
        dim = {height : label.height, width: label.width};
      else
        dim = options.text.canvas(label);
  
      dim.width = Math.floor(dim.width+margin*2);
      dim.height = Math.floor(dim.height+margin*2);
  
           if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
      else if (options.position.indexOf('l') != -1) left -= dim.width;
      
           if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
      else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin;
      else top += options.verticalMargin;
      
      left = Math.floor(left)+0.5;
      top = Math.floor(top)+0.5;
      
      if(options.fill)
        context.fillRect(left, top, dim.width, dim.height);
        
      if(options.stroke)
        context.strokeRect(left, top, dim.width, dim.height);
      
      if (isImage(label))
        context.drawImage(label, parseInt(left+margin, 10), parseInt(top+margin, 10));
      else
        Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
    }
  });
  
  function isImage (i) {
    return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
  }
  
  })();
  
  /**
   * Pie
   *
   * Formats the pies labels.
   * @param {Object} slice - Slice object
   * @return {String} Formatted pie label string
   */
  (function () {
  
  var
    _ = Flotr._;
  
  Flotr.defaultPieLabelFormatter = function (total, value) {
    return (100 * value / total).toFixed(2)+'%';
  };
  
  Flotr.addType('pie', {
    options: {
      show: false,           // => setting to true will show bars, false will hide
      lineWidth: 1,          // => in pixels
      fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
      fillColor: null,       // => fill color
      fillOpacity: 0.6,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      explode: 6,            // => the number of pixels the splices will be far from the center
      sizeRatio: 0.6,        // => the size ratio of the pie relative to the plot 
      startAngle: Math.PI/4, // => the first slice start angle
      labelFormatter: Flotr.defaultPieLabelFormatter,
      pie3D: false,          // => whether to draw the pie in 3 dimenstions or not (ineffective) 
      pie3DviewAngle: (Math.PI/2 * 0.8),
      pie3DspliceThickness: 20,
      epsilon: 0.1           // => how close do you have to get to hit empty slice
    },
  
    draw : function (options) {
  
      // TODO 3D charts what?
  
      var
        data          = options.data,
        context       = options.context,
        canvas        = context.canvas,
        lineWidth     = options.lineWidth,
        shadowSize    = options.shadowSize,
        sizeRatio     = options.sizeRatio,
        height        = options.height,
        width         = options.width,
        explode       = options.explode,
        color         = options.color,
        fill          = options.fill,
        fillStyle     = options.fillStyle,
        radius        = Math.min(canvas.width, canvas.height) * sizeRatio / 2,
        value         = data[0][1],
        html          = [],
        vScale        = 1,//Math.cos(series.pie.viewAngle);
        measure       = Math.PI * 2 * value / this.total,
        startAngle    = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
        endAngle      = startAngle + measure,
        bisection     = startAngle + measure / 2,
        label         = options.labelFormatter(this.total, value),
        //plotTickness  = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;
        explodeCoeff  = explode + radius + 4,
        distX         = Math.cos(bisection) * explodeCoeff,
        distY         = Math.sin(bisection) * explodeCoeff,
        textAlign     = distX < 0 ? 'right' : 'left',
        textBaseline  = distY > 0 ? 'top' : 'bottom',
        style,
        x, y;
      
      context.save();
      context.translate(width / 2, height / 2);
      context.scale(1, vScale);
  
      x = Math.cos(bisection) * explode;
      y = Math.sin(bisection) * explode;
  
      // Shadows
      if (shadowSize > 0) {
        this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context);
        if (fill) {
          context.fillStyle = 'rgba(0,0,0,0.1)';
          context.fill();
        }
      }
  
      this.plotSlice(x, y, radius, startAngle, endAngle, context);
      if (fill) {
        context.fillStyle = fillStyle;
        context.fill();
      }
      context.lineWidth = lineWidth;
      context.strokeStyle = color;
      context.stroke();
  
      style = {
        size : options.fontSize * 1.2,
        color : options.fontColor,
        weight : 1.5
      };
  
      if (label) {
        if (options.htmlText || !options.textEnabled) {
          divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;';
          divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;';
          html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>');
        }
        else {
          style.textAlign = textAlign;
          style.textBaseline = textBaseline;
          Flotr.drawText(context, label, distX, distY, style);
        }
      }
      
      if (options.htmlText || !options.textEnabled) {
        var div = Flotr.DOM.node('<div style="color:' + options.fontColor + '" class="flotr-labels"></div>');
        Flotr.DOM.insert(div, html.join(''));
        Flotr.DOM.insert(options.element, div);
      }
      
      context.restore();
  
      // New start angle
      this.startAngle = endAngle;
      this.slices = this.slices || [];
      this.slices.push({
        radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2,
        x : x,
        y : y,
        explode : explode,
        start : startAngle,
        end : endAngle
      });
    },
    plotSlice : function (x, y, radius, startAngle, endAngle, context) {
      context.beginPath();
      context.moveTo(x, y);
      context.arc(x, y, radius, startAngle, endAngle, false);
      context.lineTo(x, y);
      context.closePath();
    },
    hit : function (options) {
  
      var
        data      = options.data[0],
        args      = options.args,
        index     = options.index,
        mouse     = args[0],
        n         = args[1],
        slice     = this.slices[index],
        x         = mouse.relX - options.width / 2,
        y         = mouse.relY - options.height / 2,
        r         = Math.sqrt(x * x + y * y),
        theta     = Math.atan(y / x),
        circle    = Math.PI * 2,
        explode   = slice.explode || options.explode,
        start     = slice.start % circle,
        end       = slice.end % circle,
        epsilon   = options.epsilon;
  
      if (x < 0) {
        theta += Math.PI;
      } else if (x > 0 && y < 0) {
        theta += circle;
      }
  
      if (r < slice.radius + explode && r > explode) {
        if (
            (theta > start && theta < end) || // Normal Slice
            (start > end && (theta < end || theta > start)) || // First slice
            // TODO: Document the two cases at the end:
            (start === end && ((slice.start === slice.end && Math.abs(theta - start) < epsilon) || (slice.start !== slice.end && Math.abs(theta-start) > epsilon)))
           ) {
            
            // TODO Decouple this from hit plugin (chart shouldn't know what n means)
           n.x = data[0];
           n.y = data[1];
           n.sAngle = start;
           n.eAngle = end;
           n.index = 0;
           n.seriesIndex = index;
           n.fraction = data[1] / this.total;
        }
      }
    },
    drawHit: function (options) {
      var
        context = options.context,
        slice = this.slices[options.args.seriesIndex];
  
      context.save();
      context.translate(options.width / 2, options.height / 2);
      this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context);
      context.stroke();
      context.restore();
    },
    clearHit : function (options) {
      var
        context = options.context,
        slice = this.slices[options.args.seriesIndex],
        padding = 2 * options.lineWidth,
        radius = slice.radius + padding;
  
      context.save();
      context.translate(options.width / 2, options.height / 2);
      context.clearRect(
        slice.x - radius,
        slice.y - radius,
        2 * radius + padding,
        2 * radius + padding 
      );
      context.restore();
    },
    extendYRange : function (axis, data) {
      this.total = (this.total || 0) + data[0][1];
    }
  });
  })();
  
  /** Points **/
  Flotr.addType('points', {
    options: {
      show: false,           // => setting to true will show points, false will hide
      radius: 3,             // => point radius (pixels)
      lineWidth: 2,          // => line width in pixels
      fill: true,            // => true to fill the points with a color, false for (transparent) no fill
      fillColor: '#FFFFFF',  // => fill color.  Null to use series color.
      fillOpacity: 1,        // => opacity of color inside the points
      hitRadius: null        // => override for points hit radius
    },
  
    draw : function (options) {
      var
        context     = options.context,
        lineWidth   = options.lineWidth,
        shadowSize  = options.shadowSize;
  
      context.save();
  
      if (shadowSize > 0) {
        context.lineWidth = shadowSize / 2;
        
        context.strokeStyle = 'rgba(0,0,0,0.1)';
        this.plot(options, shadowSize / 2 + context.lineWidth / 2);
  
        context.strokeStyle = 'rgba(0,0,0,0.2)';
        this.plot(options, context.lineWidth / 2);
      }
  
      context.lineWidth = options.lineWidth;
      context.strokeStyle = options.color;
      if (options.fill) context.fillStyle = options.fillStyle;
  
      this.plot(options);
      context.restore();
    },
  
    plot : function (options, offset) {
      var
        data    = options.data,
        context = options.context,
        xScale  = options.xScale,
        yScale  = options.yScale,
        i, x, y;
        
      for (i = data.length - 1; i > -1; --i) {
        y = data[i][1];
        if (y === null) continue;
  
        x = xScale(data[i][0]);
        y = yScale(y);
  
        if (x < 0 || x > options.width || y < 0 || y > options.height) continue;
        
        context.beginPath();
        if (offset) {
          context.arc(x, y + offset, options.radius, 0, Math.PI, false);
        } else {
          context.arc(x, y, options.radius, 0, 2 * Math.PI, true);
          if (options.fill) context.fill();
        }
        context.stroke();
        context.closePath();
      }
    }
  });
  
  /** Radar **/
  Flotr.addType('radar', {
    options: {
      show: false,           // => setting to true will show radar chart, false will hide
      lineWidth: 2,          // => line width in pixels
      fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
      fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
      radiusRatio: 0.90,      // => ratio of the radar, against the plot size
      sensibility: 2         // => the lower this number, the more precise you have to aim to show a value.
    },
    draw : function (options) {
      var
        context = options.context,
        shadowSize = options.shadowSize;
  
      context.save();
      context.translate(options.width / 2, options.height / 2);
      context.lineWidth = options.lineWidth;
      
      // Shadow
      context.fillStyle = 'rgba(0,0,0,0.05)';
      context.strokeStyle = 'rgba(0,0,0,0.05)';
      this.plot(options, shadowSize / 2);
      context.strokeStyle = 'rgba(0,0,0,0.1)';
      this.plot(options, shadowSize / 4);
  
      // Chart
      context.strokeStyle = options.color;
      context.fillStyle = options.fillStyle;
      this.plot(options);
      
      context.restore();
    },
    plot : function (options, offset) {
      var
        data    = options.data,
        context = options.context,
        radius  = Math.min(options.height, options.width) * options.radiusRatio / 2,
        step    = 2 * Math.PI / data.length,
        angle   = -Math.PI / 2,
        i, ratio;
  
      offset = offset || 0;
  
      context.beginPath();
      for (i = 0; i < data.length; ++i) {
        ratio = data[i][1] / this.max;
  
        context[i === 0 ? 'moveTo' : 'lineTo'](
          Math.cos(i * step + angle) * radius * ratio + offset,
          Math.sin(i * step + angle) * radius * ratio + offset
        );
      }
      context.closePath();
      if (options.fill) context.fill();
      context.stroke();
    },
    getGeometry : function (point, options) {
      var
        radius  = Math.min(options.height, options.width) * options.radiusRatio / 2,
        step    = 2 * Math.PI / options.data.length,
        angle   = -Math.PI / 2,
        ratio = point[1] / this.max;
  
      return {
        x : (Math.cos(point[0] * step + angle) * radius * ratio) + options.width / 2,
        y : (Math.sin(point[0] * step + angle) * radius * ratio) + options.height / 2
      };
    },
    hit : function (options) {
      var
        args = options.args,
        mouse = args[0],
        n = args[1],
        relX = mouse.relX,
        relY = mouse.relY,
        distance,
        geometry,
        dx, dy;
  
        for (var i = 0; i < n.series.length; i++) {
          var serie = n.series[i];
          var data = serie.data;
  
          for (var j = data.length; j--;) {
            geometry = this.getGeometry(data[j], options);
  
            dx = geometry.x - relX;
            dy = geometry.y - relY;
            distance = Math.sqrt(dx * dx + dy * dy);
  
            if (distance <  options.sensibility*2) {
              n.x = data[j][0];
              n.y = data[j][1];
              n.index = j;
              n.seriesIndex = i;
              return n;
            }
          }
        }
      },
    drawHit : function (options) {
      var step = 2 * Math.PI / options.data.length;
      var angle   = -Math.PI / 2;
      var radius  = Math.min(options.height, options.width) * options.radiusRatio / 2;
  
      var s = options.args.series;
      var point_radius = s.points.hitRadius || s.points.radius || s.mouse.radius;
  
      var context = options.context;
  
      context.translate(options.width / 2, options.height / 2);
  
      var j = options.args.index;
      var ratio = options.data[j][1] / this.max;
      var x = Math.cos(j * step + angle) * radius * ratio;
      var y = Math.sin(j * step + angle) * radius * ratio;
      context.beginPath();
      context.arc(x, y, point_radius , 0, 2 * Math.PI, true);
      context.closePath();
      context.stroke();
    },
    clearHit : function (options) {
      var step = 2 * Math.PI / options.data.length;
      var angle   = -Math.PI / 2;
      var radius  = Math.min(options.height, options.width) * options.radiusRatio / 2;
  
      var context = options.context;
  
      var
          s = options.args.series,
          lw = (s.points ? s.points.lineWidth : 1);
          offset = (s.points.hitRadius || s.points.radius || s.mouse.radius) + lw;
  
      context.translate(options.width / 2, options.height / 2);
  
      var j = options.args.index;
      var ratio = options.data[j][1] / this.max;
      var x = Math.cos(j * step + angle) * radius * ratio;
      var y = Math.sin(j * step + angle) * radius * ratio;
      context.clearRect(x-offset,y-offset,offset*2,offset*2);
    },
    extendYRange : function (axis, data) {
      this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE);
    }
  });
  
  Flotr.addType('timeline', {
    options: {
      show: false,
      lineWidth: 1,
      barWidth: 0.2,
      fill: true,
      fillColor: null,
      fillOpacity: 0.4,
      centered: true
    },
  
    draw : function (options) {
  
      var
        context = options.context;
  
      context.save();
      context.lineJoin    = 'miter';
      context.lineWidth   = options.lineWidth;
      context.strokeStyle = options.color;
      context.fillStyle   = options.fillStyle;
  
      this.plot(options);
  
      context.restore();
    },
  
    plot : function (options) {
  
      var
        data      = options.data,
        context   = options.context,
        xScale    = options.xScale,
        yScale    = options.yScale,
        barWidth  = options.barWidth,
        lineWidth = options.lineWidth,
        i;
  
      Flotr._.each(data, function (timeline) {
  
        var 
          x   = timeline[0],
          y   = timeline[1],
          w   = timeline[2],
          h   = barWidth,
  
          xt  = Math.ceil(xScale(x)),
          wt  = Math.ceil(xScale(x + w)) - xt,
          yt  = Math.round(yScale(y)),
          ht  = Math.round(yScale(y - h)) - yt,
  
          x0  = xt - lineWidth / 2,
          y0  = Math.round(yt - ht / 2) - lineWidth / 2;
  
        context.strokeRect(x0, y0, wt, ht);
        context.fillRect(x0, y0, wt, ht);
  
      });
    },
  
    extendRange : function (series) {
  
      var
        data  = series.data,
        xa    = series.xaxis,
        ya    = series.yaxis,
        w     = series.timeline.barWidth;
  
      if (xa.options.min === null)
        xa.min = xa.datamin - w / 2;
  
      if (xa.options.max === null) {
  
        var
          max = xa.max;
  
        Flotr._.each(data, function (timeline) {
          max = Math.max(max, timeline[0] + timeline[2]);
        }, this);
  
        xa.max = max + w / 2;
      }
  
      if (ya.options.min === null)
        ya.min = ya.datamin - w;
      if (ya.options.min === null)
        ya.max = ya.datamax + w;
    }
  
  });
  
  (function () {
  
  var D = Flotr.DOM;
  
  Flotr.addPlugin('crosshair', {
    options: {
      mode: null,            // => one of null, 'x', 'y' or 'xy'
      color: '#FF0000',      // => crosshair color
      hideCursor: true       // => hide the cursor when the crosshair is shown
    },
    callbacks: {
      'flotr:mousemove': function(e, pos) {
        if (this.options.crosshair.mode) {
          this.crosshair.clearCrosshair();
          this.crosshair.drawCrosshair(pos);
        }
      }
    },
    /**   
     * Draws the selection box.
     */
    drawCrosshair: function(pos) {
      var octx = this.octx,
        options = this.options.crosshair,
        plotOffset = this.plotOffset,
        x = plotOffset.left + Math.round(pos.relX) + 0.5,
        y = plotOffset.top + Math.round(pos.relY) + 0.5;
      
      if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) {
        this.el.style.cursor = null;
        D.removeClass(this.el, 'flotr-crosshair');
        return; 
      }
      
      if (options.hideCursor) {
        this.el.style.cursor = 'none';
        D.addClass(this.el, 'flotr-crosshair');
      }
      
      octx.save();
      octx.strokeStyle = options.color;
      octx.lineWidth = 1;
      octx.beginPath();
      
      if (options.mode.indexOf('x') != -1) {
        octx.moveTo(x, plotOffset.top);
        octx.lineTo(x, plotOffset.top + this.plotHeight);
      }
      
      if (options.mode.indexOf('y') != -1) {
        octx.moveTo(plotOffset.left, y);
        octx.lineTo(plotOffset.left + this.plotWidth, y);
      }
      
      octx.stroke();
      octx.restore();
    },
    /**
     * Removes the selection box from the overlay canvas.
     */
    clearCrosshair: function() {
  
      var
        plotOffset = this.plotOffset,
        position = this.lastMousePos,
        context = this.octx;
  
      if (position) {
        context.clearRect(
          Math.round(position.relX) + plotOffset.left,
          plotOffset.top,
          1,
          this.plotHeight + 1
        );
        context.clearRect(
          plotOffset.left,
          Math.round(position.relY) + plotOffset.top,
          this.plotWidth + 1,
          1
        );    
      }
    }
  });
  })();
  
  (function() {
  
  var
    D = Flotr.DOM,
    _ = Flotr._;
  
  function getImage (type, canvas, context, width, height, background) {
  
    // TODO add scaling for w / h
    var
      mime = 'image/'+type,
      data = context.getImageData(0, 0, width, height),
      image = new Image();
  
    context.save();
    context.globalCompositeOperation = 'destination-over';
    context.fillStyle = background;
    context.fillRect(0, 0, width, height);
    image.src = canvas.toDataURL(mime);
    context.restore();
  
    context.clearRect(0, 0, width, height);
    context.putImageData(data, 0, 0);
  
    return image;
  }
  
  Flotr.addPlugin('download', {
  
    saveImage: function (type, width, height, replaceCanvas) {
      var
        grid = this.options.grid,
        image;
  
      if (Flotr.isIE && Flotr.isIE < 9) {
        image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>';
        return window.open().document.write(image);
      }
  
      if (type !== 'jpeg' && type !== 'png') return;
  
      image = getImage(
        type, this.canvas, this.ctx,
        this.canvasWidth, this.canvasHeight,
        grid && grid.backgroundColor || '#ffffff'
      );
  
      if (_.isElement(image) && replaceCanvas) {
        this.download.restoreCanvas();
        D.hide(this.canvas);
        D.hide(this.overlay);
        D.setStyles({position: 'absolute'});
        D.insert(this.el, image);
        this.saveImageElement = image;
      } else {
        return window.open(image.src);
      }
    },
  
    restoreCanvas: function() {
      D.show(this.canvas);
      D.show(this.overlay);
      if (this.saveImageElement) this.el.removeChild(this.saveImageElement);
      this.saveImageElement = null;
    }
  });
  
  })();
  
  (function () {
  
  var E = Flotr.EventAdapter,
      _ = Flotr._;
  
  Flotr.addPlugin('graphGrid', {
  
    callbacks: {
      'flotr:beforedraw' : function () {
        this.graphGrid.drawGrid();
      },
      'flotr:afterdraw' : function () {
        this.graphGrid.drawOutline();
      }
    },
  
    drawGrid: function(){
  
      var
        ctx = this.ctx,
        options = this.options,
        grid = options.grid,
        verticalLines = grid.verticalLines,
        horizontalLines = grid.horizontalLines,
        minorVerticalLines = grid.minorVerticalLines,
        minorHorizontalLines = grid.minorHorizontalLines,
        plotHeight = this.plotHeight,
        plotWidth = this.plotWidth,
        a, v, i, j;
          
      if(verticalLines || minorVerticalLines || 
             horizontalLines || minorHorizontalLines){
        E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]);
      }
      ctx.save();
      ctx.lineWidth = 1;
      ctx.strokeStyle = grid.tickColor;
      
      function circularHorizontalTicks (ticks) {
        for(i = 0; i < ticks.length; ++i){
          var ratio = ticks[i].v / a.max;
          for(j = 0; j <= sides; ++j){
            ctx[j === 0 ? 'moveTo' : 'lineTo'](
              Math.cos(j*coeff+angle)*radius*ratio,
              Math.sin(j*coeff+angle)*radius*ratio
            );
          }
        }
      }
      function drawGridLines (ticks, callback) {
        _.each(_.pluck(ticks, 'v'), function(v){
          // Don't show lines on upper and lower bounds.
          if ((v <= a.min || v >= a.max) || 
              (v == a.min || v == a.max) && grid.outlineWidth)
            return;
          callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2);
        });
      }
      function drawVerticalLines (x) {
        ctx.moveTo(x, 0);
        ctx.lineTo(x, plotHeight);
      }
      function drawHorizontalLines (y) {
        ctx.moveTo(0, y);
        ctx.lineTo(plotWidth, y);
      }
  
      if (grid.circular) {
        ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2);
        var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2,
            sides = this.axes.x.ticks.length,
            coeff = 2*(Math.PI/sides),
            angle = -Math.PI/2;
        
        // Draw grid lines in vertical direction.
        ctx.beginPath();
        
        a = this.axes.y;
  
        if(horizontalLines){
          circularHorizontalTicks(a.ticks);
        }
        if(minorHorizontalLines){
          circularHorizontalTicks(a.minorTicks);
        }
        
        if(verticalLines){
          _.times(sides, function(i){
            ctx.moveTo(0, 0);
            ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
          });
        }
        ctx.stroke();
      }
      else {
        ctx.translate(this.plotOffset.left, this.plotOffset.top);
    
        // Draw grid background, if present in options.
        if(grid.backgroundColor){
          ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight});
          ctx.fillRect(0, 0, plotWidth, plotHeight);
        }
        
        ctx.beginPath();
  
        a = this.axes.x;
        if (verticalLines)        drawGridLines(a.ticks, drawVerticalLines);
        if (minorVerticalLines)   drawGridLines(a.minorTicks, drawVerticalLines);
  
        a = this.axes.y;
        if (horizontalLines)      drawGridLines(a.ticks, drawHorizontalLines);
        if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines);
  
        ctx.stroke();
      }
      
      ctx.restore();
      if(verticalLines || minorVerticalLines ||
         horizontalLines || minorHorizontalLines){
        E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]);
      }
    }, 
  
    drawOutline: function(){
      var
        that = this,
        options = that.options,
        grid = options.grid,
        outline = grid.outline,
        ctx = that.ctx,
        backgroundImage = grid.backgroundImage,
        plotOffset = that.plotOffset,
        leftOffset = plotOffset.left,
        topOffset = plotOffset.top,
        plotWidth = that.plotWidth,
        plotHeight = that.plotHeight,
        v, img, src, left, top, globalAlpha;
      
      if (!grid.outlineWidth) return;
      
      ctx.save();
      
      if (grid.circular) {
        ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2);
        var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2,
            sides = this.axes.x.ticks.length,
            coeff = 2*(Math.PI/sides),
            angle = -Math.PI/2;
        
        // Draw axis/grid border.
        ctx.beginPath();
        ctx.lineWidth = grid.outlineWidth;
        ctx.strokeStyle = grid.color;
        ctx.lineJoin = 'round';
        
        for(i = 0; i <= sides; ++i){
          ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
        }
        //ctx.arc(0, 0, radius, 0, Math.PI*2, true);
  
        ctx.stroke();
      }
      else {
        ctx.translate(leftOffset, topOffset);
        
        // Draw axis/grid border.
        var lw = grid.outlineWidth,
            orig = 0.5-lw+((lw+1)%2/2),
            lineTo = 'lineTo',
            moveTo = 'moveTo';
        ctx.lineWidth = lw;
        ctx.strokeStyle = grid.color;
        ctx.lineJoin = 'miter';
        ctx.beginPath();
        ctx.moveTo(orig, orig);
        plotWidth = plotWidth - (lw / 2) % 1;
        plotHeight = plotHeight + lw / 2;
        ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig);
        ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight);
        ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight);
        ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig);
        ctx.stroke();
        ctx.closePath();
      }
      
      ctx.restore();
  
      if (backgroundImage) {
  
        src = backgroundImage.src || backgroundImage;
        left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left;
        top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top;
        img = new Image();
  
        img.onload = function() {
          ctx.save();
          if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha;
          ctx.globalCompositeOperation = 'destination-over';
          ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight);
          ctx.restore();
        };
  
        img.src = src;
      }
    }
  });
  
  })();
  
  (function () {
  
  var
    D = Flotr.DOM,
    _ = Flotr._,
    flotr = Flotr,
    S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
  
  Flotr.addPlugin('hit', {
    callbacks: {
      'flotr:mousemove': function(e, pos) {
        this.hit.track(pos);
      },
      'flotr:click': function(pos) {
        var
          hit = this.hit.track(pos);
        if (hit && !_.isUndefined(hit.index)) pos.hit = hit;
      },
      'flotr:mouseout': function(e) {
        if (e.relatedTarget !== this.mouseTrack) {
          this.hit.clearHit();
        }
      },
      'flotr:destroy': function() {
        if (this.options.mouse.container) {
          D.remove(this.mouseTrack);
        }
        this.mouseTrack = null;
      }
    },
    track : function (pos) {
      if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) {
        return this.hit.hit(pos);
      }
    },
    /**
     * Try a method on a graph type.  If the method exists, execute it.
     * @param {Object} series
     * @param {String} method  Method name.
     * @param {Array} args  Arguments applied to method.
     * @return executed successfully or failed.
     */
    executeOnType: function(s, method, args){
      var
        success = false,
        options;
  
      if (!_.isArray(s)) s = [s];
  
      function e(s, index) {
        _.each(_.keys(flotr.graphTypes), function (type) {
          if (s[type] && s[type].show && this[type][method]) {
            options = this.getOptions(s, type);
  
            options.fill = !!s.mouse.fillColor;
            options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
            options.color = s.mouse.lineColor;
            options.context = this.octx;
            options.index = index;
  
            if (args) options.args = args;
            this[type][method].call(this[type], options);
            success = true;
          }
        }, this);
      }
      _.each(s, e, this);
  
      return success;
    },
    /**
     * Updates the mouse tracking point on the overlay.
     */
    drawHit: function(n){
      var octx = this.octx,
        s = n.series;
  
      if (s.mouse.lineColor) {
        octx.save();
        octx.lineWidth = (s.points ? s.points.lineWidth : 1);
        octx.strokeStyle = s.mouse.lineColor;
        octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
        octx.translate(this.plotOffset.left, this.plotOffset.top);
  
        if (!this.hit.executeOnType(s, 'drawHit', n)) {
          var
            xa = n.xaxis,
            ya = n.yaxis;
  
          octx.beginPath();
            // TODO fix this (points) should move to general testable graph mixin
            octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.hitRadius || s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true);
            octx.fill();
            octx.stroke();
          octx.closePath();
        }
        octx.restore();
        this.clip(octx);
      }
      this.prevHit = n;
    },
    /**
     * Removes the mouse tracking point from the overlay.
     */
    clearHit: function(){
      var prev = this.prevHit,
          octx = this.octx,
          plotOffset = this.plotOffset;
      octx.save();
      octx.translate(plotOffset.left, plotOffset.top);
      if (prev) {
        if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) {
          // TODO fix this (points) should move to general testable graph mixin
          var
            s = prev.series,
            lw = (s.points ? s.points.lineWidth : 1);
            offset = (s.points.hitRadius || s.points.radius || s.mouse.radius) + lw;
          octx.clearRect(
            prev.xaxis.d2p(prev.x) - offset,
            prev.yaxis.d2p(prev.y) - offset,
            offset*2,
            offset*2
          );
        }
        D.hide(this.mouseTrack);
        this.prevHit = null;
      }
      octx.restore();
    },
    /**
     * Retrieves the nearest data point from the mouse cursor. If it's within
     * a certain range, draw a point on the overlay canvas and display the x and y
     * value of the data.
     * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
     */
    hit : function (mouse) {
  
      var
        options = this.options,
        prevHit = this.prevHit,
        closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis, n;
  
      if (this.series.length === 0) return;
  
      // Nearest data element.
      // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse,
      // xaxis, yaxis, series, index, seriesIndex
      n = {
        relX : mouse.relX,
        relY : mouse.relY,
        absX : mouse.absX,
        absY : mouse.absY,
        series: this.series
      };
  
      if (options.mouse.trackY &&
          !options.mouse.trackAll &&
          this.hit.executeOnType(this.series, 'hit', [mouse, n]) &&
          !_.isUndefined(n.seriesIndex))
        {
        series    = this.series[n.seriesIndex];
        n.series  = series;
        n.mouse   = series.mouse;
        n.xaxis   = series.xaxis;
        n.yaxis   = series.yaxis;
      } else {
  
        closest = this.hit.closest(mouse);
  
        if (closest) {
  
          closest     = options.mouse.trackY ? closest.point : closest.x;
          seriesIndex = closest.seriesIndex;
          series      = this.series[seriesIndex];
          xaxis       = series.xaxis;
          yaxis       = series.yaxis;
          sensibility = 2 * series.mouse.sensibility;
  
          if
            (options.mouse.trackAll ||
            (closest.distanceX < sensibility / xaxis.scale &&
            (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale)))
          {
            n.series      = series;
            n.xaxis       = series.xaxis;
            n.yaxis       = series.yaxis;
            n.mouse       = series.mouse;
            n.x           = closest.x;
            n.y           = closest.y;
            n.dist        = closest.distance;
            n.index       = closest.dataIndex;
            n.seriesIndex = seriesIndex;
          }
        }
      }
  
      if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) {
        this.hit.clearHit();
        if (n.series && n.mouse && n.mouse.track) {
          this.hit.drawMouseTrack(n);
          this.hit.drawHit(n);
          Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]);
        }
      }
  
      return n;
    },
  
    closest : function (mouse) {
  
      var
        series    = this.series,
        options   = this.options,
        relX      = mouse.relX,
        relY      = mouse.relY,
        compare   = Number.MAX_VALUE,
        compareX  = Number.MAX_VALUE,
        closest   = {},
        closestX  = {},
        check     = false,
        serie, data,
        distance, distanceX, distanceY,
        mouseX, mouseY,
        x, y, i, j;
  
      function setClosest (o) {
        o.distance = distance;
        o.distanceX = distanceX;
        o.distanceY = distanceY;
        o.seriesIndex = i;
        o.dataIndex = j;
        o.x = x;
        o.y = y;
        check = true;
      }
  
      for (i = 0; i < series.length; i++) {
  
        serie = series[i];
        data = serie.data;
        mouseX = serie.xaxis.p2d(relX);
        mouseY = serie.yaxis.p2d(relY);
  
        for (j = data.length; j--;) {
  
          x = data[j][0];
          y = data[j][1];
  
          if (x === null || y === null) continue;
  
          // don't check if the point isn't visible in the current range
          if (x < serie.xaxis.min || x > serie.xaxis.max) continue;
  
          distanceX = Math.abs(x - mouseX);
          distanceY = Math.abs(y - mouseY);
  
          // Skip square root for speed
          distance = distanceX * distanceX + distanceY * distanceY;
  
          if (distance < compare) {
            compare = distance;
            setClosest(closest);
          }
  
          if (distanceX < compareX) {
            compareX = distanceX;
            setClosest(closestX);
          }
        }
      }
  
      return check ? {
        point : closest,
        x : closestX
      } : false;
    },
  
    drawMouseTrack : function (n) {
  
      var
        pos         = '', 
        s           = n.series,
        p           = n.mouse.position, 
        m           = n.mouse.margin,
        x           = n.x,
        y           = n.y,
        elStyle     = S_MOUSETRACK,
        mouseTrack  = this.mouseTrack,
        plotOffset  = this.plotOffset,
        left        = plotOffset.left,
        right       = plotOffset.right,
        bottom      = plotOffset.bottom,
        top         = plotOffset.top,
        decimals    = n.mouse.trackDecimals,
        options     = this.options,
        container   = options.mouse.container,
        oTop        = 0,
        oLeft       = 0,
        offset, size, content;
  
      // Create
      if (!mouseTrack) {
        mouseTrack = D.node('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');
        this.mouseTrack = mouseTrack;
        D.insert(container || this.el, mouseTrack);
      }
  
      // Fill tracker:
      if (!decimals || decimals < 0) decimals = 0;
      if (x && x.toFixed) x = x.toFixed(decimals);
      if (y && y.toFixed) y = y.toFixed(decimals);
      content = n.mouse.trackFormatter({
        x: x,
        y: y,
        series: n.series,
        index: n.index,
        nearest: n,
        fraction: n.fraction
      });
      if (_.isNull(content) || _.isUndefined(content)) {
        D.hide(mouseTrack);
        return;
      } else {
        mouseTrack.innerHTML = content;
        D.show(mouseTrack);
      }
      
      if (container) return;
  
      // Positioning
      size = D.size(mouseTrack);
      if (container) {
        offset = D.position(this.el);
        oTop = offset.top;
        oLeft = offset.left;
      }
  
      if (!n.mouse.relative) { // absolute to the canvas
        pos += 'top:'
        if      (p.charAt(0) == 'n') pos += (oTop + m + top);
        else if (p.charAt(0) == 's') pos += (oTop - m + top + this.plotHeight - size.height);
        pos += 'px;bottom:auto;left:';
        if      (p.charAt(1) == 'e') pos += (oLeft - m + left + this.plotWidth - size.width);
        else if (p.charAt(1) == 'w') pos += (oLeft + m + left);
        pos += 'px;right:auto;';
  
      // Pie
      } else if (s.pie && s.pie.show) {
        var center = {
            x: (this.plotWidth)/2,
            y: (this.plotHeight)/2
          },
          radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2,
          bisection = n.sAngle<n.eAngle ? (n.sAngle + n.eAngle) / 2: (n.sAngle + n.eAngle + 2* Math.PI) / 2;
        
        pos += 'bottom:' + (m - top - center.y - Math.sin(bisection) * radius/2 + this.canvasHeight) + 'px;top:auto;';
        pos += 'left:' + (m + left + center.x + Math.cos(bisection) * radius/2) + 'px;right:auto;';
  
      // Default
      } else {
        pos += 'top:'
        if (/n/.test(p)) pos += (oTop - m + top + n.yaxis.d2p(n.y) - size.height);
        else             pos += (oTop + m + top + n.yaxis.d2p(n.y));
        pos += 'px;bottom:auto;left:';
        if (/w/.test(p)) pos += (oLeft - m + left + n.xaxis.d2p(n.x) - size.width);
        else             pos += (oLeft + m + left + n.xaxis.d2p(n.x));
        pos += 'px;right:auto;';
      }
  
      // Set position
      mouseTrack.style.cssText = elStyle + pos;
  
      if (n.mouse.relative) {
        if (!/[ew]/.test(p)) {
          // Center Horizontally
          mouseTrack.style.left =
            (oLeft + left + n.xaxis.d2p(n.x) - D.size(mouseTrack).width / 2) + 'px';
        } else
        if (!/[ns]/.test(p)) {
          // Center Vertically
          mouseTrack.style.top =
            (oTop + top + n.yaxis.d2p(n.y) - D.size(mouseTrack).height / 2) + 'px';
        }
      }
    }
  
  });
  })();
  
  /** 
   * Selection Handles Plugin
   *
   *
   * Options
   *  show - True enables the handles plugin.
   *  drag - Left and Right drag handles
   *  scroll - Scrolling handle
   */
  (function () {
  
  function isLeftClick (e, type) {
    return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1));
  }
  
  function boundX(x, graph) {
    return Math.min(Math.max(0, x), graph.plotWidth - 1);
  }
  
  function boundY(y, graph) {
    return Math.min(Math.max(0, y), graph.plotHeight);
  }
  
  var
    D = Flotr.DOM,
    E = Flotr.EventAdapter,
    _ = Flotr._;
  
  
  Flotr.addPlugin('selection', {
  
    options: {
      pinchOnly: null,       // Only select on pinch
      mode: null,            // => one of null, 'x', 'y' or 'xy'
      color: '#B6D9FF',      // => selection box color
      fps: 20                // => frames-per-second
    },
  
    callbacks: {
      'flotr:mouseup' : function (event) {
  
        var
          options = this.options.selection,
          selection = this.selection,
          pointer = this.getEventPosition(event);
  
        if (!options || !options.mode) return;
        if (selection.interval) clearInterval(selection.interval);
  
        if (this.multitouches) {
          selection.updateSelection();
        } else
        if (!options.pinchOnly) {
          selection.setSelectionPos(selection.selection.second, pointer);
        }
        selection.clearSelection();
  
        if(selection.selecting && selection.selectionIsSane()){
          selection.drawSelection();
          selection.fireSelectEvent();
          this.ignoreClick = true;
        }
      },
      'flotr:mousedown' : function (event) {
  
        var
          options = this.options.selection,
          selection = this.selection,
          pointer = this.getEventPosition(event);
  
        if (!options || !options.mode) return;
        if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return;
        if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer);
        if (selection.interval) clearInterval(selection.interval);
  
        this.lastMousePos.pageX = null;
        selection.selecting = false;
        selection.interval = setInterval(
          _.bind(selection.updateSelection, this),
          1000 / options.fps
        );
      },
      'flotr:destroy' : function (event) {
        clearInterval(this.selection.interval);
      }
    },
  
    // TODO This isn't used.  Maybe it belongs in the draw area and fire select event methods?
    getArea: function() {
  
      var
        s = this.selection.selection,
        a = this.axes,
        first = s.first,
        second = s.second,
        x1, x2, y1, y2;
  
      x1 = a.x.p2d(s.first.x);
      x2 = a.x.p2d(s.second.x);
      y1 = a.y.p2d(s.first.y);
      y2 = a.y.p2d(s.second.y);
  
      return {
        x1 : Math.min(x1, x2),
        y1 : Math.min(y1, y2),
        x2 : Math.max(x1, x2),
        y2 : Math.max(y1, y2),
        xfirst : x1,
        xsecond : x2,
        yfirst : y1,
        ysecond : y2
      };
    },
  
    selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}},
    prevSelection: null,
    interval: null,
  
    /**
     * Fires the 'flotr:select' event when the user made a selection.
     */
    fireSelectEvent: function(name){
      var
        area = this.selection.getArea();
      name = name || 'select';
      area.selection = this.selection.selection;
      E.fire(this.el, 'flotr:'+name, [area, this]);
    },
  
    /**
     * Allows the user the manually select an area.
     * @param {Object} area - Object with coordinates to select.
     */
    setSelection: function(area, preventEvent){
      var options = this.options,
        xa = this.axes.x,
        ya = this.axes.y,
        vertScale = ya.scale,
        hozScale = xa.scale,
        selX = options.selection.mode.indexOf('x') != -1,
        selY = options.selection.mode.indexOf('y') != -1,
        s = this.selection.selection;
      
      this.selection.clearSelection();
  
      s.first.y  = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this);
      s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this);
      s.first.x  = boundX((selY && !selX) ? 0 : (area.x1 - xa.min) * hozScale, this);
      s.second.x = boundX((selY && !selX) ? this.plotWidth : (area.x2 - xa.min) * hozScale, this);
      
      this.selection.drawSelection();
      if (!preventEvent)
        this.selection.fireSelectEvent();
    },
  
    /**
     * Calculates the position of the selection.
     * @param {Object} pos - Position object.
     * @param {Event} event - Event object.
     */
    setSelectionPos: function(pos, pointer) {
      var mode = this.options.selection.mode,
          selection = this.selection.selection;
  
      if(mode.indexOf('x') == -1) {
        pos.x = (pos == selection.first) ? 0 : this.plotWidth;         
      }else{
        pos.x = boundX(pointer.relX, this);
      }
  
      if (mode.indexOf('y') == -1) {
        pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1;
      }else{
        pos.y = boundY(pointer.relY, this);
      }
    },
    /**
     * Draws the selection box.
     */
    drawSelection: function() {
  
      this.selection.fireSelectEvent('selecting');
  
      var s = this.selection.selection,
        octx = this.octx,
        options = this.options,
        plotOffset = this.plotOffset,
        prevSelection = this.selection.prevSelection;
      
      if (prevSelection &&
        s.first.x == prevSelection.first.x &&
        s.first.y == prevSelection.first.y && 
        s.second.x == prevSelection.second.x &&
        s.second.y == prevSelection.second.y) {
        return;
      }
  
      octx.save();
      octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8});
      octx.lineWidth = 1;
      octx.lineJoin = 'miter';
      octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4});
  
      this.selection.prevSelection = {
        first: { x: s.first.x, y: s.first.y },
        second: { x: s.second.x, y: s.second.y }
      };
  
      var x = Math.min(s.first.x, s.second.x),
          y = Math.min(s.first.y, s.second.y),
          w = Math.abs(s.second.x - s.first.x),
          h = Math.abs(s.second.y - s.first.y);
  
      octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
      octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
      octx.restore();
    },
  
    /**
     * Updates (draws) the selection box.
     */
    updateSelection: function(){
      if (!this.lastMousePos.pageX) return;
  
      this.selection.selecting = true;
  
      if (this.multitouches) {
        this.selection.setSelectionPos(this.selection.selection.first,  this.getEventPosition(this.multitouches[0]));
        this.selection.setSelectionPos(this.selection.selection.second,  this.getEventPosition(this.multitouches[1]));
      } else
      if (this.options.selection.pinchOnly) {
        return;
      } else {
        this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos);
      }
  
      this.selection.clearSelection();
      
      if(this.selection.selectionIsSane()) {
        this.selection.drawSelection();
      }
    },
  
    /**
     * Removes the selection box from the overlay canvas.
     */
    clearSelection: function() {
      if (!this.selection.prevSelection) return;
        
      var prevSelection = this.selection.prevSelection,
        lw = 1,
        plotOffset = this.plotOffset,
        x = Math.min(prevSelection.first.x, prevSelection.second.x),
        y = Math.min(prevSelection.first.y, prevSelection.second.y),
        w = Math.abs(prevSelection.second.x - prevSelection.first.x),
        h = Math.abs(prevSelection.second.y - prevSelection.first.y);
      
      this.octx.clearRect(x + plotOffset.left - lw + 0.5,
                          y + plotOffset.top - lw,
                          w + 2 * lw + 0.5,
                          h + 2 * lw + 0.5);
      
      this.selection.prevSelection = null;
    },
    /**
     * Determines whether or not the selection is sane and should be drawn.
     * @return {Boolean} - True when sane, false otherwise.
     */
    selectionIsSane: function(){
      var s = this.selection.selection;
      return Math.abs(s.second.x - s.first.x) >= 5 || 
             Math.abs(s.second.y - s.first.y) >= 5;
    }
  
  });
  
  })();
  
  (function () {
  
  var D = Flotr.DOM;
  
  Flotr.addPlugin('labels', {
  
    callbacks : {
      'flotr:afterdraw' : function () {
        this.labels.draw();
      }
    },
  
    draw: function(){
      // Construct fixed width label boxes, which can be styled easily.
      var
        axis, tick, left, top, xBoxWidth,
        radius, sides, coeff, angle,
        div, i, html = '',
        noLabels = 0,
        options  = this.options,
        ctx      = this.ctx,
        a        = this.axes,
        style    = { size: options.fontSize };
  
      for (i = 0; i < a.x.ticks.length; ++i){
        if (a.x.ticks[i].label) { ++noLabels; }
      }
      xBoxWidth = this.plotWidth / noLabels;
  
      if (options.grid.circular) {
        ctx.save();
        ctx.translate(this.plotOffset.left + this.plotWidth / 2,
            this.plotOffset.top + this.plotHeight / 2);
  
        radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize;
        sides  = this.axes.x.ticks.length;
        coeff  = 2 * (Math.PI / sides);
        angle  = -Math.PI / 2;
  
        drawLabelCircular(this, a.x, false);
        drawLabelCircular(this, a.x, true);
        drawLabelCircular(this, a.y, false);
        drawLabelCircular(this, a.y, true);
        ctx.restore();
      }
  
      if (!options.HtmlText && this.textEnabled) {
        drawLabelNoHtmlText(this, a.x, 'center', 'top');
        drawLabelNoHtmlText(this, a.x2, 'center', 'bottom');
        drawLabelNoHtmlText(this, a.y, 'right', 'middle');
        drawLabelNoHtmlText(this, a.y2, 'left', 'middle');
      
      } else if ((
          a.x.options.showLabels ||
          a.x2.options.showLabels ||
          a.y.options.showLabels ||
          a.y2.options.showLabels) &&
          !options.grid.circular
        ) {
  
        html = '';
  
        drawLabelHtml(this, a.x);
        drawLabelHtml(this, a.x2);
        drawLabelHtml(this, a.y);
        drawLabelHtml(this, a.y2);
  
        ctx.stroke();
        ctx.restore();
        div = D.create('div');
        D.setStyles(div, {
          fontSize: 'smaller',
          color: options.grid.color
        });
        div.className = 'flotr-labels';
        D.insert(this.el, div);
        D.insert(div, html);
      }
  
      function drawLabelCircular (graph, axis, minorTicks) {
        var
          ticks   = minorTicks ? axis.minorTicks : axis.ticks,
          isX     = axis.orientation === 1,
          isFirst = axis.n === 1,
          style, offset;
  
        style = {
          color        : axis.options.color || options.grid.color,
          angle        : Flotr.toRad(axis.options.labelsAngle),
          textBaseline : 'middle'
        };
  
        for (i = 0; i < ticks.length &&
            (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){
          tick = ticks[i];
          tick.label += '';
          if (!tick.label || !tick.label.length) { continue; }
  
          x = Math.cos(i * coeff + angle) * radius;
          y = Math.sin(i * coeff + angle) * radius;
  
          style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left';
  
          Flotr.drawText(
            ctx, tick.label,
            isX ? x : 3,
            isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize),
            style
          );
        }
      }
  
      function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline)  {
        var
          isX     = axis.orientation === 1,
          isFirst = axis.n === 1,
          style, offset;
  
        style = {
          color        : axis.options.color || options.grid.color,
          textAlign    : textAlign,
          textBaseline : textBaseline,
          angle : Flotr.toRad(axis.options.labelsAngle)
        };
        style = Flotr.getBestTextAlign(style.angle, style);
  
        for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) {
  
          tick = axis.ticks[i];
          if (!tick.label || !tick.label.length) { continue; }
  
          offset = axis.d2p(tick.v);
          if (offset < 0 ||
              offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; }
  
          Flotr.drawText(
            ctx, tick.label,
            leftOffset(graph, isX, isFirst, offset),
            topOffset(graph, isX, isFirst, offset),
            style
          );
  
          // Only draw on axis y2
          if (!isX && !isFirst) {
            ctx.save();
            ctx.strokeStyle = style.color;
            ctx.beginPath();
            ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v));
            ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v));
            ctx.stroke();
            ctx.restore();
          }
        }
  
        function continueShowingLabels (axis) {
          return axis.options.showLabels && axis.used;
        }
        function leftOffset (graph, isX, isFirst, offset) {
          return graph.plotOffset.left +
            (isX ? offset :
              (isFirst ?
                -options.grid.labelMargin :
                options.grid.labelMargin + graph.plotWidth));
        }
        function topOffset (graph, isX, isFirst, offset) {
          return graph.plotOffset.top +
            (isX ? options.grid.labelMargin : offset) +
            ((isX && isFirst) ? graph.plotHeight : 0);
        }
      }
  
      function drawLabelHtml (graph, axis) {
        var
          isX     = axis.orientation === 1,
          isFirst = axis.n === 1,
          name = '',
          left, style, top,
          offset = graph.plotOffset;
  
        if (!isX && !isFirst) {
          ctx.save();
          ctx.strokeStyle = axis.options.color || options.grid.color;
          ctx.beginPath();
        }
  
        if (axis.options.showLabels && (isFirst ? true : axis.used)) {
          for (i = 0; i < axis.ticks.length; ++i) {
            tick = axis.ticks[i];
            if (!tick.label || !tick.label.length ||
                ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) ||
                ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) {
              continue;
            }
            top = offset.top +
              (isX ?
                ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) :
                axis.d2p(tick.v) - axis.maxLabel.height / 2);
            left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0;
  
            name = '';
            if (i === 0) {
              name = ' first';
            } else if (i === axis.ticks.length - 1) {
              name = ' last';
            }
            name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y';
  
            html += [
              '<div style="position:absolute; text-align:' + (isX ? 'center' : 'right') + '; ',
              'top:' + top + 'px; ',
              ((!isX && !isFirst) ? 'right:' : 'left:') + left + 'px; ',
              'width:' + (isX ? xBoxWidth : ((isFirst ? offset.left : offset.right) - options.grid.labelMargin)) + 'px; ',
              axis.options.color ? ('color:' + axis.options.color + '; ') : ' ',
              '" class="flotr-grid-label' + name + '">' + tick.label + '</div>'
            ].join(' ');
            
            if (!isX && !isFirst) {
              ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v));
              ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v));
            }
          }
        }
      }
    }
  
  });
  })();
  
  (function () {
  
  var
    D = Flotr.DOM,
    _ = Flotr._;
  
  Flotr.addPlugin('legend', {
    options: {
      show: true,            // => setting to true will show the legend, hide otherwise
      noColumns: 1,          // => number of colums in legend table // @todo: doesn't work for HtmlText = false
      labelFormatter: function(v){return v;}, // => fn: string -> string
      labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes
      labelBoxWidth: 14,
      labelBoxHeight: 10,
      labelBoxMargin: 5,
      container: null,       // => container (as jQuery object) to put legend in, null means default on top of graph
      position: 'nw',        // => position of default legend container within plot
      margin: 5,             // => distance from grid edge to default legend container within plot
      backgroundColor: '#F0F0F0', // => Legend background color.
      backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background
    },
    callbacks: {
      'flotr:afterinit': function() {
        this.legend.insertLegend();
      },
      'flotr:destroy': function() {
        var markup = this.legend.markup;
        if (markup) {
          this.legend.markup = null;
          D.remove(markup);
        }
      }
    },
    /**
     * Adds a legend div to the canvas container or draws it on the canvas.
     */
    insertLegend: function(){
  
      if(!this.options.legend.show)
        return;
  
      var series      = this.series,
        plotOffset    = this.plotOffset,
        options       = this.options,
        legend        = options.legend,
        fragments     = [],
        rowStarted    = false, 
        ctx           = this.ctx,
        itemCount     = _.filter(series, function(s) {return (s.label && !s.hide);}).length,
        p             = legend.position, 
        m             = legend.margin,
        opacity       = legend.backgroundOpacity,
        i, label, color;
  
      if (itemCount) {
  
        var lbw = legend.labelBoxWidth,
            lbh = legend.labelBoxHeight,
            lbm = legend.labelBoxMargin,
            offsetX = plotOffset.left + m,
            offsetY = plotOffset.top + m,
            labelMaxWidth = 0,
            style = {
              size: options.fontSize*1.1,
              color: options.grid.color
            };
  
        // We calculate the labels' max width
        for(i = series.length - 1; i > -1; --i){
          if(!series[i].label || series[i].hide) continue;
          label = legend.labelFormatter(series[i].label);
          labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width);
        }
  
        var legendWidth  = Math.round(lbw + lbm*3 + labelMaxWidth),
            legendHeight = Math.round(itemCount*(lbm+lbh) + lbm);
  
        // Default Opacity
        if (!opacity && opacity !== 0) {
          opacity = 0.1;
        }
  
        if (!options.HtmlText && this.textEnabled && !legend.container) {
          
          if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);
          if(p.charAt(0) == 'c') offsetY = plotOffset.top + (this.plotHeight/2) - (m + (legendHeight/2));
          if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);
          
          // Legend box
          color = this.processColor(legend.backgroundColor, { opacity : opacity });
  
          ctx.fillStyle = color;
          ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);
          ctx.strokeStyle = legend.labelBoxBorderColor;
          ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);
          
          // Legend labels
          var x = offsetX + lbm;
          var y = offsetY + lbm;
          for(i = 0; i < series.length; i++){
            if(!series[i].label || series[i].hide) continue;
            label = legend.labelFormatter(series[i].label);
            
            ctx.fillStyle = series[i].color;
            ctx.fillRect(x, y, lbw-1, lbh-1);
            
            ctx.strokeStyle = legend.labelBoxBorderColor;
            ctx.lineWidth = 1;
            ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);
            
            // Legend text
            Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style);
            
            y += lbh + lbm;
          }
        }
        else {
          for(i = 0; i < series.length; ++i){
            if(!series[i].label || series[i].hide) continue;
            
            if(i % legend.noColumns === 0){
              fragments.push(rowStarted ? '</tr><tr>' : '<tr>');
              rowStarted = true;
            }
  
            var s = series[i],
              boxWidth = legend.labelBoxWidth,
              boxHeight = legend.labelBoxHeight;
  
            label = legend.labelFormatter(s.label);
            color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';';
            
            fragments.push(
              '<td class="flotr-legend-color-box">',
                '<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">',
                  '<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border
                    '<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', color, '"></div>', // Background
                  '</div>',
                '</div>',
              '</td>',
              '<td class="flotr-legend-label">', label, '</td>'
            );
          }
          if(rowStarted) fragments.push('</tr>');
            
          if(fragments.length > 0){
            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>';
            if(legend.container){
              table = D.node(table);
              this.legend.markup = table;
              D.insert(legend.container, table);
            }
            else {
              var styles = {position: 'absolute', 'zIndex': '2', 'border' : '1px solid ' + legend.labelBoxBorderColor};
  
                   if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; }
              else if(p.charAt(0) == 'c') { styles.top = (m + (this.plotHeight - legendHeight) / 2) + 'px'; styles.bottom = 'auto'; }
              else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; }
                   if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; }
              else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; }
  
              var div = D.create('div'), size;
              div.className = 'flotr-legend';
              D.setStyles(div, styles);
              D.insert(div, table);
              D.insert(this.el, div);
              
              if (!opacity) return;
  
              var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff';
  
              _.extend(styles, D.size(div), {
                'backgroundColor': c,
                'zIndex' : '',
                'border' : ''
              });
              styles.width += 'px';
              styles.height += 'px';
  
               // Put in the transparent background separately to avoid blended labels and
              div = D.create('div');
              div.className = 'flotr-legend-bg';
              D.setStyles(div, styles);
              D.opacity(div, opacity);
              D.insert(div, ' ');
              D.insert(this.el, div);
            }
          }
        }
      }
    }
  });
  })();
  
  /** Spreadsheet **/
  (function() {
  
  function getRowLabel(value){
    if (this.options.spreadsheet.tickFormatter){
      //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
      return this.options.spreadsheet.tickFormatter(value);
    }
    else {
      var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
      if (t) {
        return t.label;
      }
      return value;
    }
  }
  
  var
    D = Flotr.DOM,
    _ = Flotr._;
  
  Flotr.addPlugin('spreadsheet', {
    options: {
      show: false,           // => show the data grid using two tabs
      tabGraphLabel: 'Graph',
      tabDataLabel: 'Data',
      toolbarDownload: 'Download CSV', // @todo: add better language support
      toolbarSelectAll: 'Select all',
      csvFileSeparator: ',',
      decimalSeparator: '.',
      tickFormatter: null,
      initialTab: 'graph'
    },
    /**
     * Builds the tabs in the DOM
     */
    callbacks: {
      'flotr:afterconstruct': function(){
        // @TODO necessary?
        //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');
        
        if (!this.options.spreadsheet.show) return;
        
        var ss = this.spreadsheet,
          container = D.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),
          graph = D.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+'</div>'),
          data = D.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+'</div>'),
          offset;
  
        ss.tabsContainer = container;
        ss.tabs = { graph : graph, data : data };
  
        D.insert(container, graph);
        D.insert(container, data);
        D.insert(this.el, container);
  
        offset = D.size(data).height + 2;
        this.plotOffset.bottom += offset;
  
        D.setStyles(container, {top: this.canvasHeight-offset+'px'});
  
        this.
          observe(graph, 'click',  function(){ss.showTab('graph');}).
          observe(data, 'click', function(){ss.showTab('data');});
        if (this.options.spreadsheet.initialTab !== 'graph'){
          ss.showTab(this.options.spreadsheet.initialTab);
        }
      }
    },
    /**
     * Builds a matrix of the data to make the correspondance between the x values and the y values :
     * X value => Y values from the axes
     * @return {Array} The data grid
     */
    loadDataGrid: function(){
      if (this.seriesData) return this.seriesData;
  
      var s = this.series,
          rows = {};
  
      /* The data grid is a 2 dimensions array. There is a row for each X value.
       * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
      **/
      _.each(s, function(serie, i){
        _.each(serie.data, function (v) {
          var x = v[0],
              y = v[1],
              r = rows[x];
          if (r) {
            r[i+1] = y;
          } else {
            var newRow = [];
            newRow[0] = x;
            newRow[i+1] = y;
            rows[x] = newRow;
          }
        });
      });
  
      // The data grid is sorted by x value
      this.seriesData = _.sortBy(rows, function(row, x){
        return parseInt(x, 10);
      });
      return this.seriesData;
    },
    /**
     * Constructs the data table for the spreadsheet
     * @todo make a spreadsheet manager (Flotr.Spreadsheet)
     * @return {Element} The resulting table element
     */
    constructDataGrid: function(){
      // If the data grid has already been built, nothing to do here
      if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;
      
      var s = this.series,
          datagrid = this.spreadsheet.loadDataGrid(),
          colgroup = ['<colgroup><col />'],
          buttonDownload, buttonSelect, t;
      
      // First row : series' labels
      var html = ['<table class="flotr-datagrid"><tr class="first-row">'];
      html.push('<th>&nbsp;</th>');
      _.each(s, function(serie,i){
        html.push('<th scope="col">'+(serie.label || String.fromCharCode(65+i))+'</th>');
        colgroup.push('<col />');
      });
      html.push('</tr>');
      // Data rows
      _.each(datagrid, function(row){
        html.push('<tr>');
        _.times(s.length+1, function(i){
          var tag = 'td',
              value = row[i],
              // TODO: do we really want to handle problems with floating point
              // precision here?
              content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
          if (i === 0) {
            tag = 'th';
            var label = getRowLabel.call(this, content);
            if (label) content = label;
          }
  
          html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');
        }, this);
        html.push('</tr>');
      }, this);
      colgroup.push('</colgroup>');
      t = D.node(html.join(''));
  
      /**
       * @TODO disabled this
      if (!Flotr.isIE || Flotr.isIE == 9) {
        function handleMouseout(){
          t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
        }
        function handleMouseover(e){
          var td = e.element(),
            siblings = td.previousSiblings();
          t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
          t.select('colgroup col')[siblings.length].addClassName('hover');
        }
        _.each(t.select('td'), function(td) {
          Flotr.EventAdapter.
            observe(td, 'mouseover', handleMouseover).
            observe(td, 'mouseout', handleMouseout);
        });
      }
      */
  
      buttonDownload = D.node(
        '<button type="button" class="flotr-datagrid-toolbar-button">' +
        this.options.spreadsheet.toolbarDownload +
        '</button>');
  
      buttonSelect = D.node(
        '<button type="button" class="flotr-datagrid-toolbar-button">' +
        this.options.spreadsheet.toolbarSelectAll+
        '</button>');
  
      this.
        observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
        observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));
  
      var toolbar = D.node('<div class="flotr-datagrid-toolbar"></div>');
      D.insert(toolbar, buttonDownload);
      D.insert(toolbar, buttonSelect);
  
      var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
          container = D.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+
            this.canvasWidth+'px;height:'+containerHeight+'px;overflow:auto;z-index:10"></div>');
  
      D.insert(container, toolbar);
      D.insert(container, t);
      D.insert(this.el, container);
      this.spreadsheet.datagrid = t;
      this.spreadsheet.container = container;
  
      return t;
    },  
    /**
     * Shows the specified tab, by its name
     * @todo make a tab manager (Flotr.Tabs)
     * @param {String} tabName - The tab name
     */
    showTab: function(tabName){
      if (this.spreadsheet.activeTab === tabName){
        return;
      }
      switch(tabName) {
        case 'graph':
          D.hide(this.spreadsheet.container);
          D.removeClass(this.spreadsheet.tabs.data, 'selected');
          D.addClass(this.spreadsheet.tabs.graph, 'selected');
        break;
        case 'data':
          if (!this.spreadsheet.datagrid)
            this.spreadsheet.constructDataGrid();
          D.show(this.spreadsheet.container);
          D.addClass(this.spreadsheet.tabs.data, 'selected');
          D.removeClass(this.spreadsheet.tabs.graph, 'selected');
        break;
        default:
          throw 'Illegal tab name: ' + tabName;
      }
      this.spreadsheet.activeTab = tabName;
    },
    /**
     * Selects the data table in the DOM for copy/paste
     */
    selectAllData: function(){
      if (this.spreadsheet.tabs) {
        var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();
  
        this.spreadsheet.showTab('data');
        
        // deferred to be able to select the table
        setTimeout(function () {
          if ((doc = node.ownerDocument) && (win = doc.defaultView) && 
              win.getSelection && doc.createRange && 
              (selection = window.getSelection()) && 
              selection.removeAllRanges) {
              range = doc.createRange();
              range.selectNode(node);
              selection.removeAllRanges();
              selection.addRange(range);
          }
          else if (document.body && document.body.createTextRange && 
                  (range = document.body.createTextRange())) {
              range.moveToElementText(node);
              range.select();
          }
        }, 0);
        return true;
      }
      else return false;
    },
    /**
     * Converts the data into CSV in order to download a file
     */
    downloadCSV: function(){
      var csv = '',
          series = this.series,
          options = this.options,
          dg = this.spreadsheet.loadDataGrid(),
          separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);
      
      if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
        throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
      }
      
      // The first row
      _.each(series, function(serie, i){
        csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
      });
  
      csv += "%0D%0A"; // \r\n
      
      // For each row
      csv += _.reduce(dg, function(memo, row){
        var rowLabel = getRowLabel.call(this, row[0]) || '';
        rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
        var numbers = row.slice(1).join(separator);
        if (options.spreadsheet.decimalSeparator !== '.') {
          numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
        }
        return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
      }, '', this);
  
      if (Flotr.isIE && Flotr.isIE < 9) {
        csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
        window.open().document.write(csv);
      }
      else window.open('data:text/csv,'+csv);
    }
  });
  })();
  
  (function () {
  
  var D = Flotr.DOM;
  
  Flotr.addPlugin('titles', {
    callbacks: {
      'flotr:afterdraw': function() {
        this.titles.drawTitles();
      }
    },
    /**
     * Draws the title and the subtitle
     */
    drawTitles : function () {
      var html,
          options = this.options,
          margin = options.grid.labelMargin,
          ctx = this.ctx,
          a = this.axes;
      
      if (!options.HtmlText && this.textEnabled) {
        var style = {
          size: options.fontSize,
          color: options.grid.color,
          textAlign: 'center'
        };
        
        // Add subtitle
        if (options.subtitle){
          Flotr.drawText(
            ctx, options.subtitle,
            this.plotOffset.left + this.plotWidth/2, 
            this.titleHeight + this.subtitleHeight - 2,
            style
          );
        }
        
        style.weight = 1.5;
        style.size *= 1.5;
        
        // Add title
        if (options.title){
          Flotr.drawText(
            ctx, options.title,
            this.plotOffset.left + this.plotWidth/2, 
            this.titleHeight - 2,
            style
          );
        }
        
        style.weight = 1.8;
        style.size *= 0.8;
        
        // Add x axis title
        if (a.x.options.title && a.x.used){
          style.textAlign = a.x.options.titleAlign || 'center';
          style.textBaseline = 'top';
          style.angle = Flotr.toRad(a.x.options.titleAngle);
          style = Flotr.getBestTextAlign(style.angle, style);
          Flotr.drawText(
            ctx, a.x.options.title,
            this.plotOffset.left + this.plotWidth/2, 
            this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,
            style
          );
        }
        
        // Add x2 axis title
        if (a.x2.options.title && a.x2.used){
          style.textAlign = a.x2.options.titleAlign || 'center';
          style.textBaseline = 'bottom';
          style.angle = Flotr.toRad(a.x2.options.titleAngle);
          style = Flotr.getBestTextAlign(style.angle, style);
          Flotr.drawText(
            ctx, a.x2.options.title,
            this.plotOffset.left + this.plotWidth/2, 
            this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,
            style
          );
        }
        
        // Add y axis title
        if (a.y.options.title && a.y.used){
          style.textAlign = a.y.options.titleAlign || 'right';
          style.textBaseline = 'middle';
          style.angle = Flotr.toRad(a.y.options.titleAngle);
          style = Flotr.getBestTextAlign(style.angle, style);
          Flotr.drawText(
            ctx, a.y.options.title,
            this.plotOffset.left - a.y.maxLabel.width - 2 * margin, 
            this.plotOffset.top + this.plotHeight / 2,
            style
          );
        }
        
        // Add y2 axis title
        if (a.y2.options.title && a.y2.used){
          style.textAlign = a.y2.options.titleAlign || 'left';
          style.textBaseline = 'middle';
          style.angle = Flotr.toRad(a.y2.options.titleAngle);
          style = Flotr.getBestTextAlign(style.angle, style);
          Flotr.drawText(
            ctx, a.y2.options.title,
            this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, 
            this.plotOffset.top + this.plotHeight / 2,
            style
          );
        }
      } 
      else {
        html = [];
        
        // Add title
        if (options.title)
          html.push(
            '<div style="position:absolute;top:0;left:', 
            this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:',
            this.plotWidth,'px;" class="flotr-title">', options.title, '</div>'
          );
        
        // Add subtitle
        if (options.subtitle)
          html.push(
            '<div style="position:absolute;top:', this.titleHeight, 'px;left:', 
            this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:',
            this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>'
          );
  
        html.push('</div>');
        
        html.push('<div class="flotr-axis-title" style="font-weight:bold;">');
        
        // Add x axis title
        if (a.x.options.title && a.x.used)
          html.push(
            '<div style="position:absolute;top:', 
            (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), 
            'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, 
            'px;text-align:', a.x.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-x1">', a.x.options.title, '</div>'
          );
        
        // Add x2 axis title
        if (a.x2.options.title && a.x2.used)
          html.push(
            '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', 
            this.plotWidth, 'px;text-align:', a.x2.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-x2">', a.x2.options.title, '</div>'
          );
        
        // Add y axis title
        if (a.y.options.title && a.y.used)
          html.push(
            '<div style="position:absolute;top:', 
            (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
            'px;left:0;text-align:', a.y.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-y1">', a.y.options.title, '</div>'
          );
        
        // Add y2 axis title
        if (a.y2.options.title && a.y2.used)
          html.push(
            '<div style="position:absolute;top:', 
            (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
            'px;right:0;text-align:', a.y2.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-y2">', a.y2.options.title, '</div>'
          );
        
        html = html.join('');
  
        var div = D.create('div');
        D.setStyles({
          color: options.grid.color 
        });
        div.className = 'flotr-titles';
        D.insert(this.el, div);
        D.insert(div, html);
      }
    }
  });
  })();
  