All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.lifebuoy.envision.js Maven / Gradle / Ivy

//     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;
  };

})();
/*!
  * 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
});
/**
 * Flotr2 (c) 2012 Carl Sutherland
 * MIT License
 * Special thanks to:
 * Flotr: http://code.google.com/p/flotr/ (fork)
 * Flot: https://github.com/flot/flot (original fork)
 */
(function () {

var
  global = this,
  previousFlotr = this.Flotr,
  Flotr;

Flotr = {
  _: _,
  bean: bean,
  isIphone: /iphone/i.test(navigator.userAgent),
  isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false),

  /**
   * An object of the registered graph types. Use Flotr.addType(type, object)
   * to add your own type.
   */
  graphTypes: {},

  /**
   * The list of the registered plugins
   */
  plugins: {},

  /**
   * Can be used to add your own chart type.
   * @param {String} name - Type of chart, like 'pies', 'bars' etc.
   * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
   */
  addType: function(name, graphType){
    Flotr.graphTypes[name] = graphType;
    Flotr.defaultOptions[name] = graphType.options || {};
    Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
  },

  /**
   * Can be used to add a plugin
   * @param {String} name - The name of the plugin
   * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
   */
  addPlugin: function(name, plugin){
    Flotr.plugins[name] = plugin;
    Flotr.defaultOptions[name] = plugin.options || {};
  },

  /**
   * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
   * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
   * @param {Element} el - element to insert the graph into
   * @param {Object} data - an array or object of dataseries
   * @param {Object} options - an object containing options
   * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
   * @return {Object} returns a new graph object and of course draws the graph.
   */
  draw: function(el, data, options, GraphKlass){
    GraphKlass = GraphKlass || Flotr.Graph;
    return new GraphKlass(el, data, options);
  },

  /**
   * Recursively merges two objects.
   * @param {Object} src - source object (likely the object with the least properties)
   * @param {Object} dest - destination object (optional, object with the most properties)
   * @return {Object} recursively merged Object
   * @TODO See if we can't remove this.
   */
  merge: function(src, dest){
    var i, v, result = dest || {};

    for (i in src) {
      v = src[i];
      if (v && typeof(v) === 'object') {
        if (v.constructor === Array) {
          result[i] = this._.clone(v);
        } else if (v.constructor !== RegExp && !this._.isElement(v)) {
          result[i] = Flotr.merge(v, (dest ? dest[i] : undefined));
        } else {
          result[i] = v;
        }
      } else {
        result[i] = v;
      }
    }

    return result;
  },

  /**
   * Recursively clones an object.
   * @param {Object} object - The object to clone
   * @return {Object} the clone
   * @TODO See if we can't remove this.
   */
  clone: function(object){
    return Flotr.merge(object, {});
  },

  /**
   * Function calculates the ticksize and returns it.
   * @param {Integer} noTicks - number of ticks
   * @param {Integer} min - lower bound integer value for the current axis
   * @param {Integer} max - upper bound integer value for the current axis
   * @param {Integer} decimals - number of decimals for the ticks
   * @return {Integer} returns the ticksize in pixels
   */
  getTickSize: function(noTicks, min, max, decimals){
    var delta = (max - min) / noTicks,
        magn = Flotr.getMagnitude(delta),
        tickSize = 10,
        norm = delta / magn; // Norm is between 1.0 and 10.0.

    if(norm < 1.5) tickSize = 1;
    else if(norm < 2.25) tickSize = 2;
    else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5);
    else if(norm < 7.5) tickSize = 5;

    return tickSize * magn;
  },

  /**
   * Default tick formatter.
   * @param {String, Integer} val - tick value integer
   * @param {Object} axisOpts - the axis' options
   * @return {String} formatted tick string
   */
  defaultTickFormatter: function(val, axisOpts){
    return val+'';
  },

  /**
   * Formats the mouse tracker values.
   * @param {Object} obj - Track value Object {x:..,y:..}
   * @return {String} Formatted track string
   */
  defaultTrackFormatter: function(obj){
    return '('+obj.x+', '+obj.y+')';
  },

  /**
   * Utility function to convert file size values in bytes to kB, MB, ...
   * @param value {Number} - The value to convert
   * @param precision {Number} - The number of digits after the comma (default: 2)
   * @param base {Number} - The base (default: 1000)
   */
  engineeringNotation: function(value, precision, base){
    var sizes =         ['Y','Z','E','P','T','G','M','k',''],
        fractionSizes = ['y','z','a','f','p','n','µ','m',''],
        total = sizes.length;

    base = base || 1000;
    precision = Math.pow(10, precision || 2);

    if (value === 0) return 0;

    if (value > 1) {
      while (total-- && (value >= base)) value /= base;
    }
    else {
      sizes = fractionSizes;
      total = sizes.length;
      while (total-- && (value < 1)) value *= base;
    }

    return (Math.round(value * precision) / precision) + sizes[total];
  },

  /**
   * Returns the magnitude of the input value.
   * @param {Integer, Float} x - integer or float value
   * @return {Integer, Float} returns the magnitude of the input value
   */
  getMagnitude: function(x){
    return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
  },
  toPixel: function(val){
    return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
  },
  toRad: function(angle){
    return -angle * (Math.PI/180);
  },
  floorInBase: function(n, base) {
    return base * Math.floor(n / base);
  },
  drawText: function(ctx, text, x, y, style) {
    if (!ctx.fillText) {
      ctx.drawText(text, x, y, style);
      return;
    }

    style = this._.extend({
      size: Flotr.defaultOptions.fontSize,
      color: '#000000',
      textAlign: 'left',
      textBaseline: 'bottom',
      weight: 1,
      angle: 0
    }, style);

    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(style.angle);
    ctx.fillStyle = style.color;
    ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
    ctx.textAlign = style.textAlign;
    ctx.textBaseline = style.textBaseline;
    ctx.fillText(text, 0, 0);
    ctx.restore();
  },
  getBestTextAlign: function(angle, style) {
    style = style || {textAlign: 'center', textBaseline: 'middle'};
    angle += Flotr.getTextAngleFromAlign(style);

    if (Math.abs(Math.cos(angle)) > 10e-3)
      style.textAlign    = (Math.cos(angle) > 0 ? 'right' : 'left');

    if (Math.abs(Math.sin(angle)) > 10e-3)
      style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');

    return style;
  },
  alignTable: {
    'right middle' : 0,
    'right top'    : Math.PI/4,
    'center top'   : Math.PI/2,
    'left top'     : 3*(Math.PI/4),
    'left middle'  : Math.PI,
    'left bottom'  : -3*(Math.PI/4),
    'center bottom': -Math.PI/2,
    'right bottom' : -Math.PI/4,
    'center middle': 0
  },
  getTextAngleFromAlign: function(style) {
    return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
  },
  noConflict : function () {
    global.Flotr = previousFlotr;
    return this;
  }
};

global.Flotr = Flotr;

})();

/**
 * Flotr Defaults
 */
Flotr.defaultOptions = {
  colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
  ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping
  title: null,             // => The graph's title
  subtitle: null,          // => The graph's subtitle
  shadowSize: 4,           // => size of the 'fake' shadow
  defaultType: null,       // => default series type
  HtmlText: true,          // => wether to draw the text using HTML or on the canvas
  fontColor: '#545454',    // => default font color
  fontSize: 7.5,           // => canvas' text font size
  resolution: 1,           // => resolution of the graph, to have printer-friendly graphs !
  parseFloat: true,        // => whether to preprocess data for floats (ie. if input is string)
  preventDefault: true,    // => preventDefault by default for mobile events.  Turn off to enable scroll.
  xaxis: {
    ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
    minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
    showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
    showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
    labelsAngle: 0,        // => labels' angle, in degrees
    title: null,           // => axis title
    titleAngle: 0,         // => axis title's angle, in degrees
    noTicks: 5,            // => number of ticks for automagically generated ticks
    minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
    tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
    tickDecimals: null,    // => no. of decimals, null means auto
    min: null,             // => min. value to show, null means set automatically
    max: null,             // => max. value to show, null means set automatically
    autoscale: false,      // => Turns autoscaling on with true
    autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
    color: null,           // => color of the ticks
    mode: 'normal',        // => can be 'time' or 'normal'
    timeFormat: null,
    timeMode:'UTC',        // => For UTC time ('local' for local time).
    timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year)
    scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
    base: Math.E,
    titleAlign: 'center',
    margin: true           // => Turn off margins with false
  },
  x2axis: {},
  yaxis: {
    ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
    minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
    showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
    showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
    labelsAngle: 0,        // => labels' angle, in degrees
    title: null,           // => axis title
    titleAngle: 90,        // => axis title's angle, in degrees
    noTicks: 5,            // => number of ticks for automagically generated ticks
    minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
    tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
    tickDecimals: null,    // => no. of decimals, null means auto
    min: null,             // => min. value to show, null means set automatically
    max: null,             // => max. value to show, null means set automatically
    autoscale: false,      // => Turns autoscaling on with true
    autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
    color: null,           // => The color of the ticks
    scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
    base: Math.E,
    titleAlign: 'center',
    margin: true           // => Turn off margins with false
  },
  y2axis: {
    titleAngle: 270
  },
  grid: {
    color: '#545454',      // => primary color used for outline and labels
    backgroundColor: null, // => null for transparent, else color
    backgroundImage: null, // => background image. String or object with src, left and top
    watermarkAlpha: 0.4,   // =>
    tickColor: '#DDDDDD',  // => color used for the ticks
    labelMargin: 3,        // => margin in pixels
    verticalLines: true,   // => whether to show gridlines in vertical direction
    minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir.
    horizontalLines: true, // => whether to show gridlines in horizontal direction
    minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir.
    outlineWidth: 1,       // => width of the grid outline/border in pixels
    outline : 'nsew',      // => walls of the outline to display
    circular: false        // => if set to true, the grid will be circular, must be used when radars are drawn
  },
  mouse: {
    track: false,          // => true to track the mouse, no tracking otherwise
    trackAll: false,
    position: 'se',        // => position of the value box (default south-east)
    relative: false,       // => next to the mouse cursor
    trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
    margin: 5,             // => margin in pixels of the valuebox
    lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series
    trackDecimals: 1,      // => decimals for the track values
    sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value
    trackY: true,          // => whether or not to track the mouse in the y axis
    radius: 3,             // => radius of the track point
    fillColor: null,       // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
    fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
  }
};

/**
 * Flotr Color
 */

(function () {

var
  _ = Flotr._;

// Constructor
function Color (r, g, b, a) {
  this.rgba = ['r','g','b','a'];
  var x = 4;
  while(-1<--x){
    this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
  }
  this.normalize();
}

// Constants
var COLOR_NAMES = {
  aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],
  brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],
  darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],
  darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],
  darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],
  khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],
  lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],
  maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],
  violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]
};

Color.prototype = {
  scale: function(rf, gf, bf, af){
    var x = 4;
    while (-1 < --x) {
      if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x];
    }
    return this.normalize();
  },
  alpha: function(alpha) {
    if (!_.isUndefined(alpha) && !_.isNull(alpha)) {
      this.a = alpha;
    }
    return this.normalize();
  },
  clone: function(){
    return new Color(this.r, this.b, this.g, this.a);
  },
  limit: function(val,minVal,maxVal){
    return Math.max(Math.min(val, maxVal), minVal);
  },
  normalize: function(){
    var limit = this.limit;
    this.r = limit(parseInt(this.r, 10), 0, 255);
    this.g = limit(parseInt(this.g, 10), 0, 255);
    this.b = limit(parseInt(this.b, 10), 0, 255);
    this.a = limit(this.a, 0, 1);
    return this;
  },
  distance: function(color){
    if (!color) return;
    color = new Color.parse(color);
    var dist = 0, x = 3;
    while(-1<--x){
      dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
    }
    return dist;
  },
  toString: function(){
    return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
  },
  contrast: function () {
    var
      test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
    return (test < 0.5 ? '#000000' : '#ffffff');
  }
};

_.extend(Color, {
  /**
   * Parses a color string and returns a corresponding Color.
   * The different tests are in order of probability to improve speed.
   * @param {String, Color} str - string thats representing a color
   * @return {Color} returns a Color object or false
   */
  parse: function(color){
    if (color instanceof Color) return color;

    var result;

    // #a0b1c2
    if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
      return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));

    // rgb(num,num,num)
    if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
      return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));

    // #fff
    if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
      return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));

    // rgba(num,num,num,num)
    if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
      return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));

    // rgb(num%,num%,num%)
    if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
      return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);

    // rgba(num%,num%,num%,num)
    if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
      return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));

    // Otherwise, we're most likely dealing with a named color.
    var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase();
    if(name == 'transparent'){
      return new Color(255, 255, 255, 0);
    }
    return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
  },

  /**
   * Process color and options into color style.
   */
  processColor: function(color, options) {

    var opacity = options.opacity;
    if (!color) return 'rgba(0, 0, 0, 0)';
    if (color instanceof Color) return color.alpha(opacity).toString();
    if (_.isString(color)) return Color.parse(color).alpha(opacity).toString();

    var grad = color.colors ? color : {colors: color};

    if (!options.ctx) {
      if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
      return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString();
    }
    grad = _.extend({start: 'top', end: 'bottom'}, grad);

    if (/top/i.test(grad.start))  options.x1 = 0;
    if (/left/i.test(grad.start)) options.y1 = 0;
    if (/bottom/i.test(grad.end)) options.x2 = 0;
    if (/right/i.test(grad.end))  options.y2 = 0;

    var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
    for (i = 0; i < grad.colors.length; i++) {
      c = grad.colors[i];
      if (_.isArray(c)) {
        stop = c[0];
        c = c[1];
      }
      else stop = i / (grad.colors.length-1);
      gradient.addColorStop(stop, Color.parse(c).alpha(opacity));
    }
    return gradient;
  }
});

Flotr.Color = Color;

})();

/**
 * Flotr Date
 */
Flotr.Date = {

  set : function (date, name, mode, value) {
    mode = mode || 'UTC';
    name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name;
    date[name](value);
  },

  get : function (date, name, mode) {
    mode = mode || 'UTC';
    name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name;
    return date[name]();
  },

  format: function(d, format, mode) {
    if (!d) return;

    // We should maybe use an "official" date format spec, like PHP date() or ColdFusion
    // http://fr.php.net/manual/en/function.date.php
    // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
    var
      get = this.get,
      tokens = {
        h: get(d, 'Hours', mode).toString(),
        H: leftPad(get(d, 'Hours', mode)),
        M: leftPad(get(d, 'Minutes', mode)),
        S: leftPad(get(d, 'Seconds', mode)),
        s: get(d, 'Milliseconds', mode),
        d: get(d, 'Date', mode).toString(),
        m: (get(d, 'Month', mode) + 1).toString(),
        y: get(d, 'FullYear', mode).toString(),
        b: Flotr.Date.monthNames[get(d, 'Month', mode)]
      };

    function leftPad(n){
      n += '';
      return n.length == 1 ? "0" + n : n;
    }

    var r = [], c,
        escape = false;

    for (var i = 0; i < format.length; ++i) {
      c = format.charAt(i);

      if (escape) {
        r.push(tokens[c] || c);
        escape = false;
      }
      else if (c == "%")
        escape = true;
      else
        r.push(c);
    }
    return r.join('');
  },
  getFormat: function(time, span) {
    var tu = Flotr.Date.timeUnits;
         if (time < tu.second) return "%h:%M:%S.%s";
    else if (time < tu.minute) return "%h:%M:%S";
    else if (time < tu.day)    return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M";
    else if (time < tu.month)  return "%b %d";
    else if (time < tu.year)   return (span < tu.year) ? "%b" : "%b %y";
    else                       return "%y";
  },
  formatter: function (v, axis) {
    var
      options = axis.options,
      scale = Flotr.Date.timeUnits[options.timeUnit],
      d = new Date(v * scale);

    // first check global format
    if (axis.options.timeFormat)
      return Flotr.Date.format(d, options.timeFormat, options.timeMode);

    var span = (axis.max - axis.min) * scale,
        t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit];

    return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode);
  },
  generator: function(axis) {

     var
      set       = this.set,
      get       = this.get,
      timeUnits = this.timeUnits,
      spec      = this.spec,
      options   = axis.options,
      mode      = options.timeMode,
      scale     = timeUnits[options.timeUnit],
      min       = axis.min * scale,
      max       = axis.max * scale,
      delta     = (max - min) / options.noTicks,
      ticks     = [],
      tickSize  = axis.tickSize,
      tickUnit,
      formatter, i;

    // Use custom formatter or time tick formatter
    formatter = (options.tickFormatter === Flotr.defaultTickFormatter ?
      this.formatter : options.tickFormatter
    );

    for (i = 0; i < spec.length - 1; ++i) {
      var d = spec[i][0] * timeUnits[spec[i][1]];
      if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize)
        break;
    }
    tickSize = spec[i][0];
    tickUnit = spec[i][1];

    // special-case the possibility of several years
    if (tickUnit == "year") {
      tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0);

      // Fix for 0.5 year case
      if (tickSize == 0.5) {
        tickUnit = "month";
        tickSize = 6;
      }
    }

    axis.tickUnit = tickUnit;
    axis.tickSize = tickSize;

    var step = tickSize * timeUnits[tickUnit];
    d = new Date(min);

    function setTick (name) {
      set(d, name, mode, Flotr.floorInBase(
        get(d, name, mode), tickSize
      ));
    }

    switch (tickUnit) {
      case "millisecond": setTick('Milliseconds'); break;
      case "second": setTick('Seconds'); break;
      case "minute": setTick('Minutes'); break;
      case "hour": setTick('Hours'); break;
      case "month": setTick('Month'); break;
      case "year": setTick('FullYear'); break;
    }

    // reset smaller components
    if (step >= timeUnits.second)  set(d, 'Milliseconds', mode, 0);
    if (step >= timeUnits.minute)  set(d, 'Seconds', mode, 0);
    if (step >= timeUnits.hour)    set(d, 'Minutes', mode, 0);
    if (step >= timeUnits.day)     set(d, 'Hours', mode, 0);
    if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1);
    if (step >= timeUnits.year)    set(d, 'Month', mode, 0);

    var carry = 0, v = NaN, prev;
    do {
      prev = v;
      v = d.getTime();
      ticks.push({ v: v / scale, label: formatter(v / scale, axis) });
      if (tickUnit == "month") {
        if (tickSize < 1) {
          /* a bit complicated - we'll divide the month up but we need to take care of fractions
           so we don't end up in the middle of a day */
          set(d, 'Date', mode, 1);
          var start = d.getTime();
          set(d, 'Month', mode, get(d, 'Month', mode) + 1);
          var end = d.getTime();
          d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize);
          carry = get(d, 'Hours', mode);
          set(d, 'Hours', mode, 0);
        }
        else
          set(d, 'Month', mode, get(d, 'Month', mode) + tickSize);
      }
      else if (tickUnit == "year") {
        set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize);
      }
      else
        d.setTime(v + step);

    } while (v < max && v != prev);

    return ticks;
  },
  timeUnits: {
    millisecond: 1,
    second: 1000,
    minute: 1000 * 60,
    hour:   1000 * 60 * 60,
    day:    1000 * 60 * 60 * 24,
    month:  1000 * 60 * 60 * 24 * 30,
    year:   1000 * 60 * 60 * 24 * 365.2425
  },
  // the allowed tick sizes, after 1 year we use an integer algorithm
  spec: [
    [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"],
    [1, "second"],   [2, "second"],  [5, "second"], [10, "second"], [30, "second"],
    [1, "minute"],   [2, "minute"],  [5, "minute"], [10, "minute"], [30, "minute"],
    [1, "hour"],     [2, "hour"],    [4, "hour"],   [8, "hour"],    [12, "hour"],
    [1, "day"],      [2, "day"],     [3, "day"],
    [0.25, "month"], [0.5, "month"], [1, "month"],  [2, "month"],   [3, "month"], [6, "month"],
    [1, "year"]
  ],
  monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
};

(function () {

var _ = Flotr._;

Flotr.DOM = {
  addClass: function(element, name){
    var classList = (element.className ? element.className : '');
      if (_.include(classList.split(/\s+/g), name)) return;
    element.className = (classList ? classList + ' ' : '') + name;
  },
  /**
   * Create an element.
   */
  create: function(tag){
    return document.createElement(tag);
  },
  node: function(html) {
    var div = Flotr.DOM.create('div'), n;
    div.innerHTML = html;
    n = div.children[0];
    div.innerHTML = '';
    return n;
  },
  /**
   * Remove all children.
   */
  empty: function(element){
    element.innerHTML = '';
    /*
    if (!element) return;
    _.each(element.childNodes, function (e) {
      Flotr.DOM.empty(e);
      element.removeChild(e);
    });
    */
  },
  hide: function(element){
    Flotr.DOM.setStyles(element, {display:'none'});
  },
  /**
   * Insert a child.
   * @param {Element} element
   * @param {Element|String} Element or string to be appended.
   */
  insert: function(element, child){
    if(_.isString(child))
      element.innerHTML += child;
    else if (_.isElement(child))
      element.appendChild(child);
  },
  // @TODO find xbrowser implementation
  opacity: function(element, opacity) {
    element.style.opacity = opacity;
  },
  position: function(element, p){
    if (!element.offsetParent)
      return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)};

    p = this.position(element.offsetParent);
    p.left  += element.offsetLeft;
    p.top   += element.offsetTop;
    return p;
  },
  removeClass: function(element, name) {
    var classList = (element.className ? element.className : '');
    element.className = _.filter(classList.split(/\s+/g), function (c) {
      if (c != name) return true; }
    ).join(' ');
  },
  setStyles: function(element, o) {
    _.each(o, function (value, key) {
      element.style[key] = value;
    });
  },
  show: function(element){
    Flotr.DOM.setStyles(element, {display:''});
  },
  /**
   * Return element size.
   */
  size: function(element){
    return {
      height : element.offsetHeight,
      width : element.offsetWidth };
  }
};

})();

/**
 * Flotr Event Adapter
 */
(function () {
var
  F = Flotr,
  bean = F.bean;
F.EventAdapter = {
  observe: function(object, name, callback) {
    bean.add(object, name, callback);
    return this;
  },
  fire: function(object, name, args) {
    bean.fire(object, name, args);
    if (typeof(Prototype) != 'undefined')
      Event.fire(object, name, args);
    // @TODO Someone who uses mootools, add mootools adapter for existing applciations.
    return this;
  },
  stopObserving: function(object, name, callback) {
    bean.remove(object, name, callback);
    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
      };
    }
  }
};
})();

/**
 * 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 {
      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;
    }
    if (y.options.margin === false) {
      p.left  = 0;
      p.right = 0;
    } else {
      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;
    }

    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) {
      // 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.fire(el, 'flotr:mouseout');
        });
    }
  },

  /**
   * 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);
        }
        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;

})();

/**
 * 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, '
' + text + '
'); 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; })(); /** Lines **/ Flotr.addType('lines', { options: { show: false, // => setting to true will show lines, false will hide lineWidth: 2, // => line width in pixels fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillBorder: false, // => draw a border around the fill fillColor: null, // => fill color fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill steps: false, // => draw steps stacked: false // => setting to true will show stacked lines, false will show normal lines }, stack : { values : [] }, /** * Draws lines series in the canvas element. * @param {Object} options */ draw : function (options) { var context = options.context, lineWidth = options.lineWidth, shadowSize = options.shadowSize, offset; context.save(); context.lineJoin = 'round'; if (shadowSize) { context.lineWidth = shadowSize / 2; offset = lineWidth / 2 + context.lineWidth / 2; // @TODO do this instead with a linear gradient context.strokeStyle = "rgba(0,0,0,0.1)"; this.plot(options, offset + shadowSize / 2, false); context.strokeStyle = "rgba(0,0,0,0.2)"; this.plot(options, offset, false); } context.lineWidth = lineWidth; context.strokeStyle = options.color; this.plot(options, 0, true); context.restore(); }, plot : function (options, shadowOffset, incStack) { var context = options.context, width = options.width, height = options.height, xScale = options.xScale, yScale = options.yScale, data = options.data, stack = options.stacked ? this.stack : false, length = data.length - 1, prevx = null, prevy = null, zero = yScale(0), start = null, x1, x2, y1, y2, stack1, stack2, i; if (length < 1) return; context.beginPath(); for (i = 0; i < length; ++i) { // To allow empty values if (data[i][1] === null || data[i+1][1] === null) { if (options.fill) { if (i > 0 && data[i][1]) { context.stroke(); fill(); start = null; context.closePath(); context.beginPath(); } } continue; } // Zero is infinity for log scales // TODO handle zero for logarithmic // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; x1 = xScale(data[i][0]); x2 = xScale(data[i+1][0]); if (start === null) start = data[i]; if (stack) { stack1 = stack.values[data[i][0]] || 0; stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; y1 = yScale(data[i][1] + stack1); y2 = yScale(data[i+1][1] + stack2); if(incStack){ stack.values[data[i][0]] = data[i][1]+stack1; if(i == length-1) stack.values[data[i+1][0]] = data[i+1][1]+stack2; } } else{ y1 = yScale(data[i][1]); y2 = yScale(data[i+1][1]); } if ( (y1 > height && y2 > height) || (y1 < 0 && y2 < 0) || (x1 < 0 && x2 < 0) || (x1 > width && x2 > width) ) continue; if((prevx != x1) || (prevy != y1 + shadowOffset)) context.moveTo(x1, y1 + shadowOffset); prevx = x2; prevy = y2 + shadowOffset; if (options.steps) { context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); context.lineTo(prevx + shadowOffset / 2, prevy); } else { context.lineTo(prevx, prevy); } } if (!options.fill || options.fill && !options.fillBorder) context.stroke(); fill(); function fill () { // TODO stacked lines if(!shadowOffset && options.fill && start){ x1 = xScale(start[0]); context.fillStyle = options.fillStyle; context.lineTo(x2, zero); context.lineTo(x1, zero); context.lineTo(x1, yScale(start[1])); context.fill(); if (options.fillBorder) { context.stroke(); } } } context.closePath(); }, // Perform any pre-render precalculations (this should be run on data first) // - Pie chart total for calculating measures // - Stacks for lines and bars // precalculate : function () { // } // // // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) // getBounds : function () { // } // getMin : function () { // } // getMax : function () { // } // // // Padding around rendered elements // getPadding : function () { // } extendYRange : function (axis, data, options, lines) { var o = axis.options; // If stacked and auto-min if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { var newmax = axis.max, newmin = axis.min, positiveSums = lines.positiveSums || {}, negativeSums = lines.negativeSums || {}, x, j; for (j = 0; j < data.length; j++) { x = data[j][0] + ''; // Positive if (data[j][1] > 0) { positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; newmax = Math.max(newmax, positiveSums[x]); } // Negative else { negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; newmin = Math.min(newmin, negativeSums[x]); } } lines.negativeSums = negativeSums; lines.positiveSums = positiveSums; axis.max = newmax; axis.min = newmin; } if (options.steps) { this.hit = function (options) { var data = options.data, args = options.args, yScale = options.yScale, mouse = args[0], length = data.length, n = args[1], x = options.xInverse(mouse.relX), relY = mouse.relY, i; for (i = 0; i < length - 1; i++) { if (x >= data[i][0] && x <= data[i+1][0]) { if (Math.abs(yScale(data[i][1]) - relY) < 8) { n.x = data[i][0]; n.y = data[i][1]; n.index = i; n.seriesIndex = options.index; } break; } } }; this.drawHit = function (options) { var context = options.context, args = options.args, data = options.data, xScale = options.xScale, index = args.index, x = xScale(args.x), y = options.yScale(args.y), x2; if (data.length - 1 > index) { x2 = options.xScale(data[index + 1][0]); context.save(); context.strokeStyle = options.color; context.lineWidth = options.lineWidth; context.beginPath(); context.moveTo(x, y); context.lineTo(x2, y); context.stroke(); context.closePath(); context.restore(); } }; this.clearHit = function (options) { var context = options.context, args = options.args, data = options.data, xScale = options.xScale, width = options.lineWidth, index = args.index, x = xScale(args.x), y = options.yScale(args.y), x2; if (data.length - 1 > index) { x2 = options.xScale(data[index + 1][0]); context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); } }; } } }); /** 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; } }); /** 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(); } } }); /** * 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._; 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(); } }, /** * 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 ? '' : ''); 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( '', '
', '
', // Border '
', // Background '
', '
', '', '', label, '' ); } if(rowStarted) fragments.push(''); if(fragments.length > 0){ var table = '' + fragments.join('') + '
'; if(legend.container){ D.empty(legend.container); 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); } } } } } }); })(); (function () { var D = Flotr.DOM, _ = Flotr._, flotr = Flotr, S_MOUSETRACK = '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;'; Flotr.addPlugin('hit', { callbacks: { 'flotr:mousemove': function(e, pos) { this.hit.track(pos); }, 'flotr:click': function(pos) { var hit = this.hit.track(pos); _.defaults(pos, hit); }, 'flotr:mouseout': function() { this.hit.clearHit(); }, 'flotr:destroy': function() { 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 }; 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; // Create if (!mouseTrack) { mouseTrack = D.node('
'); this.mouseTrack = mouseTrack; D.insert(this.el, mouseTrack); } if (!n.mouse.relative) { // absolute to the canvas if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;'; else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;'; if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;'; else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + '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 (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 += [ '
' + tick.label + '
' ].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(); } }, /** * 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 ? '' : ''); 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( '', '
', '
', // Border '
', // Background '
', '
', '', '', label, '' ); } if(rowStarted) fragments.push(''); if(fragments.length > 0){ var table = '' + fragments.join('') + '
'; if(legend.container){ D.empty(legend.container); 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); } } } } } }); })(); (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( '
', options.title, '
' ); // Add subtitle if (options.subtitle) html.push( '
', options.subtitle, '
' ); html.push('
'); html.push('
'); // Add x axis title if (a.x.options.title && a.x.used) html.push( '
', a.x.options.title, '
' ); // Add x2 axis title if (a.x2.options.title && a.x2.used) html.push( '
', a.x2.options.title, '
' ); // Add y axis title if (a.y.options.title && a.y.used) html.push( '
', a.y.options.title, '
' ); // Add y2 axis title if (a.y2.options.title && a.y2.used) html.push( '
', a.y2.options.title, '
' ); 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); } } }); })(); /** * Selection Handles Plugin * * Depends upon options.selection.mode * * Options * show - True enables the handles plugin. * drag - Left and Right drag handles * scroll - Scrolling handle */ (function () { var D = Flotr.DOM; Flotr.addPlugin('handles', { options: { show: false, drag: true, scroll: true }, callbacks: { 'flotr:afterinit': init, 'flotr:select': handleSelect, 'flotr:mousedown': reset, 'flotr:mousemove': mouseMoveHandler } }); function init() { var options = this.options, handles = this.handles, el = this.el, scroll, left, right, container; if (!options.selection.mode || !options.handles.show || 'ontouchstart' in el) return; handles.initialized = true; container = D.node('
'); options = options.handles; // Drag handles if (options.drag) { right = D.node('
'); left = D.node('
'); D.insert(container, right); D.insert(container, left); D.hide(left); D.hide(right); handles.left = left; handles.right = right; this.observe(left, 'mousedown', function () { handles.moveHandler = leftMoveHandler; }); this.observe(right, 'mousedown', function () { handles.moveHandler = rightMoveHandler; }); } // Scroll handle if (options.scroll) { scroll = D.node('
'); D.insert(container, scroll); D.hide(scroll); handles.scroll = scroll; this.observe(scroll, 'mousedown', function () { handles.moveHandler = scrollMoveHandler; }); } this.observe(document, 'mouseup', function() { handles.moveHandler = null; }); D.insert(el, container); } function handleSelect(selection) { if (!this.handles.initialized) return; var handles = this.handles, options = this.options.handles, left = handles.left, right = handles.right, scroll = handles.scroll; if (options) { if (options.drag) { positionDrag(this, left, selection.x1); positionDrag(this, right, selection.x2); } if (options.scroll) { positionScroll( this, scroll, selection.x1, selection.x2 ); } } } function positionDrag(graph, handle, x) { D.show(handle); var size = D.size(handle), l = Math.round(graph.axes.x.d2p(x) - size.width / 2), t = (graph.plotHeight - size.height) / 2; D.setStyles(handle, { 'left' : l+'px', 'top' : t+'px' }); } function positionScroll(graph, handle, x1, x2) { D.show(handle); var size = D.size(handle), l = Math.round(graph.axes.x.d2p(x1)), t = (graph.plotHeight) - size.height / 2, w = (graph.axes.x.d2p(x2) - graph.axes.x.d2p(x1)); D.setStyles(handle, { 'left' : l+'px', 'top' : t+'px', 'width': w+'px' }); } function reset() { if (!this.handles.initialized) return; var handles = this.handles; if (handles) { D.hide(handles.left); D.hide(handles.right); D.hide(handles.scroll); } } function mouseMoveHandler(e, position) { if (!this.handles.initialized) return; if (!this.handles.moveHandler) return; var delta = position.x - this.lastMousePos.x, selection = this.selection.selection, area = this.selection.getArea(), handles = this.handles; handles.moveHandler(area, delta); checkSwap(area, handles); this.selection.setSelection(area); } function checkSwap (area, handles) { var moveHandler = handles.moveHandler; if (area.x1 > area.x2) { if (moveHandler == leftMoveHandler) { moveHandler = rightMoveHandler; } else if (moveHandler == rightMoveHandler) { moveHandler = leftMoveHandler; } handles.moveHandler = moveHandler; } } function leftMoveHandler(area, delta) { area.x1 += delta; } function rightMoveHandler(area, delta) { area.x2 += delta; } function scrollMoveHandler(area, delta) { area.x1 += delta; area.x2 += delta; } })(); /*! * Bonzo: DOM Utility (c) Dustin Diaz 2011 * https://github.com/ded/bonzo * License MIT */ !function(a,b){typeof define=="function"?define(b):typeof module!="undefined"?module.exports=b():this[a]=b()}("bonzo",function(){function x(a){return new RegExp("(^|\\s+)"+a+"(\\s+|$)")}function y(a,b,c){for(var d=0,e=a.length;d]+)/.exec(a),d=c.createElement(b&&k[b[1].toLowerCase()]||"div"),e=[];d.innerHTML=a;var f=d.childNodes;d=d.firstChild,e.push(d);while(d=d.nextSibling)d.nodeType==1&&e.push(d);return e}():A(a)?[a.cloneNode(!0)]:[]},N.doc=function(){var a=this.viewport();return{width:Math.max(c.body.scrollWidth,d.scrollWidth,a.width),height:Math.max(c.body.scrollHeight,d.scrollHeight,a.height)}},N.firstChild=function(a){for(var b=a.childNodes,c=0,d=b&&b.length||0,e;c= 0 && newIndex < components.length && this.remove(component)) { if (this.rendered) { if (newIndex === components.length) this.node.appendChild(component.container); else this.node.insertBefore(component.container, components[newIndex].container); } components.splice(newIndex, 0, component); this._updateClasses(); } return this; }, /** * Gets the position of a component. * * @param {envision.Component} component */ indexOf : function (component) { return _.indexOf(this.components, component); }, /** * Gets the component at a position. * * @param {envision.Component} component * @returns {envision.Component} The found component. */ getComponent : function (index) { var components = this.components; if (index < components.length) return components[index]; }, /** * Gets whether or not the component is the first component * in the visualization. * * @param {envision.Component} component * @returns {Boolean} */ isFirst : function (component) { return (this.indexOf(component) === 0 ? true : false); }, /** * Gets whether or not the component is the last component * in the visualization. * * @param {envision.Component} component * @returns {Boolean} */ isLast : function (component) { return (this.indexOf(component) === this.components.length - 1 ? true : false); }, /** * Destroys the visualization. * * This empties the container and destroys all the components which are part * of the visualization. */ destroy : function () { _.each(this.components, function (component) { component.destroy(); }); bonzo(this.container).empty(); }, _renderComponent : function (component) { var container = bonzo.create(T_COMPONENT_CONTAINER)[0]; bonzo(this.node).append(container); component.render(container); }, _updateClasses : function () { var components = this.components, first = 0, last = components.length -1, node; _.each(components, function (component, index) { node = bonzo(component.container); if (index === first) node.addClass(CN_FIRST); else node.removeClass(CN_FIRST); if (index === last) node.addClass(CN_LAST); else node.removeClass(CN_LAST); }); } }; envision.Visualization = Visualization; })(); // Component Class (function () { var V = envision, CN_COMPONENT = 'envision-component', T_COMPONENT = '
'; /** * @summary Defines a visualization component. * * @description Components are the building blocks of a visualization, * representing one typically graphical piece of the vis. This class manages * the options, DOM and API construction for an adapter which handles the * actual drawing of the visualization piece. * * Adapters can take the form of an actual object, a constructor function * or a function returning an object. Only one of these will be used. If * none is submitted, the default adapter Flotr2 is used. * * @param {String} [name] A name for the component. * @param {Element} [element] A container element for the component. * @param {Number} [height] An explicit component height. * @param {Number} [width] An explicit component width. * @param {Array} [data] An array of data. Data may be formatted for * envision or for the adapter itself, in which case skipPreprocess will * also need to be submitted. * @param {Boolean} [skipPreprocess] Skip data preprocessing. This is useful * when using the native data format for an adapter. * @param {Object} [adapter] An adapter object. * @param {Function} [adapterConstructor] An adapter constructor to be * instantiated by the component. * @param {Function} [adapterCallback] An callback invoked by the component * returning an adapter. * @param {Object} [config] Configuration for the adapter. * * @memberof envision * @class */ function Component (options) { options = options || {}; var node = bonzo.create(T_COMPONENT)[0]; this.options = options; this.node = node; // Instantiate Adapter if (options.adapter) { this.api = options.adapter; } else if (options.adapterConstructor) { this.api = new options.adapterConstructor(options.config); } else if (options.adapterCallback) { this.api = options.adapterCallback.call(null, options.config); } else if (options.config) { this.api = new V.adapters.flotr.Child(options.config || {}); } // this.id = _.uniqueId(CN_COMPONENT); this.preprocessors = []; } Component.prototype = { /** * Render the component. * * If no element is submitted, the component will * render in the element configured in the constructor. * * @param {Element} [element] */ render : function (element) { var node = this.node, options = this.options; element = element || options.element; if (!element) throw 'No element to render within.'; bonzo(element) .addClass(options.name || '') .append(this.node); this._setDimension('width'); this._setDimension('height'); this.container = element; this.draw(options.data, options.config); }, /** * Draw the component. * * @param {Array} [data] Data for the adapter. * @param {Object} [options] Configuration object for the adapters draw method. */ draw : function (data, config) { var api = this.api, options = this.options, preprocessors = this.preprocessors, clientData; clientData = data = data || options.data; config = config || options.config; if (!options.skipPreprocess && data) { clientData = []; _.each(api.getDataArray(data), function (d, index) { var preprocessor = preprocessors[index] || new V.Preprocessor(), isArray = _.isArray(d), isFunction = _.isFunction(d), unprocessed = isArray ? d : (isFunction ? d : d.data), processData = options.processData, range = api.range(config), min = range.min, max = range.max, resolution = this.node.clientWidth, dataArray = d, processed, objectData; // For object data if (!isFunction && !isArray) { dataArray = d.data; objectData = _.extend({}, d); } // Do data function preprocessing if (isFunction) { processed = data(min, max, resolution); } else { // Update if new data if (dataArray !== preprocessor.data) { preprocessor.setData(dataArray); } else { preprocessor.reset(); } // Do custom callback preprocessing if (processData) { processData.apply(this, [{ preprocessor : preprocessor, min : min, max : max, resolution : resolution }]); processed = preprocessor.getData(); } // Default preprocessing else { processed = preprocessor .bound(min, max) .subsampleMinMax(resolution) .getData(); } } // If present, transform the data for the API if (api.transformData) { processed = api.transformData(processed); } // Object Data if (objectData) { objectData.data = processed; clientData.push(objectData); } // Array Data else { clientData.push(processed); } }, this); } if (api) api.draw(clientData, config, this.node); }, /** * Trigger an event on the component's API. * * Arguments are passed through to the API. */ trigger : function () { this.api.trigger.apply(this.api, Array.prototype.concat.apply([this], arguments)); }, /** * Attach to an event on the component's API. * * Arguments are passed through to the API. */ attach : function () { this.api.attach.apply(this.api, Array.prototype.concat.apply([this], arguments)); }, /** * Detach a listener from an event on the component's API. * * Arguments are passed through to the API. */ detach : function () { this.api.detach.apply(this.api, Array.prototype.concat.apply([this], arguments)); }, /** * Destroy the component. * * Empties the container and calls the destroy method on the * component's API. */ destroy : function () { if (this.api && this.api.destroy) this.api.destroy(); bonzo(this.container).empty(); }, _setDimension : function (attribute) { var node = this.node, options = this.options; if (options[attribute]) { bonzo(node).css(attribute, options[attribute]); } else { //options[attribute] = parseInt(bonzo(node).css(attribute), 10); options[attribute] = node.clientWidth; } this[attribute] = options[attribute]; } }; V.Component = Component; })(); // Interaction Class (function () { var H = envision; /** * @summary Defines an interaction between components. * * @description This class defines interactions in which actions are triggered * by leader components and reacted to by follower components. These actions * are defined as configurable mappings of trigger events and event consumers. * It is up to the adapter to implement the triggers and consumers. * * A component may be both a leader and a follower. A leader which is a * follower will react to actions triggered by other leaders, but will safely * not react to its own. This allows for groups of components to perform a * common action. * * Optionally, actions may be supplied with a callback executed before the * action is consumed. This allows for quick custom functionality to be added * and is how advanced data management (ie. live Ajax data) may be implemented. * * This class follow an observer mediator pattern. * * @param {envision.Component|Array} [leader] Component(s) to lead the * interaction * * @memberof envision * @class */ function Interaction(options) { this.options = options = options || {}; this.actions = []; this.actionOptions = []; this.followers = []; this.leaders = []; this.prevent = {}; if (options.leader) { this.leader(options.leader); } } Interaction.prototype = { /** * Add a component as an interaction leader. * * @param {envision.Component} component */ leader : function (component) { this.leaders.push(component); _.each(this.actions, function (action, i) { this._bindLeader(component, action, this.actionOptions[i]); }, this); return this; }, /** * Add a component as an interaction leader. * * @param {envision.Component} component */ follower : function (component) { this.followers.push(component); return this; }, /** * Adds an array of components as both followers and leaders. * * @param {Array} components An array of components */ group : function (components) { if (!_.isArray(components)) components = [components]; _.each(components, function (component) { this.leader(component); this.follower(component); }, this); return this; }, /** * Adds an action to the interaction. * * The action may be optionally configured with the options argument. * Currently the accepts a callback member, invoked after an action * is triggered and before it is consumed by followers. * * @param {Object} action * @param {Object} [options] */ add : function (action, options) { this.actions.push(action); this.actionOptions.push(options); _.each(this.leaders, function (leader) { this._bindLeader(leader, action, options); }, this); return this; }, _bindLeader : function (leader, action, options) { _.each(action.events, function (e) { var handler = e.handler || e, consumer = e.consumer || e; leader.attach(handler, _.bind(function (leader, result) { if (this.prevent[name]) return; // Apply custom callback configured for this action if (options && options.callback) { options.callback.call(this, result); } this.prevent[name] = true; // Prevent recursions for this name try { _.each(this.followers, function (follower) { if (leader === follower) return; // Skip leader (recursion) follower.trigger(consumer, result); }, this); } catch (e) { this.prevent[name] = false; throw e; } this.prevent[name] = false; }, this)); }, this); } }; H.Interaction = Interaction; })(); // Preprocessor Class (function () { /** * @summary Data preprocessor. * * @description Data can be preprocessed before it is rendered by an adapter. * * This has several important performance considerations. If data will be * rendered repeatedly or on slower browsers, it will be faster after being * optimized. * * First, data outside the boundaries does not need to be rendered. Second, * the resolution of the data only needs to be at most the number of pixels * in the width of the visualization. * * Performing these optimizations will limit memory overhead, important * for garbage collection and performance on old browsers, as well as drawing * overhead, important for mobile devices, old browsers and large data sets. * * @param {Array} [data] The data for processing. * * @memberof envision * @class */ function Preprocessor (options) { options = options || {}; /** * Returns data. */ this.getData = function () { if (this.bounded) bound(this); return this.processing; }; this.reset = function () { this.processing = this.data; return this; }; /** * Set the data object. */ this.setData = function (data) { var i, length; if (!_.isArray(data)) throw new Error('Array expected.'); if (data.length < 2) throw new Error('Data must contain at least two dimensions.'); length = data[0].length; for (i = data.length; i--;) { if (!_.isArray(data[i])) throw new Error('Data dimensions must be arrays.'); if (data[i].length !== length) throw new Error('Data dimensions must contain the same number of points.'); } this.processing = data; this.data = data; return this; }; if (options.data) this.setData(options.data); } function getStartIndex (data, min) { var i = _.sortedIndex(data, min); // Include point outside range when not exact match if (data[i] > min && i > 0) i--; return i; } function getEndIndex (data, max) { return _.sortedIndex(data, max); } function bound (that) { delete that.bounded; var data = that.processing, length = that.length(), x = data[0], y = data[1], min = that.min || 0, max = that.max || length, start = getStartIndex(x, min), end = getEndIndex(x, max); that.processing = [ x.slice(start, end + 1), y.slice(start, end + 1) ]; that.start = start; that.end = end; } Preprocessor.prototype = { /** * Returns the length of the data set. * * @return {Number} Length of the data set. */ length : function () { return this.getData()[0].length; }, /** * Bounds the data set at within a range. * * @param {Number} min * @param {Number} max */ bound : function (min, max) { if (!_.isNumber(min) || !_.isNumber(max)) return this; this.min = min; this.max = max; this.bounded = true; return this; }, /** * Subsample data using MinMax. * * MinMax will display the extrema of the subsample intervals. This is * slower than regular interval subsampling but necessary for data that * is very non-homogenous. * * @param {Number} resolution */ subsampleMinMax : function (resolution) { var bounded = this.bounded; delete this.bounded; var data = this.processing, length = this.length(), x = data[0], y = data[1], start = bounded ? getStartIndex(x, this.min) : 0, end = bounded ? getEndIndex(x, this.max) : length - 1, count = (resolution - 2) / 2, newX = [], newY = [], min = Number.MAX_VALUE, max = -Number.MAX_VALUE, minI = 1, maxI = 1, unit = (end - start)/ count, position, datum, i, j; if (end - start + 1 > resolution) { newX.push(x[start]); newY.push(y[start]); position = start + unit; for (i = start; i < end; i++) { if (i === Math.round(position)) { position += unit; j = Math.min(maxI, minI); newX.push(x[j]); newY.push(y[j]); j = Math.max(maxI, minI); newX.push(x[j]); newY.push(y[j]); minI = i; min = y[minI]; maxI = i; max = y[maxI]; } else { if (y[i] > max) { max = y[i]; maxI = i; } if (y[i] < min) { min = y[i]; minI = i; } } } if (i < position) { newX.push(x[minI]); newY.push(min); newX.push(x[maxI]); newY.push(max); } // Last newX.push(x[end]); newY.push(y[end]); this.processing = [newX, newY]; this.start = start; this.end = end; } else { this.bounded = bounded; } return this; }, /** * Subsample data at a regular interval for resolution. * * This is the fastest subsampling and good for monotonic data and fairly * homogenous data (not a lot of up and down). * * @param {Number} resolution */ subsample : function (resolution) { var bounded = this.bounded; delete this.bounded; var data = this.processing, length = this.length(), x = data[0], y = data[1], start = bounded ? getStartIndex(x, this.min) : 0, end = bounded ? getEndIndex(x, this.max) : length - 1, unit = (end - start + 1) / resolution, newX = [], newY = [], i, index; if (end - start + 1 > resolution) { // First newX.push(x[start]); newY.push(y[start]); for (i = 1; i < resolution; i++) { if (i * unit >= end - unit) break; index = Math.round(i * unit) + start; newX.push(x[index]); newY.push(y[index]); } // Last newX.push(x[end]); newY.push(y[end]); this.processing = [newX, newY]; this.start = start; this.end = end; } else { this.bounded = bounded; } return this; }, interpolate : function (resolution) { var bounded = this.bounded; delete this.bounded; var data = this.processing, length = this.length(), x = data[0], y = data[1], start = bounded ? getStartIndex(x, this.min) : 0, end = bounded ? getEndIndex(x, this.max) : length - 1, unit = (x[end] - x[start]) / resolution, newX = [], newY = [], i, j, delta; newX.push(x[start]); newY.push(y[start]); if (end - start + 1 < resolution) { for (i = start; i < end; i++) { delta = x[i + 1] - x[i]; newX.push(x[i]); newY.push(y[i]); for (j = x[i + 0] + unit; j < x[i + 1]; j += unit) { newX.push(j); newY.push(cubicHermiteSpline( j, x[i - 1], y[i - 1], x[i + 0], y[i + 0], x[i + 1], y[i + 1], x[i + 2], y[i + 2] )); } } this.processing = [newX, newY]; this.start = start; this.end = end; } return this; } }; function cubicHermiteSpline (x, tk0, pk0, tk1, pk1, tk2, pk2, tk3, pk3) { var t = (x - tk1) / (tk2 - tk1), t1 = 1 - t, h00 = (1 + 2 * t) * t1 * t1, h10 = t * t1 * t1, h01 = t * t * (3 - 2 * t), h11 = t * t * (t - 1), mk = (pk2 - pk1) / (2 * (tk2 - tk1)) + (typeof pk0 === 'undefined' ? 0 : (pk1 - pk0) / (2 * (tk1 - tk0))), mk1 = (typeof pk3 === 'undefined' ? 0 : (pk3 - pk2) / (2 * (tk3 - tk2))) + (pk2 - pk1) / (2 * (tk2 - tk1)), px = h00 * pk1 + h10 * (tk2 - tk1) * mk + h01 * pk2 + h11 * (tk2 - tk1) * mk1; return px; } envision.Preprocessor = Preprocessor; }()); /** * Actions namespace. Actions are configurations for * common use cases when building Interactions. */ envision.actions = envision.actions || {}; envision.actions.hit = { events : [ 'hit', 'mouseout' ] }; envision.actions.selection = { events : [ { handler : 'select', consumer : 'zoom' }, // Reset on click, avoids re-drawing the leader. { handler : 'click', consumer : 'reset' } ] }; envision.actions.zoom = { events : [ // Zoom on the followers as selecting { handler : 'select', consumer : 'zoom' }, // Zoom on the leader after mouseup 'zoom', // Reset all on click 'reset' ] }; /** * Adapters namespace. These are component adapters for external * librares. Envision.js ships with a Flotr2 adapter. */ envision.adapters = envision.adapters || {}; envision.adapters.flotr = {}; /* * Flotr Default Options */ envision.adapters.defaultOptions = { grid : { outlineWidth : 0, labelMargin : 0, horizontalLines : false, verticalLines : false }, bars : { show : false, barWidth : 0.5, fill : true, lineWidth : 1, fillOpacity : 1 }, lines : { lineWidth : 1 }, xaxis : { margin : false, tickDecimals: 0, showLabels : false }, yaxis : { margin : false, showLabels : false }, shadowSize : false }; /** * Flotr Adapter */ (function () { var V = envision, A = envision.adapters, E = Flotr.EventAdapter, DEFAULTS = A.defaultOptions; function Child (options) { this.options = options || {}; this.flotr = null; this._flotrDefaultOptions(); } Child.prototype = { destroy : function () { this.flotr.destroy(); }, draw : function (data, flotr, node) { var options = this.options; data = this.getDataArray(data || options.data); if (flotr) { flotr = Flotr.merge(flotr, Flotr.clone(options)); } else { flotr = options; } options.data = data; if (!flotr) throw 'No graph submitted.'; this.flotr = Flotr.draw(node, data, flotr); }, range : function (flotr) { var axis = flotr.xaxis; return { min : axis.min, max : axis.max }; }, // Transform for Flotr transformData : function (data) { var length = data[0].length, dimension = data.length, transformed = [], point, i, j; for (i = 0; i < length; i++) { point = []; for (j = 0; j < dimension; j++) { point.push(data[j][i]); } transformed.push(point); } return transformed; }, getDataArray : function (data) { if ( data[0] && // Data exists and ( !_.isArray(data[0]) || // data not an array !data[0].length || // data is an empty series (data[0][0] && _.isArray(data[0][0])) // data is a series ) ) return data; else return [data]; }, _flotrDefaultOptions : function (options) { var o = options || this.options, i; for (i in DEFAULTS) { if (DEFAULTS.hasOwnProperty(i)) { if (_.isUndefined(o[i])) { o[i] = DEFAULTS[i]; } else { _.defaults(o[i], DEFAULTS[i]); } } } }, attach : function (component, name, callback) { var event = this.events[name] || {}, handler = event.handler || false; name = event.name || false; if (handler) { return E.observe(component.node, name, function () { var args = [component].concat(Array.prototype.slice.call(arguments)), result = handler.apply(this, args); return callback.apply(null, [component, result]); }); } else { return false; } }, detach : function (component, name, callback) { return E.stopObserve(component.node, name, handler); }, trigger : function (component, name, options) { var event = this.events[name], consumer = event.consumer || false; return consumer ? consumer.apply(this, [component, options]) : false; }, events : { hit : { name : 'flotr:hit', handler : function (component, hit) { var x = hit.x, y = hit.y, graph = component.api.flotr, options; // Normalized hit: options = { data : { index : hit.index, x : x, y : y }, x : graph.axes.x.d2p(x), y : graph.axes.y.d2p(y) }; return options; }, consumer : function (component, hit) { var graph = component.api.flotr, o; // TODO this is a hack; // the hit plugin should expose an API to do this easily o = { x : hit.data.x, y : hit.data.y || 1, relX : hit.x, relY : hit.y || 1 }; graph.hit.hit(o); } }, select : { name : 'flotr:selecting', handler : selectHandler, consumer : function (component, selection) { var graph = component.api.flotr, axes = graph.axes, data = selection.data || {}, options = {}, x = selection.x, y = selection.y; if (!x && data.x) { // Translate data to pixels x = data.x; options.x1 = x.min; options.x2 = x.max; } else if (x) { // Use pixels options.x1 = axes.x.p2d(x.min); options.x2 = axes.x.p2d(x.max); } if (!y && data.y) { // Translate data to pixels y = data.y; options.y1 = y.min; options.y2 = y.max; } else if (y) { // Use pixels options.y1 = axes.y.d2p(y.min); options.y2 = axes.y.d2p(y.max); } graph.selection.setSelection(options); } }, zoom : { name : 'flotr:select', handler : function (component, selection) { var options = selectHandler(component, selection); component.trigger('zoom', options); return options; }, consumer : function (component, selection) { var x = selection.data.x, y = selection.data.y, options = {}; if (x) { options.xaxis = { min : x.min, max : x.max }; } if (y) { options.yaxis = { min : y.min, max : y.max }; } component.draw(null, options); } }, mouseout : { name : 'flotr:mouseout', handler : function (component) { }, consumer : function (component) { component.api.flotr.hit.clearHit(); } }, reset : { name : 'flotr:click', handler : function (component) { component.draw(); }, consumer : function (component) { component.draw(); } }, click : { name : 'flotr:click', handler : function (component) { var min = component.api.flotr.axes.x.min, max = component.api.flotr.axes.x.max; return { data : { x : { min : min, max : max } }, x : { min : component.api.flotr.axes.x.d2p(min), max : component.api.flotr.axes.x.d2p(max) } }; }, consumer : function (component, selection) { var x = selection.data.x, y = selection.data.y, options = {}; if (x) { options.xaxis = { min : x.min, max : x.max }; } if (y) { options.yaxis = { min : y.min, max : y.max }; } component.draw(null, options); } } } }; function selectHandler (component, selection) { var mode = component.options.config.selection.mode, axes = component.api.flotr.axes, datax, datay, x, y, options; if (mode.indexOf('x') !== -1) { datax = {}; datax.min = selection.x1; datax.max = selection.x2; x = {}; x.min = axes.x.d2p(selection.x1); x.max = axes.x.d2p(selection.x2); } if (mode.indexOf('y') !== -1) { datay = {}; datay.min = selection.y1; datay.max = selection.y2; y = {}; y.min = axes.y.d2p(selection.y1); y.max = axes.y.d2p(selection.y2); } // Normalized selection: options = { data : { x : datax, y : datay }, x : x, y : y }; return options; } A.flotr.Child = Child; })(); /** Lines **/ Flotr.addType('lite-lines', { options: { show: false, // => setting to true will show lines, false will hide lineWidth: 2, // => line width in pixels fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillBorder: false, // => draw a border around the fill fillColor: null, // => fill color fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill }, /** * Draws lines series in the canvas element. * @param {Object} options */ draw : function (options) { var context = options.context, lineWidth = options.lineWidth, shadowSize = options.shadowSize, offset; context.save(); context.lineCap = 'butt'; context.lineWidth = lineWidth; context.strokeStyle = options.color; this.plot(options); context.restore(); }, plot : function (options) { var context = options.context, xScale = options.xScale, yScale = options.yScale, data = options.data, length = data.length - 1, zero = yScale(0), x0, y0; if (length < 1) return; x0 = xScale(data[0][0]); y0 = yScale(data[0][1]); context.beginPath(); context.moveTo(x0, y0); for (i = 0; i < length; ++i) { context.lineTo( xScale(data[i+1][0]), yScale(data[i+1][1]) ); } if (!options.fill || options.fill && !options.fillBorder) context.stroke(); if (options.fill){ x0 = xScale(data[0][0]); context.fillStyle = options.fillStyle; context.lineTo(xScale(data[length][0]), zero); context.lineTo(x0, zero); context.lineTo(x0, yScale(data[0][1])); context.fill(); if (options.fillBorder) { context.stroke(); } } }, extendYRange : function (axis, data, options, lines) { var o = axis.options; // HACK if ((!o.max && o.max !== 0) || (!o.min && o.min !== 0)) { axis.max += options.lineWidth * 0.01; axis.min -= options.lineWidth * 0.01; /* axis.max = axis.p2d((axis.d2p(axis.max) + options.lineWidth)); axis.min = axis.p2d((axis.d2p(axis.max) - options.lineWidth)); */ } } }); /** Bars **/ Flotr.addType('whiskers', { 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 }, stack : { positive : [], negative : [], _positive : [], // Shadow _negative : [] // Shadow }, draw : function (options) { var context = options.context; context.save(); context.lineJoin = 'miter'; context.lineCap = 'butt'; 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, xScale = options.xScale, yScale = options.yScale, zero = yScale(0), i, x; if (data.length < 1) return; context.translate(-options.lineWidth, 0); context.beginPath(); for (i = 0; i < data.length; i++) { x = xScale(data[i][0]); context.moveTo(x, zero); context.lineTo(x, yScale(data[i][1])); } context.closePath(); context.stroke(); }, drawHit : function (options) { var args = options.args, context = options.context, shadowSize = options.shadowSize, xScale = options.xScale, yScale = options.yScale, zero = yScale(0), x = xScale(args.x), y = yScale(args.y); context.save(); context.translate(-options.lineWidth, 0); context.beginPath(); context.moveTo(x, zero); context.lineTo(x, y); context.closePath(); context.stroke(); context.restore(); }, clearHit: function (options) { var args = options.args, context = options.context, shadowSize = options.shadowSize, xScale = options.xScale, yScale = options.yScale, lineWidth = options.lineWidth, zero = yScale(0), x = xScale(args.x), y = yScale(args.y); context.save(); context.clearRect(x - 2 * lineWidth, y - lineWidth, 4 * lineWidth, zero - y + lineWidth); context.restore(); } }); /** * Components namespace. These are standalone, custom components * APIs for widgets, decorations, flair. */ envision.components = envision.components || {}; (function () { function QuadraticDrawing (options) { this.options = options || {}; } QuadraticDrawing.prototype = { height : null, width : null, rendered : false, render : function (node) { var canvas = document.createElement('canvas'), offset = bonzo(node).offset(); this.height = offset.height; this.width = offset.width; bonzo(canvas) .attr('height', offset.height) .attr('width', offset.width) .css({ position : 'absolute', top : '0px', left : '0px' }); node.appendChild(canvas); bonzo(node).css({ position : 'relative' }); if (typeof FlashCanvas !== 'undefined') FlashCanvas.initElement(canvas); this.context = canvas.getContext('2d'); this.rendered = true; }, draw : function (data, options, node) { if (!this.rendered) this.render(node); var context = this.context, height = this.height, width = this.width, half = Math.round(height / 2) - 0.5, min, max; options = options || { min : width / 2, max : width / 2}; min = options.min + 0.5; max = options.max + 0.5; context.clearRect(0, 0, width, height); if (min || max) { context.save(); context.strokeStyle = this.options.strokeStyle || '#B6D9FF'; context.fillStyle = this.options.fillStyle || 'rgba(182, 217, 255, .4)'; context.beginPath(); // Left if (min <= 1) { context.moveTo(0, height); context.lineTo(0, -0.5); } else { context.moveTo(min, height); context.quadraticCurveTo(min, half, Math.max(min - half, min / 2), half); context.lineTo(Math.min(half, min / 2), half); context.quadraticCurveTo(0, half, 0.5, -0.5); } // Top context.lineTo(width - 0.5, -0.5); // Right if (max >= width - 1) { context.lineTo(max, height); } else { context.quadraticCurveTo(width, half, Math.max(width - half, width - (width - max) / 2), half); context.lineTo(Math.min(max + half, width - (width - max) / 2), half); context.quadraticCurveTo(max, half, max, height); } context.stroke(); context.closePath(); context.fill(); context.restore(); } }, trigger : function (component, name, options) { if (name === 'zoom') { this.zoom(component, options); } else if (name === 'reset') { this.reset(component); } }, zoom : function (component, options) { var x = options.x || {}, min = x.min, max = x.max, api = component.api; component.draw(null, { min : min, max : max }); }, reset : function (component) { component.draw(null, { min : component.width / 2, max : component.width / 2 }); } }; envision.components.QuadraticDrawing = QuadraticDrawing; })(); /** * Templates namespace. * * Templates are pre-built interactive visualizations fitting common * use-cases. These include several components together with * interactions and configuration for each. They may have their own * custom configuration options as well. */ envision.templates = envision.templates || {}; (function () { var V = envision; // Custom data processor function processData (options) { var resolution = options.resolution; options.preprocessor .bound(options.min, options.max) .subsampleMinMax(resolution + Math.round(resolution / 3)); } function getDefaults () { return { price : { name : 'envision-finance-price', config : { 'lite-lines' : { lineWidth : 1, show : true, fill : true, fillOpacity : 0.2 }, mouse : { track: true, trackY: false, trackAll: true, sensibility: 1, trackDecimals: 4, position: 'ne' }, yaxis : { autoscale : true, autoscaleMargin : 0.05, noTicks : 4, showLabels : true, min : 0 } }, processData : processData }, volume : { name : 'envision-finance-volume', config : { whiskers : { show : true, lineWidth : 2 }, mouse: { track: true, trackY: false, trackAll: true }, yaxis : { autoscale : true, autoscaleMargin : 0.5 } }, processData : processData }, summary : { name : 'envision-finance-summary', config : { 'lite-lines' : { show : true, lineWidth : 1, fill : true, fillOpacity : 0.2, fillBorder : true }, xaxis : { noTicks: 5, showLabels : true }, yaxis : { autoscale : true, autoscaleMargin : 0.1 }, handles : { show : true }, selection : { mode : 'x' }, grid : { verticalLines : false } } }, connection : { name : 'envision-finance-connection', adapterConstructor : V.components.QuadraticDrawing } }; } function Finance (options) { var data = options.data, defaults = getDefaults(), vis = new V.Visualization({name : 'envision-finance'}), selection = new V.Interaction(), hit = new V.Interaction(), price, volume, connection, summary; if (options.defaults) { defaults = Flotr.merge(options.defaults, defaults); } defaults.price.data = data.price; defaults.volume.data = data.volume; defaults.summary.data = data.summary; defaults.price.config.mouse.trackFormatter = options.trackFormatter || function (o) { var index = o.index, value; if (price.api.preprocessor) { index += price.api.preprocessor.start; } value = 'Price: $' + data.price[1][index] + ", Vol: " + data.volume[1][index]; return value; }; if (options.xTickFormatter) { defaults.summary.config.xaxis.tickFormatter = options.xTickFormatter; } defaults.price.config.yaxis.tickFormatter = options.yTickFormatter || function (n) { return '$' + n; }; price = new V.Component(defaults.price); volume = new V.Component(defaults.volume); connection = new V.Component(defaults.connection); summary = new V.Component(defaults.summary); // Render visualization vis .add(price) .add(volume) .add(connection) .add(summary) .render(options.container); // Define the selection zooming interaction selection .follower(price) .follower(volume) .follower(connection) .leader(summary) .add(V.actions.selection, options.selectionCallback ? { callback : options.selectionCallback } : null); // Define the mouseover hit interaction hit .group([price, volume]) .add(V.actions.hit); // Optional initial selection if (options.selection) { summary.trigger('select', options.selection); } // Members this.vis = vis; this.selection = selection; this.hit = hit; this.price = price; this.volume = volume; this.summary = summary; } V.templates.Finance = Finance; })(); (function () { var V = envision; function getDefaults () { return { detail : { name : 'envision-timeseries-detail', config : { 'lite-lines' : { lineWidth : 1, show : true } } }, summary : { name : 'envision-timeseries-summary', config : { 'lite-lines' : { lineWidth : 1, show : true }, handles : { show : true }, selection : { mode : 'x' }, yaxis : { autoscale : true, autoscaleMargin : 0.1 } } }, connection : { name : 'envision-timeseries-connection', adapterConstructor : V.components.QuadraticDrawing } }; } function TimeSeries (options) { var data = options.data, defaults = getDefaults(), vis = new V.Visualization({name : 'envision-timeseries'}), selection = new V.Interaction(), detail, summary, connection; // Fill Defaults if (options.defaults) { defaults = Flotr.merge(options.defaults, defaults); } defaults.detail.data = data.detail; defaults.summary.data = data.summary; // Build Components detail = new V.Component(defaults.detail); connection = new V.Component(defaults.connection); summary = new V.Component(defaults.summary); // Render visualization vis .add(detail) .add(connection) .add(summary) .render(options.container); // Selection action selection .follower(detail) .follower(connection) .leader(summary) .add(V.actions.selection, options.selectionCallback ? { callback : options.selectionCallback } : null); // Optional initial selection if (options.selection) { summary.trigger('select', options.selection); } this.vis = vis; this.selection = selection; this.detail = detail; this.summary = summary; } V.templates.TimeSeries = TimeSeries; })(); (function () { var V = envision, Zoom; function defaultsZoom () { return { name : 'zoom' }; } function defaultsSummary () { return { name : 'summary', config : { handles : { show : true }, selection : { mode : 'x'} } }; } function getDefaults (options, defaults) { var o = _.defaults(options, defaults); o.flotr = _.defaults(o.flotr, defaults.flotr); return o; } Zoom = function (options) { var vis = new V.Visualization(), zoom = new V.Component(getDefaults(options.zoom || {}, defaultsZoom())), summary = new V.Component(getDefaults(options.summary || {}, defaultsSummary())), interaction = new V.Interaction({leader : summary}); vis .add(zoom) .add(summary); interaction.add(V.actions.selection); interaction.follower(zoom); this.vis = vis; this.interaction = interaction; if (options.container) { this.render(options.container); } }; Zoom.prototype = { render : function (container) { this.vis.render(container); } }; V.templates.Zoom = Zoom; })(); ;




© 2015 - 2025 Weber Informatics LLC | Privacy Policy