]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/debian/missing-sources/protected/JavaScript/flotr2.js
baculum: Add missing-sources directory in debian metadata structure
[bacula/bacula] / gui / baculum / debian / missing-sources / protected / JavaScript / flotr2.js
1 /*!
2   * bean.js - copyright Jacob Thornton 2011
3   * https://github.com/fat/bean
4   * MIT License
5   * special thanks to:
6   * dean edwards: http://dean.edwards.name/
7   * dperini: https://github.com/dperini/nwevents
8   * the entire mootools team: github.com/mootools/mootools-core
9   */
10 /*global module:true, define:true*/
11 !function (name, context, definition) {
12   if (typeof module !== 'undefined') module.exports = definition(name, context);
13   else if (typeof define === 'function' && typeof define.amd  === 'object') define(definition);
14   else context[name] = definition(name, context);
15 }('bean', this, function (name, context) {
16   var win = window
17     , old = context[name]
18     , overOut = /over|out/
19     , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
20     , nameRegex = /\..*/
21     , addEvent = 'addEventListener'
22     , attachEvent = 'attachEvent'
23     , removeEvent = 'removeEventListener'
24     , detachEvent = 'detachEvent'
25     , doc = document || {}
26     , root = doc.documentElement || {}
27     , W3C_MODEL = root[addEvent]
28     , eventSupport = W3C_MODEL ? addEvent : attachEvent
29     , slice = Array.prototype.slice
30     , mouseTypeRegex = /click|mouse|menu|drag|drop/i
31     , touchTypeRegex = /^touch|^gesture/i
32     , ONE = { one: 1 } // singleton for quick matching making add() do one()
33
34     , nativeEvents = (function (hash, events, i) {
35         for (i = 0; i < events.length; i++)
36           hash[events[i]] = 1
37         return hash
38       })({}, (
39           'click dblclick mouseup mousedown contextmenu ' +                  // mouse buttons
40           'mousewheel DOMMouseScroll ' +                                     // mouse wheel
41           'mouseover mouseout mousemove selectstart selectend ' +            // mouse movement
42           'keydown keypress keyup ' +                                        // keyboard
43           'orientationchange ' +                                             // mobile
44           'focus blur change reset select submit ' +                         // form elements
45           'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window
46           'error abort scroll ' +                                            // misc
47           (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
48                        // that doesn't actually exist, so make sure we only do these on newer browsers
49             'show ' +                                                          // mouse buttons
50             'input invalid ' +                                                 // form elements
51             'touchstart touchmove touchend touchcancel ' +                     // touch
52             'gesturestart gesturechange gestureend ' +                         // gesture
53             'message readystatechange pageshow pagehide popstate ' +           // window
54             'hashchange offline online ' +                                     // window
55             'afterprint beforeprint ' +                                        // printing
56             'dragstart dragenter dragover dragleave drag drop dragend ' +      // dnd
57             'loadstart progress suspend emptied stalled loadmetadata ' +       // media
58             'loadeddata canplay canplaythrough playing waiting seeking ' +     // media
59             'seeked ended durationchange timeupdate play pause ratechange ' +  // media
60             'volumechange cuechange ' +                                        // media
61             'checking noupdate downloading cached updateready obsolete ' +     // appcache
62             '' : '')
63         ).split(' ')
64       )
65
66     , customEvents = (function () {
67         function isDescendant(parent, node) {
68           while ((node = node.parentNode) !== null) {
69             if (node === parent) return true
70           }
71           return false
72         }
73
74         function check(event) {
75           var related = event.relatedTarget
76           if (!related) return related === null
77           return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related))
78         }
79
80         return {
81             mouseenter: { base: 'mouseover', condition: check }
82           , mouseleave: { base: 'mouseout', condition: check }
83           , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
84         }
85       })()
86
87     , fixEvent = (function () {
88         var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ')
89           , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' '))
90           , keyProps = commonProps.concat('char charCode key keyCode'.split(' '))
91           , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' '))
92           , preventDefault = 'preventDefault'
93           , createPreventDefault = function (event) {
94               return function () {
95                 if (event[preventDefault])
96                   event[preventDefault]()
97                 else
98                   event.returnValue = false
99               }
100             }
101           , stopPropagation = 'stopPropagation'
102           , createStopPropagation = function (event) {
103               return function () {
104                 if (event[stopPropagation])
105                   event[stopPropagation]()
106                 else
107                   event.cancelBubble = true
108               }
109             }
110           , createStop = function (synEvent) {
111               return function () {
112                 synEvent[preventDefault]()
113                 synEvent[stopPropagation]()
114                 synEvent.stopped = true
115               }
116             }
117           , copyProps = function (event, result, props) {
118               var i, p
119               for (i = props.length; i--;) {
120                 p = props[i]
121                 if (!(p in result) && p in event) result[p] = event[p]
122               }
123             }
124
125         return function (event, isNative) {
126           var result = { originalEvent: event, isNative: isNative }
127           if (!event)
128             return result
129
130           var props
131             , type = event.type
132             , target = event.target || event.srcElement
133
134           result[preventDefault] = createPreventDefault(event)
135           result[stopPropagation] = createStopPropagation(event)
136           result.stop = createStop(result)
137           result.target = target && target.nodeType === 3 ? target.parentNode : target
138
139           if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive
140             if (type.indexOf('key') !== -1) {
141               props = keyProps
142               result.keyCode = event.which || event.keyCode
143             } else if (mouseTypeRegex.test(type)) {
144               props = mouseProps
145               result.rightClick = event.which === 3 || event.button === 2
146               result.pos = { x: 0, y: 0 }
147               if (event.pageX || event.pageY) {
148                 result.clientX = event.pageX
149                 result.clientY = event.pageY
150               } else if (event.clientX || event.clientY) {
151                 result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
152                 result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
153               }
154               if (overOut.test(type))
155                 result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element']
156             } else if (touchTypeRegex.test(type)) {
157               props = touchProps
158             }
159             copyProps(event, result, props || commonProps)
160           }
161           return result
162         }
163       })()
164
165       // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
166     , targetElement = function (element, isNative) {
167         return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
168       }
169
170       // we use one of these per listener, of any type
171     , RegEntry = (function () {
172         function entry(element, type, handler, original, namespaces) {
173           this.element = element
174           this.type = type
175           this.handler = handler
176           this.original = original
177           this.namespaces = namespaces
178           this.custom = customEvents[type]
179           this.isNative = nativeEvents[type] && element[eventSupport]
180           this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'
181           this.customType = !W3C_MODEL && !this.isNative && type
182           this.target = targetElement(element, this.isNative)
183           this.eventSupport = this.target[eventSupport]
184         }
185
186         entry.prototype = {
187             // given a list of namespaces, is our entry in any of them?
188             inNamespaces: function (checkNamespaces) {
189               var i, j
190               if (!checkNamespaces)
191                 return true
192               if (!this.namespaces)
193                 return false
194               for (i = checkNamespaces.length; i--;) {
195                 for (j = this.namespaces.length; j--;) {
196                   if (checkNamespaces[i] === this.namespaces[j])
197                     return true
198                 }
199               }
200               return false
201             }
202
203             // match by element, original fn (opt), handler fn (opt)
204           , matches: function (checkElement, checkOriginal, checkHandler) {
205               return this.element === checkElement &&
206                 (!checkOriginal || this.original === checkOriginal) &&
207                 (!checkHandler || this.handler === checkHandler)
208             }
209         }
210
211         return entry
212       })()
213
214     , registry = (function () {
215         // our map stores arrays by event type, just because it's better than storing
216         // everything in a single array. uses '$' as a prefix for the keys for safety
217         var map = {}
218
219           // generic functional search of our registry for matching listeners,
220           // `fn` returns false to break out of the loop
221           , forAll = function (element, type, original, handler, fn) {
222               if (!type || type === '*') {
223                 // search the whole registry
224                 for (var t in map) {
225                   if (t.charAt(0) === '$')
226                     forAll(element, t.substr(1), original, handler, fn)
227                 }
228               } else {
229                 var i = 0, l, list = map['$' + type], all = element === '*'
230                 if (!list)
231                   return
232                 for (l = list.length; i < l; i++) {
233                   if (all || list[i].matches(element, original, handler))
234                     if (!fn(list[i], list, i, type))
235                       return
236                 }
237               }
238             }
239
240           , has = function (element, type, original) {
241               // we're not using forAll here simply because it's a bit slower and this
242               // needs to be fast
243               var i, list = map['$' + type]
244               if (list) {
245                 for (i = list.length; i--;) {
246                   if (list[i].matches(element, original, null))
247                     return true
248                 }
249               }
250               return false
251             }
252
253           , get = function (element, type, original) {
254               var entries = []
255               forAll(element, type, original, null, function (entry) { return entries.push(entry) })
256               return entries
257             }
258
259           , put = function (entry) {
260               (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry)
261               return entry
262             }
263
264           , del = function (entry) {
265               forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) {
266                 list.splice(i, 1)
267                 if (list.length === 0)
268                   delete map['$' + entry.type]
269                 return false
270               })
271             }
272
273             // dump all entries, used for onunload
274           , entries = function () {
275               var t, entries = []
276               for (t in map) {
277                 if (t.charAt(0) === '$')
278                   entries = entries.concat(map[t])
279               }
280               return entries
281             }
282
283         return { has: has, get: get, put: put, del: del, entries: entries }
284       })()
285
286       // add and remove listeners to DOM elements
287     , listener = W3C_MODEL ? function (element, type, fn, add) {
288         element[add ? addEvent : removeEvent](type, fn, false)
289       } : function (element, type, fn, add, custom) {
290         if (custom && add && element['_on' + custom] === null)
291           element['_on' + custom] = 0
292         element[add ? attachEvent : detachEvent]('on' + type, fn)
293       }
294
295     , nativeHandler = function (element, fn, args) {
296         return function (event) {
297           event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true)
298           return fn.apply(element, [event].concat(args))
299         }
300       }
301
302     , customHandler = function (element, fn, type, condition, args, isNative) {
303         return function (event) {
304           if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) {
305             if (event)
306               event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative)
307             fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args))
308           }
309         }
310       }
311
312     , once = function (rm, element, type, fn, originalFn) {
313         // wrap the handler in a handler that does a remove as well
314         return function () {
315           rm(element, type, originalFn)
316           fn.apply(this, arguments)
317         }
318       }
319
320     , removeListener = function (element, orgType, handler, namespaces) {
321         var i, l, entry
322           , type = (orgType && orgType.replace(nameRegex, ''))
323           , handlers = registry.get(element, type, handler)
324
325         for (i = 0, l = handlers.length; i < l; i++) {
326           if (handlers[i].inNamespaces(namespaces)) {
327             if ((entry = handlers[i]).eventSupport)
328               listener(entry.target, entry.eventType, entry.handler, false, entry.type)
329             // TODO: this is problematic, we have a registry.get() and registry.del() that
330             // both do registry searches so we waste cycles doing this. Needs to be rolled into
331             // a single registry.forAll(fn) that removes while finding, but the catch is that
332             // we'll be splicing the arrays that we're iterating over. Needs extra tests to
333             // make sure we don't screw it up. @rvagg
334             registry.del(entry)
335           }
336         }
337       }
338
339     , addListener = function (element, orgType, fn, originalFn, args) {
340         var entry
341           , type = orgType.replace(nameRegex, '')
342           , namespaces = orgType.replace(namespaceRegex, '').split('.')
343
344         if (registry.has(element, type, fn))
345           return element // no dupe
346         if (type === 'unload')
347           fn = once(removeListener, element, type, fn, originalFn) // self clean-up
348         if (customEvents[type]) {
349           if (customEvents[type].condition)
350             fn = customHandler(element, fn, type, customEvents[type].condition, true)
351           type = customEvents[type].base || type
352         }
353         entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces))
354         entry.handler = entry.isNative ?
355           nativeHandler(element, entry.handler, args) :
356           customHandler(element, entry.handler, type, false, args, false)
357         if (entry.eventSupport)
358           listener(entry.target, entry.eventType, entry.handler, true, entry.customType)
359       }
360
361     , del = function (selector, fn, $) {
362         return function (e) {
363           var target, i, array = typeof selector === 'string' ? $(selector, this) : selector
364           for (target = e.target; target && target !== this; target = target.parentNode) {
365             for (i = array.length; i--;) {
366               if (array[i] === target) {
367                 return fn.apply(target, arguments)
368               }
369             }
370           }
371         }
372       }
373
374     , remove = function (element, typeSpec, fn) {
375         var k, m, type, namespaces, i
376           , rm = removeListener
377           , isString = typeSpec && typeof typeSpec === 'string'
378
379         if (isString && typeSpec.indexOf(' ') > 0) {
380           // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
381           typeSpec = typeSpec.split(' ')
382           for (i = typeSpec.length; i--;)
383             remove(element, typeSpec[i], fn)
384           return element
385         }
386         type = isString && typeSpec.replace(nameRegex, '')
387         if (type && customEvents[type])
388           type = customEvents[type].type
389         if (!typeSpec || isString) {
390           // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
391           if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
392             namespaces = namespaces.split('.')
393           rm(element, type, fn, namespaces)
394         } else if (typeof typeSpec === 'function') {
395           // remove(el, fn)
396           rm(element, null, typeSpec)
397         } else {
398           // remove(el, { t1: fn1, t2, fn2 })
399           for (k in typeSpec) {
400             if (typeSpec.hasOwnProperty(k))
401               remove(element, k, typeSpec[k])
402           }
403         }
404         return element
405       }
406
407     , add = function (element, events, fn, delfn, $) {
408         var type, types, i, args
409           , originalFn = fn
410           , isDel = fn && typeof fn === 'string'
411
412         if (events && !fn && typeof events === 'object') {
413           for (type in events) {
414             if (events.hasOwnProperty(type))
415               add.apply(this, [ element, type, events[type] ])
416           }
417         } else {
418           args = arguments.length > 3 ? slice.call(arguments, 3) : []
419           types = (isDel ? fn : events).split(' ')
420           isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
421           // special case for one()
422           this === ONE && (fn = once(remove, element, events, fn, originalFn))
423           for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
424         }
425         return element
426       }
427
428     , one = function () {
429         return add.apply(ONE, arguments)
430       }
431
432     , fireListener = W3C_MODEL ? function (isNative, type, element) {
433         var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
434         evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
435         element.dispatchEvent(evt)
436       } : function (isNative, type, element) {
437         element = targetElement(element, isNative)
438         // if not-native then we're using onpropertychange so we just increment a custom property
439         isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
440       }
441
442     , fire = function (element, type, args) {
443         var i, j, l, names, handlers
444           , types = type.split(' ')
445
446         for (i = types.length; i--;) {
447           type = types[i].replace(nameRegex, '')
448           if (names = types[i].replace(namespaceRegex, ''))
449             names = names.split('.')
450           if (!names && !args && element[eventSupport]) {
451             fireListener(nativeEvents[type], type, element)
452           } else {
453             // non-native event, either because of a namespace, arguments or a non DOM element
454             // iterate over all listeners and manually 'fire'
455             handlers = registry.get(element, type)
456             args = [false].concat(args)
457             for (j = 0, l = handlers.length; j < l; j++) {
458               if (handlers[j].inNamespaces(names))
459                 handlers[j].handler.apply(element, args)
460             }
461           }
462         }
463         return element
464       }
465
466     , clone = function (element, from, type) {
467         var i = 0
468           , handlers = registry.get(from, type)
469           , l = handlers.length
470
471         for (;i < l; i++)
472           handlers[i].original && add(element, handlers[i].type, handlers[i].original)
473         return element
474       }
475
476     , bean = {
477           add: add
478         , one: one
479         , remove: remove
480         , clone: clone
481         , fire: fire
482         , noConflict: function () {
483             context[name] = old
484             return this
485           }
486       }
487
488   if (win[attachEvent]) {
489     // for IE, clean up on unload to avoid leaks
490     var cleanup = function () {
491       var i, entries = registry.entries()
492       for (i in entries) {
493         if (entries[i].type && entries[i].type !== 'unload')
494           remove(entries[i].element, entries[i].type)
495       }
496       win[detachEvent]('onunload', cleanup)
497       win.CollectGarbage && win.CollectGarbage()
498     }
499     win[attachEvent]('onunload', cleanup)
500   }
501
502   return bean
503 });
504 //     Underscore.js 1.1.7
505 //     (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
506 //     Underscore is freely distributable under the MIT license.
507 //     Portions of Underscore are inspired or borrowed from Prototype,
508 //     Oliver Steele's Functional, and John Resig's Micro-Templating.
509 //     For all details and documentation:
510 //     http://documentcloud.github.com/underscore
511
512 (function() {
513
514   // Baseline setup
515   // --------------
516
517   // Establish the root object, `window` in the browser, or `global` on the server.
518   var root = this;
519
520   // Save the previous value of the `_` variable.
521   var previousUnderscore = root._;
522
523   // Establish the object that gets returned to break out of a loop iteration.
524   var breaker = {};
525
526   // Save bytes in the minified (but not gzipped) version:
527   var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
528
529   // Create quick reference variables for speed access to core prototypes.
530   var slice            = ArrayProto.slice,
531       unshift          = ArrayProto.unshift,
532       toString         = ObjProto.toString,
533       hasOwnProperty   = ObjProto.hasOwnProperty;
534
535   // All **ECMAScript 5** native function implementations that we hope to use
536   // are declared here.
537   var
538     nativeForEach      = ArrayProto.forEach,
539     nativeMap          = ArrayProto.map,
540     nativeReduce       = ArrayProto.reduce,
541     nativeReduceRight  = ArrayProto.reduceRight,
542     nativeFilter       = ArrayProto.filter,
543     nativeEvery        = ArrayProto.every,
544     nativeSome         = ArrayProto.some,
545     nativeIndexOf      = ArrayProto.indexOf,
546     nativeLastIndexOf  = ArrayProto.lastIndexOf,
547     nativeIsArray      = Array.isArray,
548     nativeKeys         = Object.keys,
549     nativeBind         = FuncProto.bind;
550
551   // Create a safe reference to the Underscore object for use below.
552   var _ = function(obj) { return new wrapper(obj); };
553
554   // Export the Underscore object for **CommonJS**, with backwards-compatibility
555   // for the old `require()` API. If we're not in CommonJS, add `_` to the
556   // global object.
557   if (typeof module !== 'undefined' && module.exports) {
558     module.exports = _;
559     _._ = _;
560   } else {
561     // Exported as a string, for Closure Compiler "advanced" mode.
562     root['_'] = _;
563   }
564
565   // Current version.
566   _.VERSION = '1.1.7';
567
568   // Collection Functions
569   // --------------------
570
571   // The cornerstone, an `each` implementation, aka `forEach`.
572   // Handles objects with the built-in `forEach`, arrays, and raw objects.
573   // Delegates to **ECMAScript 5**'s native `forEach` if available.
574   var each = _.each = _.forEach = function(obj, iterator, context) {
575     if (obj == null) return;
576     if (nativeForEach && obj.forEach === nativeForEach) {
577       obj.forEach(iterator, context);
578     } else if (obj.length === +obj.length) {
579       for (var i = 0, l = obj.length; i < l; i++) {
580         if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
581       }
582     } else {
583       for (var key in obj) {
584         if (hasOwnProperty.call(obj, key)) {
585           if (iterator.call(context, obj[key], key, obj) === breaker) return;
586         }
587       }
588     }
589   };
590
591   // Return the results of applying the iterator to each element.
592   // Delegates to **ECMAScript 5**'s native `map` if available.
593   _.map = function(obj, iterator, context) {
594     var results = [];
595     if (obj == null) return results;
596     if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
597     each(obj, function(value, index, list) {
598       results[results.length] = iterator.call(context, value, index, list);
599     });
600     return results;
601   };
602
603   // **Reduce** builds up a single result from a list of values, aka `inject`,
604   // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
605   _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
606     var initial = memo !== void 0;
607     if (obj == null) obj = [];
608     if (nativeReduce && obj.reduce === nativeReduce) {
609       if (context) iterator = _.bind(iterator, context);
610       return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
611     }
612     each(obj, function(value, index, list) {
613       if (!initial) {
614         memo = value;
615         initial = true;
616       } else {
617         memo = iterator.call(context, memo, value, index, list);
618       }
619     });
620     if (!initial) throw new TypeError("Reduce of empty array with no initial value");
621     return memo;
622   };
623
624   // The right-associative version of reduce, also known as `foldr`.
625   // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
626   _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
627     if (obj == null) obj = [];
628     if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
629       if (context) iterator = _.bind(iterator, context);
630       return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
631     }
632     var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
633     return _.reduce(reversed, iterator, memo, context);
634   };
635
636   // Return the first value which passes a truth test. Aliased as `detect`.
637   _.find = _.detect = function(obj, iterator, context) {
638     var result;
639     any(obj, function(value, index, list) {
640       if (iterator.call(context, value, index, list)) {
641         result = value;
642         return true;
643       }
644     });
645     return result;
646   };
647
648   // Return all the elements that pass a truth test.
649   // Delegates to **ECMAScript 5**'s native `filter` if available.
650   // Aliased as `select`.
651   _.filter = _.select = function(obj, iterator, context) {
652     var results = [];
653     if (obj == null) return results;
654     if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
655     each(obj, function(value, index, list) {
656       if (iterator.call(context, value, index, list)) results[results.length] = value;
657     });
658     return results;
659   };
660
661   // Return all the elements for which a truth test fails.
662   _.reject = function(obj, iterator, context) {
663     var results = [];
664     if (obj == null) return results;
665     each(obj, function(value, index, list) {
666       if (!iterator.call(context, value, index, list)) results[results.length] = value;
667     });
668     return results;
669   };
670
671   // Determine whether all of the elements match a truth test.
672   // Delegates to **ECMAScript 5**'s native `every` if available.
673   // Aliased as `all`.
674   _.every = _.all = function(obj, iterator, context) {
675     var result = true;
676     if (obj == null) return result;
677     if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
678     each(obj, function(value, index, list) {
679       if (!(result = result && iterator.call(context, value, index, list))) return breaker;
680     });
681     return result;
682   };
683
684   // Determine if at least one element in the object matches a truth test.
685   // Delegates to **ECMAScript 5**'s native `some` if available.
686   // Aliased as `any`.
687   var any = _.some = _.any = function(obj, iterator, context) {
688     iterator = iterator || _.identity;
689     var result = false;
690     if (obj == null) return result;
691     if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
692     each(obj, function(value, index, list) {
693       if (result |= iterator.call(context, value, index, list)) return breaker;
694     });
695     return !!result;
696   };
697
698   // Determine if a given value is included in the array or object using `===`.
699   // Aliased as `contains`.
700   _.include = _.contains = function(obj, target) {
701     var found = false;
702     if (obj == null) return found;
703     if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
704     any(obj, function(value) {
705       if (found = value === target) return true;
706     });
707     return found;
708   };
709
710   // Invoke a method (with arguments) on every item in a collection.
711   _.invoke = function(obj, method) {
712     var args = slice.call(arguments, 2);
713     return _.map(obj, function(value) {
714       return (method.call ? method || value : value[method]).apply(value, args);
715     });
716   };
717
718   // Convenience version of a common use case of `map`: fetching a property.
719   _.pluck = function(obj, key) {
720     return _.map(obj, function(value){ return value[key]; });
721   };
722
723   // Return the maximum element or (element-based computation).
724   _.max = function(obj, iterator, context) {
725     if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
726     var result = {computed : -Infinity};
727     each(obj, function(value, index, list) {
728       var computed = iterator ? iterator.call(context, value, index, list) : value;
729       computed >= result.computed && (result = {value : value, computed : computed});
730     });
731     return result.value;
732   };
733
734   // Return the minimum element (or element-based computation).
735   _.min = function(obj, iterator, context) {
736     if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
737     var result = {computed : Infinity};
738     each(obj, function(value, index, list) {
739       var computed = iterator ? iterator.call(context, value, index, list) : value;
740       computed < result.computed && (result = {value : value, computed : computed});
741     });
742     return result.value;
743   };
744
745   // Sort the object's values by a criterion produced by an iterator.
746   _.sortBy = function(obj, iterator, context) {
747     return _.pluck(_.map(obj, function(value, index, list) {
748       return {
749         value : value,
750         criteria : iterator.call(context, value, index, list)
751       };
752     }).sort(function(left, right) {
753       var a = left.criteria, b = right.criteria;
754       return a < b ? -1 : a > b ? 1 : 0;
755     }), 'value');
756   };
757
758   // Groups the object's values by a criterion produced by an iterator
759   _.groupBy = function(obj, iterator) {
760     var result = {};
761     each(obj, function(value, index) {
762       var key = iterator(value, index);
763       (result[key] || (result[key] = [])).push(value);
764     });
765     return result;
766   };
767
768   // Use a comparator function to figure out at what index an object should
769   // be inserted so as to maintain order. Uses binary search.
770   _.sortedIndex = function(array, obj, iterator) {
771     iterator || (iterator = _.identity);
772     var low = 0, high = array.length;
773     while (low < high) {
774       var mid = (low + high) >> 1;
775       iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
776     }
777     return low;
778   };
779
780   // Safely convert anything iterable into a real, live array.
781   _.toArray = function(iterable) {
782     if (!iterable)                return [];
783     if (iterable.toArray)         return iterable.toArray();
784     if (_.isArray(iterable))      return slice.call(iterable);
785     if (_.isArguments(iterable))  return slice.call(iterable);
786     return _.values(iterable);
787   };
788
789   // Return the number of elements in an object.
790   _.size = function(obj) {
791     return _.toArray(obj).length;
792   };
793
794   // Array Functions
795   // ---------------
796
797   // Get the first element of an array. Passing **n** will return the first N
798   // values in the array. Aliased as `head`. The **guard** check allows it to work
799   // with `_.map`.
800   _.first = _.head = function(array, n, guard) {
801     return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
802   };
803
804   // Returns everything but the first entry of the array. Aliased as `tail`.
805   // Especially useful on the arguments object. Passing an **index** will return
806   // the rest of the values in the array from that index onward. The **guard**
807   // check allows it to work with `_.map`.
808   _.rest = _.tail = function(array, index, guard) {
809     return slice.call(array, (index == null) || guard ? 1 : index);
810   };
811
812   // Get the last element of an array.
813   _.last = function(array) {
814     return array[array.length - 1];
815   };
816
817   // Trim out all falsy values from an array.
818   _.compact = function(array) {
819     return _.filter(array, function(value){ return !!value; });
820   };
821
822   // Return a completely flattened version of an array.
823   _.flatten = function(array) {
824     return _.reduce(array, function(memo, value) {
825       if (_.isArray(value)) return memo.concat(_.flatten(value));
826       memo[memo.length] = value;
827       return memo;
828     }, []);
829   };
830
831   // Return a version of the array that does not contain the specified value(s).
832   _.without = function(array) {
833     return _.difference(array, slice.call(arguments, 1));
834   };
835
836   // Produce a duplicate-free version of the array. If the array has already
837   // been sorted, you have the option of using a faster algorithm.
838   // Aliased as `unique`.
839   _.uniq = _.unique = function(array, isSorted) {
840     return _.reduce(array, function(memo, el, i) {
841       if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
842       return memo;
843     }, []);
844   };
845
846   // Produce an array that contains the union: each distinct element from all of
847   // the passed-in arrays.
848   _.union = function() {
849     return _.uniq(_.flatten(arguments));
850   };
851
852   // Produce an array that contains every item shared between all the
853   // passed-in arrays. (Aliased as "intersect" for back-compat.)
854   _.intersection = _.intersect = function(array) {
855     var rest = slice.call(arguments, 1);
856     return _.filter(_.uniq(array), function(item) {
857       return _.every(rest, function(other) {
858         return _.indexOf(other, item) >= 0;
859       });
860     });
861   };
862
863   // Take the difference between one array and another.
864   // Only the elements present in just the first array will remain.
865   _.difference = function(array, other) {
866     return _.filter(array, function(value){ return !_.include(other, value); });
867   };
868
869   // Zip together multiple lists into a single array -- elements that share
870   // an index go together.
871   _.zip = function() {
872     var args = slice.call(arguments);
873     var length = _.max(_.pluck(args, 'length'));
874     var results = new Array(length);
875     for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
876     return results;
877   };
878
879   // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
880   // we need this function. Return the position of the first occurrence of an
881   // item in an array, or -1 if the item is not included in the array.
882   // Delegates to **ECMAScript 5**'s native `indexOf` if available.
883   // If the array is large and already in sort order, pass `true`
884   // for **isSorted** to use binary search.
885   _.indexOf = function(array, item, isSorted) {
886     if (array == null) return -1;
887     var i, l;
888     if (isSorted) {
889       i = _.sortedIndex(array, item);
890       return array[i] === item ? i : -1;
891     }
892     if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
893     for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
894     return -1;
895   };
896
897
898   // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
899   _.lastIndexOf = function(array, item) {
900     if (array == null) return -1;
901     if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
902     var i = array.length;
903     while (i--) if (array[i] === item) return i;
904     return -1;
905   };
906
907   // Generate an integer Array containing an arithmetic progression. A port of
908   // the native Python `range()` function. See
909   // [the Python documentation](http://docs.python.org/library/functions.html#range).
910   _.range = function(start, stop, step) {
911     if (arguments.length <= 1) {
912       stop = start || 0;
913       start = 0;
914     }
915     step = arguments[2] || 1;
916
917     var len = Math.max(Math.ceil((stop - start) / step), 0);
918     var idx = 0;
919     var range = new Array(len);
920
921     while(idx < len) {
922       range[idx++] = start;
923       start += step;
924     }
925
926     return range;
927   };
928
929   // Function (ahem) Functions
930   // ------------------
931
932   // Create a function bound to a given object (assigning `this`, and arguments,
933   // optionally). Binding with arguments is also known as `curry`.
934   // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
935   // We check for `func.bind` first, to fail fast when `func` is undefined.
936   _.bind = function(func, obj) {
937     if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
938     var args = slice.call(arguments, 2);
939     return function() {
940       return func.apply(obj, args.concat(slice.call(arguments)));
941     };
942   };
943
944   // Bind all of an object's methods to that object. Useful for ensuring that
945   // all callbacks defined on an object belong to it.
946   _.bindAll = function(obj) {
947     var funcs = slice.call(arguments, 1);
948     if (funcs.length == 0) funcs = _.functions(obj);
949     each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
950     return obj;
951   };
952
953   // Memoize an expensive function by storing its results.
954   _.memoize = function(func, hasher) {
955     var memo = {};
956     hasher || (hasher = _.identity);
957     return function() {
958       var key = hasher.apply(this, arguments);
959       return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
960     };
961   };
962
963   // Delays a function for the given number of milliseconds, and then calls
964   // it with the arguments supplied.
965   _.delay = function(func, wait) {
966     var args = slice.call(arguments, 2);
967     return setTimeout(function(){ return func.apply(func, args); }, wait);
968   };
969
970   // Defers a function, scheduling it to run after the current call stack has
971   // cleared.
972   _.defer = function(func) {
973     return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
974   };
975
976   // Internal function used to implement `_.throttle` and `_.debounce`.
977   var limit = function(func, wait, debounce) {
978     var timeout;
979     return function() {
980       var context = this, args = arguments;
981       var throttler = function() {
982         timeout = null;
983         func.apply(context, args);
984       };
985       if (debounce) clearTimeout(timeout);
986       if (debounce || !timeout) timeout = setTimeout(throttler, wait);
987     };
988   };
989
990   // Returns a function, that, when invoked, will only be triggered at most once
991   // during a given window of time.
992   _.throttle = function(func, wait) {
993     return limit(func, wait, false);
994   };
995
996   // Returns a function, that, as long as it continues to be invoked, will not
997   // be triggered. The function will be called after it stops being called for
998   // N milliseconds.
999   _.debounce = function(func, wait) {
1000     return limit(func, wait, true);
1001   };
1002
1003   // Returns a function that will be executed at most one time, no matter how
1004   // often you call it. Useful for lazy initialization.
1005   _.once = function(func) {
1006     var ran = false, memo;
1007     return function() {
1008       if (ran) return memo;
1009       ran = true;
1010       return memo = func.apply(this, arguments);
1011     };
1012   };
1013
1014   // Returns the first function passed as an argument to the second,
1015   // allowing you to adjust arguments, run code before and after, and
1016   // conditionally execute the original function.
1017   _.wrap = function(func, wrapper) {
1018     return function() {
1019       var args = [func].concat(slice.call(arguments));
1020       return wrapper.apply(this, args);
1021     };
1022   };
1023
1024   // Returns a function that is the composition of a list of functions, each
1025   // consuming the return value of the function that follows.
1026   _.compose = function() {
1027     var funcs = slice.call(arguments);
1028     return function() {
1029       var args = slice.call(arguments);
1030       for (var i = funcs.length - 1; i >= 0; i--) {
1031         args = [funcs[i].apply(this, args)];
1032       }
1033       return args[0];
1034     };
1035   };
1036
1037   // Returns a function that will only be executed after being called N times.
1038   _.after = function(times, func) {
1039     return function() {
1040       if (--times < 1) { return func.apply(this, arguments); }
1041     };
1042   };
1043
1044
1045   // Object Functions
1046   // ----------------
1047
1048   // Retrieve the names of an object's properties.
1049   // Delegates to **ECMAScript 5**'s native `Object.keys`
1050   _.keys = nativeKeys || function(obj) {
1051     if (obj !== Object(obj)) throw new TypeError('Invalid object');
1052     var keys = [];
1053     for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
1054     return keys;
1055   };
1056
1057   // Retrieve the values of an object's properties.
1058   _.values = function(obj) {
1059     return _.map(obj, _.identity);
1060   };
1061
1062   // Return a sorted list of the function names available on the object.
1063   // Aliased as `methods`
1064   _.functions = _.methods = function(obj) {
1065     var names = [];
1066     for (var key in obj) {
1067       if (_.isFunction(obj[key])) names.push(key);
1068     }
1069     return names.sort();
1070   };
1071
1072   // Extend a given object with all the properties in passed-in object(s).
1073   _.extend = function(obj) {
1074     each(slice.call(arguments, 1), function(source) {
1075       for (var prop in source) {
1076         if (source[prop] !== void 0) obj[prop] = source[prop];
1077       }
1078     });
1079     return obj;
1080   };
1081
1082   // Fill in a given object with default properties.
1083   _.defaults = function(obj) {
1084     each(slice.call(arguments, 1), function(source) {
1085       for (var prop in source) {
1086         if (obj[prop] == null) obj[prop] = source[prop];
1087       }
1088     });
1089     return obj;
1090   };
1091
1092   // Create a (shallow-cloned) duplicate of an object.
1093   _.clone = function(obj) {
1094     return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
1095   };
1096
1097   // Invokes interceptor with the obj, and then returns obj.
1098   // The primary purpose of this method is to "tap into" a method chain, in
1099   // order to perform operations on intermediate results within the chain.
1100   _.tap = function(obj, interceptor) {
1101     interceptor(obj);
1102     return obj;
1103   };
1104
1105   // Perform a deep comparison to check if two objects are equal.
1106   _.isEqual = function(a, b) {
1107     // Check object identity.
1108     if (a === b) return true;
1109     // Different types?
1110     var atype = typeof(a), btype = typeof(b);
1111     if (atype != btype) return false;
1112     // Basic equality test (watch out for coercions).
1113     if (a == b) return true;
1114     // One is falsy and the other truthy.
1115     if ((!a && b) || (a && !b)) return false;
1116     // Unwrap any wrapped objects.
1117     if (a._chain) a = a._wrapped;
1118     if (b._chain) b = b._wrapped;
1119     // One of them implements an isEqual()?
1120     if (a.isEqual) return a.isEqual(b);
1121     if (b.isEqual) return b.isEqual(a);
1122     // Check dates' integer values.
1123     if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
1124     // Both are NaN?
1125     if (_.isNaN(a) && _.isNaN(b)) return false;
1126     // Compare regular expressions.
1127     if (_.isRegExp(a) && _.isRegExp(b))
1128       return a.source     === b.source &&
1129              a.global     === b.global &&
1130              a.ignoreCase === b.ignoreCase &&
1131              a.multiline  === b.multiline;
1132     // If a is not an object by this point, we can't handle it.
1133     if (atype !== 'object') return false;
1134     // Check for different array lengths before comparing contents.
1135     if (a.length && (a.length !== b.length)) return false;
1136     // Nothing else worked, deep compare the contents.
1137     var aKeys = _.keys(a), bKeys = _.keys(b);
1138     // Different object sizes?
1139     if (aKeys.length != bKeys.length) return false;
1140     // Recursive comparison of contents.
1141     for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
1142     return true;
1143   };
1144
1145   // Is a given array or object empty?
1146   _.isEmpty = function(obj) {
1147     if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
1148     for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
1149     return true;
1150   };
1151
1152   // Is a given value a DOM element?
1153   _.isElement = function(obj) {
1154     return !!(obj && obj.nodeType == 1);
1155   };
1156
1157   // Is a given value an array?
1158   // Delegates to ECMA5's native Array.isArray
1159   _.isArray = nativeIsArray || function(obj) {
1160     return toString.call(obj) === '[object Array]';
1161   };
1162
1163   // Is a given variable an object?
1164   _.isObject = function(obj) {
1165     return obj === Object(obj);
1166   };
1167
1168   // Is a given variable an arguments object?
1169   _.isArguments = function(obj) {
1170     return !!(obj && hasOwnProperty.call(obj, 'callee'));
1171   };
1172
1173   // Is a given value a function?
1174   _.isFunction = function(obj) {
1175     return !!(obj && obj.constructor && obj.call && obj.apply);
1176   };
1177
1178   // Is a given value a string?
1179   _.isString = function(obj) {
1180     return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
1181   };
1182
1183   // Is a given value a number?
1184   _.isNumber = function(obj) {
1185     return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
1186   };
1187
1188   // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
1189   // that does not equal itself.
1190   _.isNaN = function(obj) {
1191     return obj !== obj;
1192   };
1193
1194   // Is a given value a boolean?
1195   _.isBoolean = function(obj) {
1196     return obj === true || obj === false;
1197   };
1198
1199   // Is a given value a date?
1200   _.isDate = function(obj) {
1201     return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
1202   };
1203
1204   // Is the given value a regular expression?
1205   _.isRegExp = function(obj) {
1206     return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
1207   };
1208
1209   // Is a given value equal to null?
1210   _.isNull = function(obj) {
1211     return obj === null;
1212   };
1213
1214   // Is a given variable undefined?
1215   _.isUndefined = function(obj) {
1216     return obj === void 0;
1217   };
1218
1219   // Utility Functions
1220   // -----------------
1221
1222   // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1223   // previous owner. Returns a reference to the Underscore object.
1224   _.noConflict = function() {
1225     root._ = previousUnderscore;
1226     return this;
1227   };
1228
1229   // Keep the identity function around for default iterators.
1230   _.identity = function(value) {
1231     return value;
1232   };
1233
1234   // Run a function **n** times.
1235   _.times = function (n, iterator, context) {
1236     for (var i = 0; i < n; i++) iterator.call(context, i);
1237   };
1238
1239   // Add your own custom functions to the Underscore object, ensuring that
1240   // they're correctly added to the OOP wrapper as well.
1241   _.mixin = function(obj) {
1242     each(_.functions(obj), function(name){
1243       addToWrapper(name, _[name] = obj[name]);
1244     });
1245   };
1246
1247   // Generate a unique integer id (unique within the entire client session).
1248   // Useful for temporary DOM ids.
1249   var idCounter = 0;
1250   _.uniqueId = function(prefix) {
1251     var id = idCounter++;
1252     return prefix ? prefix + id : id;
1253   };
1254
1255   // By default, Underscore uses ERB-style template delimiters, change the
1256   // following template settings to use alternative delimiters.
1257   _.templateSettings = {
1258     evaluate    : /<%([\s\S]+?)%>/g,
1259     interpolate : /<%=([\s\S]+?)%>/g
1260   };
1261
1262   // JavaScript micro-templating, similar to John Resig's implementation.
1263   // Underscore templating handles arbitrary delimiters, preserves whitespace,
1264   // and correctly escapes quotes within interpolated code.
1265   _.template = function(str, data) {
1266     var c  = _.templateSettings;
1267     var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
1268       'with(obj||{}){__p.push(\'' +
1269       str.replace(/\\/g, '\\\\')
1270          .replace(/'/g, "\\'")
1271          .replace(c.interpolate, function(match, code) {
1272            return "'," + code.replace(/\\'/g, "'") + ",'";
1273          })
1274          .replace(c.evaluate || null, function(match, code) {
1275            return "');" + code.replace(/\\'/g, "'")
1276                               .replace(/[\r\n\t]/g, ' ') + "__p.push('";
1277          })
1278          .replace(/\r/g, '\\r')
1279          .replace(/\n/g, '\\n')
1280          .replace(/\t/g, '\\t')
1281          + "');}return __p.join('');";
1282     var func = new Function('obj', tmpl);
1283     return data ? func(data) : func;
1284   };
1285
1286   // The OOP Wrapper
1287   // ---------------
1288
1289   // If Underscore is called as a function, it returns a wrapped object that
1290   // can be used OO-style. This wrapper holds altered versions of all the
1291   // underscore functions. Wrapped objects may be chained.
1292   var wrapper = function(obj) { this._wrapped = obj; };
1293
1294   // Expose `wrapper.prototype` as `_.prototype`
1295   _.prototype = wrapper.prototype;
1296
1297   // Helper function to continue chaining intermediate results.
1298   var result = function(obj, chain) {
1299     return chain ? _(obj).chain() : obj;
1300   };
1301
1302   // A method to easily add functions to the OOP wrapper.
1303   var addToWrapper = function(name, func) {
1304     wrapper.prototype[name] = function() {
1305       var args = slice.call(arguments);
1306       unshift.call(args, this._wrapped);
1307       return result(func.apply(_, args), this._chain);
1308     };
1309   };
1310
1311   // Add all of the Underscore functions to the wrapper object.
1312   _.mixin(_);
1313
1314   // Add all mutator Array functions to the wrapper.
1315   each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1316     var method = ArrayProto[name];
1317     wrapper.prototype[name] = function() {
1318       method.apply(this._wrapped, arguments);
1319       return result(this._wrapped, this._chain);
1320     };
1321   });
1322
1323   // Add all accessor Array functions to the wrapper.
1324   each(['concat', 'join', 'slice'], function(name) {
1325     var method = ArrayProto[name];
1326     wrapper.prototype[name] = function() {
1327       return result(method.apply(this._wrapped, arguments), this._chain);
1328     };
1329   });
1330
1331   // Start chaining a wrapped Underscore object.
1332   wrapper.prototype.chain = function() {
1333     this._chain = true;
1334     return this;
1335   };
1336
1337   // Extracts the result from a wrapped and chained object.
1338   wrapper.prototype.value = function() {
1339     return this._wrapped;
1340   };
1341
1342 })();
1343 /**
1344  * Flotr2 (c) 2012 Carl Sutherland
1345  * MIT License
1346  * Special thanks to:
1347  * Flotr: http://code.google.com/p/flotr/ (fork)
1348  * Flot: https://github.com/flot/flot (original fork)
1349  */
1350 (function () {
1351
1352 var
1353   global = this,
1354   previousFlotr = this.Flotr,
1355   Flotr;
1356
1357 Flotr = {
1358   _: _,
1359   bean: bean,
1360   isIphone: /iphone/i.test(navigator.userAgent),
1361   isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false),
1362   
1363   /**
1364    * An object of the registered graph types. Use Flotr.addType(type, object)
1365    * to add your own type.
1366    */
1367   graphTypes: {},
1368   
1369   /**
1370    * The list of the registered plugins
1371    */
1372   plugins: {},
1373   
1374   /**
1375    * Can be used to add your own chart type. 
1376    * @param {String} name - Type of chart, like 'pies', 'bars' etc.
1377    * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
1378    */
1379   addType: function(name, graphType){
1380     Flotr.graphTypes[name] = graphType;
1381     Flotr.defaultOptions[name] = graphType.options || {};
1382     Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
1383   },
1384   
1385   /**
1386    * Can be used to add a plugin
1387    * @param {String} name - The name of the plugin
1388    * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
1389    */
1390   addPlugin: function(name, plugin){
1391     Flotr.plugins[name] = plugin;
1392     Flotr.defaultOptions[name] = plugin.options || {};
1393   },
1394   
1395   /**
1396    * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
1397    * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
1398    * @param {Element} el - element to insert the graph into
1399    * @param {Object} data - an array or object of dataseries
1400    * @param {Object} options - an object containing options
1401    * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
1402    * @return {Object} returns a new graph object and of course draws the graph.
1403    */
1404   draw: function(el, data, options, GraphKlass){  
1405     GraphKlass = GraphKlass || Flotr.Graph;
1406     return new GraphKlass(el, data, options);
1407   },
1408   
1409   /**
1410    * Recursively merges two objects.
1411    * @param {Object} src - source object (likely the object with the least properties)
1412    * @param {Object} dest - destination object (optional, object with the most properties)
1413    * @return {Object} recursively merged Object
1414    * @TODO See if we can't remove this.
1415    */
1416   merge: function(src, dest){
1417     var i, v, result = dest || {};
1418
1419     for (i in src) {
1420       v = src[i];
1421       if (v && typeof(v) === 'object') {
1422         if (v.constructor === Array) {
1423           result[i] = this._.clone(v);
1424         } else if (
1425             v.constructor !== RegExp &&
1426             !this._.isElement(v) &&
1427             !v.jquery
1428         ) {
1429           result[i] = Flotr.merge(v, (dest ? dest[i] : undefined));
1430         } else {
1431           result[i] = v;
1432         }
1433       } else {
1434         result[i] = v;
1435       }
1436     }
1437
1438     return result;
1439   },
1440   
1441   /**
1442    * Recursively clones an object.
1443    * @param {Object} object - The object to clone
1444    * @return {Object} the clone
1445    * @TODO See if we can't remove this.
1446    */
1447   clone: function(object){
1448     return Flotr.merge(object, {});
1449   },
1450   
1451   /**
1452    * Function calculates the ticksize and returns it.
1453    * @param {Integer} noTicks - number of ticks
1454    * @param {Integer} min - lower bound integer value for the current axis
1455    * @param {Integer} max - upper bound integer value for the current axis
1456    * @param {Integer} decimals - number of decimals for the ticks
1457    * @return {Integer} returns the ticksize in pixels
1458    */
1459   getTickSize: function(noTicks, min, max, decimals){
1460     var delta = (max - min) / noTicks,
1461         magn = Flotr.getMagnitude(delta),
1462         tickSize = 10,
1463         norm = delta / magn; // Norm is between 1.0 and 10.0.
1464         
1465     if(norm < 1.5) tickSize = 1;
1466     else if(norm < 2.25) tickSize = 2;
1467     else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5);
1468     else if(norm < 7.5) tickSize = 5;
1469     
1470     return tickSize * magn;
1471   },
1472   
1473   /**
1474    * Default tick formatter.
1475    * @param {String, Integer} val - tick value integer
1476    * @param {Object} axisOpts - the axis' options
1477    * @return {String} formatted tick string
1478    */
1479   defaultTickFormatter: function(val, axisOpts){
1480     return val+'';
1481   },
1482   
1483   /**
1484    * Formats the mouse tracker values.
1485    * @param {Object} obj - Track value Object {x:..,y:..}
1486    * @return {String} Formatted track string
1487    */
1488   defaultTrackFormatter: function(obj){
1489     return '('+obj.x+', '+obj.y+')';
1490   }, 
1491   
1492   /**
1493    * Utility function to convert file size values in bytes to kB, MB, ...
1494    * @param value {Number} - The value to convert
1495    * @param precision {Number} - The number of digits after the comma (default: 2)
1496    * @param base {Number} - The base (default: 1000)
1497    */
1498   engineeringNotation: function(value, precision, base){
1499     var sizes =         ['Y','Z','E','P','T','G','M','k',''],
1500         fractionSizes = ['y','z','a','f','p','n','µ','m',''],
1501         total = sizes.length;
1502
1503     base = base || 1000;
1504     precision = Math.pow(10, precision || 2);
1505
1506     if (value === 0) return 0;
1507
1508     if (value > 1) {
1509       while (total-- && (value >= base)) value /= base;
1510     }
1511     else {
1512       sizes = fractionSizes;
1513       total = sizes.length;
1514       while (total-- && (value < 1)) value *= base;
1515     }
1516
1517     return (Math.round(value * precision) / precision) + sizes[total];
1518   },
1519   
1520   /**
1521    * Returns the magnitude of the input value.
1522    * @param {Integer, Float} x - integer or float value
1523    * @return {Integer, Float} returns the magnitude of the input value
1524    */
1525   getMagnitude: function(x){
1526     return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
1527   },
1528   toPixel: function(val){
1529     return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
1530   },
1531   toRad: function(angle){
1532     return -angle * (Math.PI/180);
1533   },
1534   floorInBase: function(n, base) {
1535     return base * Math.floor(n / base);
1536   },
1537   drawText: function(ctx, text, x, y, style) {
1538     if (!ctx.fillText) {
1539       ctx.drawText(text, x, y, style);
1540       return;
1541     }
1542     
1543     style = this._.extend({
1544       size: Flotr.defaultOptions.fontSize,
1545       color: '#000000',
1546       textAlign: 'left',
1547       textBaseline: 'bottom',
1548       weight: 1,
1549       angle: 0
1550     }, style);
1551     
1552     ctx.save();
1553     ctx.translate(x, y);
1554     ctx.rotate(style.angle);
1555     ctx.fillStyle = style.color;
1556     ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
1557     ctx.textAlign = style.textAlign;
1558     ctx.textBaseline = style.textBaseline;
1559     ctx.fillText(text, 0, 0);
1560     ctx.restore();
1561   },
1562   getBestTextAlign: function(angle, style) {
1563     style = style || {textAlign: 'center', textBaseline: 'middle'};
1564     angle += Flotr.getTextAngleFromAlign(style);
1565     
1566     if (Math.abs(Math.cos(angle)) > 10e-3) 
1567       style.textAlign    = (Math.cos(angle) > 0 ? 'right' : 'left');
1568     
1569     if (Math.abs(Math.sin(angle)) > 10e-3) 
1570       style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');
1571     
1572     return style;
1573   },
1574   alignTable: {
1575     'right middle' : 0,
1576     'right top'    : Math.PI/4,
1577     'center top'   : Math.PI/2,
1578     'left top'     : 3*(Math.PI/4),
1579     'left middle'  : Math.PI,
1580     'left bottom'  : -3*(Math.PI/4),
1581     'center bottom': -Math.PI/2,
1582     'right bottom' : -Math.PI/4,
1583     'center middle': 0
1584   },
1585   getTextAngleFromAlign: function(style) {
1586     return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
1587   },
1588   noConflict : function () {
1589     global.Flotr = previousFlotr;
1590     return this;
1591   }
1592 };
1593
1594 global.Flotr = Flotr;
1595
1596 })();
1597
1598 /**
1599  * Flotr Defaults
1600  */
1601 Flotr.defaultOptions = {
1602   colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
1603   ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping
1604   title: null,             // => The graph's title
1605   subtitle: null,          // => The graph's subtitle
1606   shadowSize: 4,           // => size of the 'fake' shadow
1607   defaultType: null,       // => default series type
1608   HtmlText: true,          // => wether to draw the text using HTML or on the canvas
1609   fontColor: '#545454',    // => default font color
1610   fontSize: 7.5,           // => canvas' text font size
1611   resolution: 1,           // => resolution of the graph, to have printer-friendly graphs !
1612   parseFloat: true,        // => whether to preprocess data for floats (ie. if input is string)
1613   preventDefault: true,    // => preventDefault by default for mobile events.  Turn off to enable scroll.
1614   xaxis: {
1615     ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
1616     minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
1617     showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
1618     showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
1619     labelsAngle: 0,        // => labels' angle, in degrees
1620     title: null,           // => axis title
1621     titleAngle: 0,         // => axis title's angle, in degrees
1622     noTicks: 5,            // => number of ticks for automagically generated ticks
1623     minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
1624     tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
1625     tickDecimals: null,    // => no. of decimals, null means auto
1626     min: null,             // => min. value to show, null means set automatically
1627     max: null,             // => max. value to show, null means set automatically
1628     autoscale: false,      // => Turns autoscaling on with true
1629     autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
1630     color: null,           // => color of the ticks
1631     mode: 'normal',        // => can be 'time' or 'normal'
1632     timeFormat: null,
1633     timeMode:'UTC',        // => For UTC time ('local' for local time).
1634     timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year)
1635     scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
1636     base: Math.E,
1637     titleAlign: 'center',
1638     margin: true           // => Turn off margins with false
1639   },
1640   x2axis: {},
1641   yaxis: {
1642     ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
1643     minorTicks: null,      // => format: either [1, 3] or [[1, 'a'], 3]
1644     showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
1645     showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
1646     labelsAngle: 0,        // => labels' angle, in degrees
1647     title: null,           // => axis title
1648     titleAngle: 90,        // => axis title's angle, in degrees
1649     noTicks: 5,            // => number of ticks for automagically generated ticks
1650     minorTickFreq: null,   // => number of minor ticks between major ticks for autogenerated ticks
1651     tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
1652     tickDecimals: null,    // => no. of decimals, null means auto
1653     min: null,             // => min. value to show, null means set automatically
1654     max: null,             // => max. value to show, null means set automatically
1655     autoscale: false,      // => Turns autoscaling on with true
1656     autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
1657     color: null,           // => The color of the ticks
1658     scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
1659     base: Math.E,
1660     titleAlign: 'center',
1661     margin: true           // => Turn off margins with false
1662   },
1663   y2axis: {
1664     titleAngle: 270
1665   },
1666   grid: {
1667     color: '#545454',      // => primary color used for outline and labels
1668     backgroundColor: null, // => null for transparent, else color
1669     backgroundImage: null, // => background image. String or object with src, left and top
1670     watermarkAlpha: 0.4,   // => 
1671     tickColor: '#DDDDDD',  // => color used for the ticks
1672     labelMargin: 3,        // => margin in pixels
1673     verticalLines: true,   // => whether to show gridlines in vertical direction
1674     minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir.
1675     horizontalLines: true, // => whether to show gridlines in horizontal direction
1676     minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir.
1677     outlineWidth: 1,       // => width of the grid outline/border in pixels
1678     outline : 'nsew',      // => walls of the outline to display
1679     circular: false        // => if set to true, the grid will be circular, must be used when radars are drawn
1680   },
1681   mouse: {
1682     track: false,          // => true to track the mouse, no tracking otherwise
1683     trackAll: false,
1684     position: 'se',        // => position of the value box (default south-east).  False disables.
1685     relative: false,       // => next to the mouse cursor
1686     trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
1687     margin: 5,             // => margin in pixels of the valuebox
1688     lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series
1689     trackDecimals: 1,      // => decimals for the track values
1690     sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value
1691     trackY: true,          // => whether or not to track the mouse in the y axis
1692     radius: 3,             // => radius of the track point
1693     fillColor: null,       // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
1694     fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill 
1695   }
1696 };
1697
1698 /**
1699  * Flotr Color
1700  */
1701
1702 (function () {
1703
1704 var
1705   _ = Flotr._;
1706
1707 // Constructor
1708 function Color (r, g, b, a) {
1709   this.rgba = ['r','g','b','a'];
1710   var x = 4;
1711   while(-1<--x){
1712     this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
1713   }
1714   this.normalize();
1715 }
1716
1717 // Constants
1718 var COLOR_NAMES = {
1719   aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],
1720   brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],
1721   darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],
1722   darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],
1723   darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],
1724   khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],
1725   lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],
1726   maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],
1727   violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]
1728 };
1729
1730 Color.prototype = {
1731   scale: function(rf, gf, bf, af){
1732     var x = 4;
1733     while (-1 < --x) {
1734       if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x];
1735     }
1736     return this.normalize();
1737   },
1738   alpha: function(alpha) {
1739     if (!_.isUndefined(alpha) && !_.isNull(alpha)) {
1740       this.a = alpha;
1741     }
1742     return this.normalize();
1743   },
1744   clone: function(){
1745     return new Color(this.r, this.b, this.g, this.a);
1746   },
1747   limit: function(val,minVal,maxVal){
1748     return Math.max(Math.min(val, maxVal), minVal);
1749   },
1750   normalize: function(){
1751     var limit = this.limit;
1752     this.r = limit(parseInt(this.r, 10), 0, 255);
1753     this.g = limit(parseInt(this.g, 10), 0, 255);
1754     this.b = limit(parseInt(this.b, 10), 0, 255);
1755     this.a = limit(this.a, 0, 1);
1756     return this;
1757   },
1758   distance: function(color){
1759     if (!color) return;
1760     color = new Color.parse(color);
1761     var dist = 0, x = 3;
1762     while(-1<--x){
1763       dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
1764     }
1765     return dist;
1766   },
1767   toString: function(){
1768     return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
1769   },
1770   contrast: function () {
1771     var
1772       test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
1773     return (test < 0.5 ? '#000000' : '#ffffff');
1774   }
1775 };
1776
1777 _.extend(Color, {
1778   /**
1779    * Parses a color string and returns a corresponding Color.
1780    * The different tests are in order of probability to improve speed.
1781    * @param {String, Color} str - string thats representing a color
1782    * @return {Color} returns a Color object or false
1783    */
1784   parse: function(color){
1785     if (color instanceof Color) return color;
1786
1787     var result;
1788
1789     // #a0b1c2
1790     if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
1791       return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
1792
1793     // rgb(num,num,num)
1794     if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
1795       return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
1796   
1797     // #fff
1798     if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
1799       return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
1800   
1801     // rgba(num,num,num,num)
1802     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)))
1803       return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
1804       
1805     // rgb(num%,num%,num%)
1806     if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
1807       return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
1808   
1809     // rgba(num%,num%,num%,num)
1810     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)))
1811       return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
1812
1813     // Otherwise, we're most likely dealing with a named color.
1814     var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase();
1815     if(name == 'transparent'){
1816       return new Color(255, 255, 255, 0);
1817     }
1818     return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
1819   },
1820
1821   /**
1822    * Process color and options into color style.
1823    */
1824   processColor: function(color, options) {
1825
1826     var opacity = options.opacity;
1827     if (!color) return 'rgba(0, 0, 0, 0)';
1828     if (color instanceof Color) return color.alpha(opacity).toString();
1829     if (_.isString(color)) return Color.parse(color).alpha(opacity).toString();
1830     
1831     var grad = color.colors ? color : {colors: color};
1832     
1833     if (!options.ctx) {
1834       if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
1835       return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString();
1836     }
1837     grad = _.extend({start: 'top', end: 'bottom'}, grad); 
1838     
1839     if (/top/i.test(grad.start))  options.x1 = 0;
1840     if (/left/i.test(grad.start)) options.y1 = 0;
1841     if (/bottom/i.test(grad.end)) options.x2 = 0;
1842     if (/right/i.test(grad.end))  options.y2 = 0;
1843
1844     var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
1845     for (i = 0; i < grad.colors.length; i++) {
1846       c = grad.colors[i];
1847       if (_.isArray(c)) {
1848         stop = c[0];
1849         c = c[1];
1850       }
1851       else stop = i / (grad.colors.length-1);
1852       gradient.addColorStop(stop, Color.parse(c).alpha(opacity));
1853     }
1854     return gradient;
1855   }
1856 });
1857
1858 Flotr.Color = Color;
1859
1860 })();
1861
1862 /**
1863  * Flotr Date
1864  */
1865 Flotr.Date = {
1866
1867   set : function (date, name, mode, value) {
1868     mode = mode || 'UTC';
1869     name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name;
1870     date[name](value);
1871   },
1872
1873   get : function (date, name, mode) {
1874     mode = mode || 'UTC';
1875     name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name;
1876     return date[name]();
1877   },
1878
1879   format: function(d, format, mode) {
1880     if (!d) return;
1881
1882     // We should maybe use an "official" date format spec, like PHP date() or ColdFusion 
1883     // http://fr.php.net/manual/en/function.date.php
1884     // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
1885     var
1886       get = this.get,
1887       tokens = {
1888         h: get(d, 'Hours', mode).toString(),
1889         H: leftPad(get(d, 'Hours', mode)),
1890         M: leftPad(get(d, 'Minutes', mode)),
1891         S: leftPad(get(d, 'Seconds', mode)),
1892         s: get(d, 'Milliseconds', mode),
1893         d: get(d, 'Date', mode).toString(),
1894         m: (get(d, 'Month', mode) + 1).toString(),
1895         y: get(d, 'FullYear', mode).toString(),
1896         b: Flotr.Date.monthNames[get(d, 'Month', mode)]
1897       };
1898
1899     function leftPad(n){
1900       n += '';
1901       return n.length == 1 ? "0" + n : n;
1902     }
1903     
1904     var r = [], c,
1905         escape = false;
1906     
1907     for (var i = 0; i < format.length; ++i) {
1908       c = format.charAt(i);
1909       
1910       if (escape) {
1911         r.push(tokens[c] || c);
1912         escape = false;
1913       }
1914       else if (c == "%")
1915         escape = true;
1916       else
1917         r.push(c);
1918     }
1919     return r.join('');
1920   },
1921   getFormat: function(time, span) {
1922     var tu = Flotr.Date.timeUnits;
1923          if (time < tu.second) return "%h:%M:%S.%s";
1924     else if (time < tu.minute) return "%h:%M:%S";
1925     else if (time < tu.day)    return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M";
1926     else if (time < tu.month)  return "%b %d";
1927     else if (time < tu.year)   return (span < tu.year) ? "%b" : "%b %y";
1928     else                       return "%y";
1929   },
1930   formatter: function (v, axis) {
1931     var
1932       options = axis.options,
1933       scale = Flotr.Date.timeUnits[options.timeUnit],
1934       d = new Date(v * scale);
1935
1936     // first check global format
1937     if (axis.options.timeFormat)
1938       return Flotr.Date.format(d, options.timeFormat, options.timeMode);
1939     
1940     var span = (axis.max - axis.min) * scale,
1941         t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit];
1942
1943     return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode);
1944   },
1945   generator: function(axis) {
1946
1947      var
1948       set       = this.set,
1949       get       = this.get,
1950       timeUnits = this.timeUnits,
1951       spec      = this.spec,
1952       options   = axis.options,
1953       mode      = options.timeMode,
1954       scale     = timeUnits[options.timeUnit],
1955       min       = axis.min * scale,
1956       max       = axis.max * scale,
1957       delta     = (max - min) / options.noTicks,
1958       ticks     = [],
1959       tickSize  = axis.tickSize,
1960       tickUnit,
1961       formatter, i;
1962
1963     // Use custom formatter or time tick formatter
1964     formatter = (options.tickFormatter === Flotr.defaultTickFormatter ?
1965       this.formatter : options.tickFormatter
1966     );
1967
1968     for (i = 0; i < spec.length - 1; ++i) {
1969       var d = spec[i][0] * timeUnits[spec[i][1]];
1970       if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize)
1971         break;
1972     }
1973     tickSize = spec[i][0];
1974     tickUnit = spec[i][1];
1975
1976     // special-case the possibility of several years
1977     if (tickUnit == "year") {
1978       tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0);
1979
1980       // Fix for 0.5 year case
1981       if (tickSize == 0.5) {
1982         tickUnit = "month";
1983         tickSize = 6;
1984       }
1985     }
1986
1987     axis.tickUnit = tickUnit;
1988     axis.tickSize = tickSize;
1989
1990     var step = tickSize * timeUnits[tickUnit];
1991     d = new Date(min);
1992
1993     function setTick (name) {
1994       set(d, name, mode, Flotr.floorInBase(
1995         get(d, name, mode), tickSize
1996       ));
1997     }
1998
1999     switch (tickUnit) {
2000       case "millisecond": setTick('Milliseconds'); break;
2001       case "second": setTick('Seconds'); break;
2002       case "minute": setTick('Minutes'); break;
2003       case "hour": setTick('Hours'); break;
2004       case "month": setTick('Month'); break;
2005       case "year": setTick('FullYear'); break;
2006     }
2007     
2008     // reset smaller components
2009     if (step >= timeUnits.second)  set(d, 'Milliseconds', mode, 0);
2010     if (step >= timeUnits.minute)  set(d, 'Seconds', mode, 0);
2011     if (step >= timeUnits.hour)    set(d, 'Minutes', mode, 0);
2012     if (step >= timeUnits.day)     set(d, 'Hours', mode, 0);
2013     if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1);
2014     if (step >= timeUnits.year)    set(d, 'Month', mode, 0);
2015
2016     var carry = 0, v = NaN, prev;
2017     do {
2018       prev = v;
2019       v = d.getTime();
2020       ticks.push({ v: v / scale, label: formatter(v / scale, axis) });
2021       if (tickUnit == "month") {
2022         if (tickSize < 1) {
2023           /* a bit complicated - we'll divide the month up but we need to take care of fractions
2024            so we don't end up in the middle of a day */
2025           set(d, 'Date', mode, 1);
2026           var start = d.getTime();
2027           set(d, 'Month', mode, get(d, 'Month', mode) + 1);
2028           var end = d.getTime();
2029           d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize);
2030           carry = get(d, 'Hours', mode);
2031           set(d, 'Hours', mode, 0);
2032         }
2033         else
2034           set(d, 'Month', mode, get(d, 'Month', mode) + tickSize);
2035       }
2036       else if (tickUnit == "year") {
2037         set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize);
2038       }
2039       else
2040         d.setTime(v + step);
2041
2042     } while (v < max && v != prev);
2043
2044     return ticks;
2045   },
2046   timeUnits: {
2047     millisecond: 1,
2048     second: 1000,
2049     minute: 1000 * 60,
2050     hour:   1000 * 60 * 60,
2051     day:    1000 * 60 * 60 * 24,
2052     month:  1000 * 60 * 60 * 24 * 30,
2053     year:   1000 * 60 * 60 * 24 * 365.2425
2054   },
2055   // the allowed tick sizes, after 1 year we use an integer algorithm
2056   spec: [
2057     [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], 
2058     [1, "second"],   [2, "second"],  [5, "second"], [10, "second"], [30, "second"], 
2059     [1, "minute"],   [2, "minute"],  [5, "minute"], [10, "minute"], [30, "minute"], 
2060     [1, "hour"],     [2, "hour"],    [4, "hour"],   [8, "hour"],    [12, "hour"],
2061     [1, "day"],      [2, "day"],     [3, "day"],
2062     [0.25, "month"], [0.5, "month"], [1, "month"],  [2, "month"],   [3, "month"], [6, "month"],
2063     [1, "year"]
2064   ],
2065   monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
2066 };
2067
2068 (function () {
2069
2070 var _ = Flotr._;
2071
2072 function getEl (el) {
2073   return (el && el.jquery) ? el[0] : el;
2074 }
2075
2076 Flotr.DOM = {
2077   addClass: function(element, name){
2078     element = getEl(element);
2079     var classList = (element.className ? element.className : '');
2080       if (_.include(classList.split(/\s+/g), name)) return;
2081     element.className = (classList ? classList + ' ' : '') + name;
2082   },
2083   /**
2084    * Create an element.
2085    */
2086   create: function(tag){
2087     return document.createElement(tag);
2088   },
2089   node: function(html) {
2090     var div = Flotr.DOM.create('div'), n;
2091     div.innerHTML = html;
2092     n = div.children[0];
2093     div.innerHTML = '';
2094     return n;
2095   },
2096   /**
2097    * Remove all children.
2098    */
2099   empty: function(element){
2100     element = getEl(element);
2101     element.innerHTML = '';
2102     /*
2103     if (!element) return;
2104     _.each(element.childNodes, function (e) {
2105       Flotr.DOM.empty(e);
2106       element.removeChild(e);
2107     });
2108     */
2109   },
2110   remove: function (element) {
2111     element = getEl(element);
2112     element.parentNode.removeChild(element);
2113   },
2114   hide: function(element){
2115     element = getEl(element);
2116     Flotr.DOM.setStyles(element, {display:'none'});
2117   },
2118   /**
2119    * Insert a child.
2120    * @param {Element} element
2121    * @param {Element|String} Element or string to be appended.
2122    */
2123   insert: function(element, child){
2124     element = getEl(element);
2125     if(_.isString(child))
2126       element.innerHTML += child;
2127     else if (_.isElement(child))
2128       element.appendChild(child);
2129   },
2130   // @TODO find xbrowser implementation
2131   opacity: function(element, opacity) {
2132     element = getEl(element);
2133     element.style.opacity = opacity;
2134   },
2135   position: function(element, p){
2136     element = getEl(element);
2137     if (!element.offsetParent)
2138       return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)};
2139
2140     p = this.position(element.offsetParent);
2141     p.left  += element.offsetLeft;
2142     p.top   += element.offsetTop;
2143     return p;
2144   },
2145   removeClass: function(element, name) {
2146     var classList = (element.className ? element.className : '');
2147     element = getEl(element);
2148     element.className = _.filter(classList.split(/\s+/g), function (c) {
2149       if (c != name) return true; }
2150     ).join(' ');
2151   },
2152   setStyles: function(element, o) {
2153     element = getEl(element);
2154     _.each(o, function (value, key) {
2155       element.style[key] = value;
2156     });
2157   },
2158   show: function(element){
2159     element = getEl(element);
2160     Flotr.DOM.setStyles(element, {display:''});
2161   },
2162   /**
2163    * Return element size.
2164    */
2165   size: function(element){
2166     element = getEl(element);
2167     return {
2168       height : element.offsetHeight,
2169       width : element.offsetWidth };
2170   }
2171 };
2172
2173 })();
2174
2175 /**
2176  * Flotr Event Adapter
2177  */
2178 (function () {
2179 var
2180   F = Flotr,
2181   bean = F.bean;
2182 F.EventAdapter = {
2183   observe: function(object, name, callback) {
2184     bean.add(object, name, callback);
2185     return this;
2186   },
2187   fire: function(object, name, args) {
2188     bean.fire(object, name, args);
2189     if (typeof(Prototype) != 'undefined')
2190       Event.fire(object, name, args);
2191     // @TODO Someone who uses mootools, add mootools adapter for existing applciations.
2192     return this;
2193   },
2194   stopObserving: function(object, name, callback) {
2195     bean.remove(object, name, callback);
2196     return this;
2197   },
2198   eventPointer: function(e) {
2199     if (!F._.isUndefined(e.touches) && e.touches.length > 0) {
2200       return {
2201         x : e.touches[0].pageX,
2202         y : e.touches[0].pageY
2203       };
2204     } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) {
2205       return {
2206         x : e.changedTouches[0].pageX,
2207         y : e.changedTouches[0].pageY
2208       };
2209     } else if (e.pageX || e.pageY) {
2210       return {
2211         x : e.pageX,
2212         y : e.pageY
2213       };
2214     } else if (e.clientX || e.clientY) {
2215       var
2216         d = document,
2217         b = d.body,
2218         de = d.documentElement;
2219       return {
2220         x: e.clientX + b.scrollLeft + de.scrollLeft,
2221         y: e.clientY + b.scrollTop + de.scrollTop
2222       };
2223     }
2224   }
2225 };
2226 })();
2227
2228 /**
2229  * Text Utilities
2230  */
2231 (function () {
2232
2233 var
2234   F = Flotr,
2235   D = F.DOM,
2236   _ = F._,
2237
2238 Text = function (o) {
2239   this.o = o;
2240 };
2241
2242 Text.prototype = {
2243
2244   dimensions : function (text, canvasStyle, htmlStyle, className) {
2245
2246     if (!text) return { width : 0, height : 0 };
2247     
2248     return (this.o.html) ?
2249       this.html(text, this.o.element, htmlStyle, className) : 
2250       this.canvas(text, canvasStyle);
2251   },
2252
2253   canvas : function (text, style) {
2254
2255     if (!this.o.textEnabled) return;
2256     style = style || {};
2257
2258     var
2259       metrics = this.measureText(text, style),
2260       width = metrics.width,
2261       height = style.size || F.defaultOptions.fontSize,
2262       angle = style.angle || 0,
2263       cosAngle = Math.cos(angle),
2264       sinAngle = Math.sin(angle),
2265       widthPadding = 2,
2266       heightPadding = 6,
2267       bounds;
2268
2269     bounds = {
2270       width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding,
2271       height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding
2272     };
2273
2274     return bounds;
2275   },
2276
2277   html : function (text, element, style, className) {
2278
2279     var div = D.create('div');
2280
2281     D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' });
2282     D.insert(div, '<div style="'+style+'" class="'+className+' flotr-dummy-div">' + text + '</div>');
2283     D.insert(this.o.element, div);
2284
2285     return D.size(div);
2286   },
2287
2288   measureText : function (text, style) {
2289
2290     var
2291       context = this.o.ctx,
2292       metrics;
2293
2294     if (!context.fillText || (F.isIphone && context.measure)) {
2295       return { width : context.measure(text, style)};
2296     }
2297
2298     style = _.extend({
2299       size: F.defaultOptions.fontSize,
2300       weight: 1,
2301       angle: 0
2302     }, style);
2303
2304     context.save();
2305     context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
2306     metrics = context.measureText(text);
2307     context.restore();
2308
2309     return metrics;
2310   }
2311 };
2312
2313 Flotr.Text = Text;
2314
2315 })();
2316
2317 /**
2318  * Flotr Graph class that plots a graph on creation.
2319  */
2320 (function () {
2321
2322 var
2323   D     = Flotr.DOM,
2324   E     = Flotr.EventAdapter,
2325   _     = Flotr._,
2326   flotr = Flotr;
2327 /**
2328  * Flotr Graph constructor.
2329  * @param {Element} el - element to insert the graph into
2330  * @param {Object} data - an array or object of dataseries
2331  * @param {Object} options - an object containing options
2332  */
2333 Graph = function(el, data, options){
2334 // Let's see if we can get away with out this [JS]
2335 //  try {
2336     this._setEl(el);
2337     this._initMembers();
2338     this._initPlugins();
2339
2340     E.fire(this.el, 'flotr:beforeinit', [this]);
2341
2342     this.data = data;
2343     this.series = flotr.Series.getSeries(data);
2344     this._initOptions(options);
2345     this._initGraphTypes();
2346     this._initCanvas();
2347     this._text = new flotr.Text({
2348       element : this.el,
2349       ctx : this.ctx,
2350       html : this.options.HtmlText,
2351       textEnabled : this.textEnabled
2352     });
2353     E.fire(this.el, 'flotr:afterconstruct', [this]);
2354     this._initEvents();
2355
2356     this.findDataRanges();
2357     this.calculateSpacing();
2358
2359     this.draw(_.bind(function() {
2360       E.fire(this.el, 'flotr:afterinit', [this]);
2361     }, this));
2362 /*
2363     try {
2364   } catch (e) {
2365     try {
2366       console.error(e);
2367     } catch (e2) {}
2368   }*/
2369 };
2370
2371 function observe (object, name, callback) {
2372   E.observe.apply(this, arguments);
2373   this._handles.push(arguments);
2374   return this;
2375 }
2376
2377 Graph.prototype = {
2378
2379   destroy: function () {
2380     E.fire(this.el, 'flotr:destroy');
2381     _.each(this._handles, function (handle) {
2382       E.stopObserving.apply(this, handle);
2383     });
2384     this._handles = [];
2385     this.el.graph = null;
2386   },
2387
2388   observe : observe,
2389
2390   /**
2391    * @deprecated
2392    */
2393   _observe : observe,
2394
2395   processColor: function(color, options){
2396     var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx };
2397     _.extend(o, options);
2398     return flotr.Color.processColor(color, o);
2399   },
2400   /**
2401    * Function determines the min and max values for the xaxis and yaxis.
2402    *
2403    * TODO logarithmic range validation (consideration of 0)
2404    */
2405   findDataRanges: function(){
2406     var a = this.axes,
2407       xaxis, yaxis, range;
2408
2409     _.each(this.series, function (series) {
2410       range = series.getRange();
2411       if (range) {
2412         xaxis = series.xaxis;
2413         yaxis = series.yaxis;
2414         xaxis.datamin = Math.min(range.xmin, xaxis.datamin);
2415         xaxis.datamax = Math.max(range.xmax, xaxis.datamax);
2416         yaxis.datamin = Math.min(range.ymin, yaxis.datamin);
2417         yaxis.datamax = Math.max(range.ymax, yaxis.datamax);
2418         xaxis.used = (xaxis.used || range.xused);
2419         yaxis.used = (yaxis.used || range.yused);
2420       }
2421     }, this);
2422
2423     // Check for empty data, no data case (none used)
2424     if (!a.x.used && !a.x2.used) a.x.used = true;
2425     if (!a.y.used && !a.y2.used) a.y.used = true;
2426
2427     _.each(a, function (axis) {
2428       axis.calculateRange();
2429     });
2430
2431     var
2432       types = _.keys(flotr.graphTypes),
2433       drawn = false;
2434
2435     _.each(this.series, function (series) {
2436       if (series.hide) return;
2437       _.each(types, function (type) {
2438         if (series[type] && series[type].show) {
2439           this.extendRange(type, series);
2440           drawn = true;
2441         }
2442       }, this);
2443       if (!drawn) {
2444         this.extendRange(this.options.defaultType, series);
2445       }
2446     }, this);
2447   },
2448
2449   extendRange : function (type, series) {
2450     if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]);
2451     if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]);
2452     if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]);
2453   },
2454
2455   /**
2456    * Calculates axis label sizes.
2457    */
2458   calculateSpacing: function(){
2459
2460     var a = this.axes,
2461         options = this.options,
2462         series = this.series,
2463         margin = options.grid.labelMargin,
2464         T = this._text,
2465         x = a.x,
2466         x2 = a.x2,
2467         y = a.y,
2468         y2 = a.y2,
2469         maxOutset = options.grid.outlineWidth,
2470         i, j, l, dim;
2471
2472     // TODO post refactor, fix this
2473     _.each(a, function (axis) {
2474       axis.calculateTicks();
2475       axis.calculateTextDimensions(T, options);
2476     });
2477
2478     // Title height
2479     dim = T.dimensions(
2480       options.title,
2481       {size: options.fontSize*1.5},
2482       'font-size:1em;font-weight:bold;',
2483       'flotr-title'
2484     );
2485     this.titleHeight = dim.height;
2486
2487     // Subtitle height
2488     dim = T.dimensions(
2489       options.subtitle,
2490       {size: options.fontSize},
2491       'font-size:smaller;',
2492       'flotr-subtitle'
2493     );
2494     this.subtitleHeight = dim.height;
2495
2496     for(j = 0; j < options.length; ++j){
2497       if (series[j].points.show){
2498         maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);
2499       }
2500     }
2501
2502     var p = this.plotOffset;
2503     if (x.options.margin === false) {
2504       p.bottom = 0;
2505       p.top    = 0;
2506     } else
2507     if (x.options.margin === true) {
2508       p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ?  (x.maxLabel.height + margin) : 0)) +
2509                   (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset;
2510
2511       p.top    += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) +
2512                   (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset;
2513     } else {
2514       p.bottom = x.options.margin;
2515       p.top = x.options.margin;
2516     }
2517     if (y.options.margin === false) {
2518       p.left  = 0;
2519       p.right = 0;
2520     } else
2521     if (y.options.margin === true) {
2522       p.left   += (options.grid.circular ? 0 : (y.used && y.options.showLabels ?  (y.maxLabel.width + margin) : 0)) +
2523                   (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset;
2524
2525       p.right  += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) +
2526                   (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset;
2527     } else {
2528       p.left = y.options.margin;
2529       p.right = y.options.margin;
2530     }
2531
2532     p.top = Math.floor(p.top); // In order the outline not to be blured
2533
2534     this.plotWidth  = this.canvasWidth - p.left - p.right;
2535     this.plotHeight = this.canvasHeight - p.bottom - p.top;
2536
2537     // TODO post refactor, fix this
2538     x.length = x2.length = this.plotWidth;
2539     y.length = y2.length = this.plotHeight;
2540     y.offset = y2.offset = this.plotHeight;
2541     x.setScale();
2542     x2.setScale();
2543     y.setScale();
2544     y2.setScale();
2545   },
2546   /**
2547    * Draws grid, labels, series and outline.
2548    */
2549   draw: function(after) {
2550
2551     var
2552       context = this.ctx,
2553       i;
2554
2555     E.fire(this.el, 'flotr:beforedraw', [this.series, this]);
2556
2557     if (this.series.length) {
2558
2559       context.save();
2560       context.translate(this.plotOffset.left, this.plotOffset.top);
2561
2562       for (i = 0; i < this.series.length; i++) {
2563         if (!this.series[i].hide) this.drawSeries(this.series[i]);
2564       }
2565
2566       context.restore();
2567       this.clip();
2568     }
2569
2570     E.fire(this.el, 'flotr:afterdraw', [this.series, this]);
2571     if (after) after();
2572   },
2573   /**
2574    * Actually draws the graph.
2575    * @param {Object} series - series to draw
2576    */
2577   drawSeries: function(series){
2578
2579     function drawChart (series, typeKey) {
2580       var options = this.getOptions(series, typeKey);
2581       this[typeKey].draw(options);
2582     }
2583
2584     var drawn = false;
2585     series = series || this.series;
2586
2587     _.each(flotr.graphTypes, function (type, typeKey) {
2588       if (series[typeKey] && series[typeKey].show && this[typeKey]) {
2589         drawn = true;
2590         drawChart.call(this, series, typeKey);
2591       }
2592     }, this);
2593
2594     if (!drawn) drawChart.call(this, series, this.options.defaultType);
2595   },
2596
2597   getOptions : function (series, typeKey) {
2598     var
2599       type = series[typeKey],
2600       graphType = this[typeKey],
2601       xaxis = series.xaxis,
2602       yaxis = series.yaxis,
2603       options = {
2604         context     : this.ctx,
2605         width       : this.plotWidth,
2606         height      : this.plotHeight,
2607         fontSize    : this.options.fontSize,
2608         fontColor   : this.options.fontColor,
2609         textEnabled : this.textEnabled,
2610         htmlText    : this.options.HtmlText,
2611         text        : this._text, // TODO Is this necessary?
2612         element     : this.el,
2613         data        : series.data,
2614         color       : series.color,
2615         shadowSize  : series.shadowSize,
2616         xScale      : xaxis.d2p,
2617         yScale      : yaxis.d2p,
2618         xInverse    : xaxis.p2d,
2619         yInverse    : yaxis.p2d
2620       };
2621
2622     options = flotr.merge(type, options);
2623
2624     // Fill
2625     options.fillStyle = this.processColor(
2626       type.fillColor || series.color,
2627       {opacity: type.fillOpacity}
2628     );
2629
2630     return options;
2631   },
2632   /**
2633    * Calculates the coordinates from a mouse event object.
2634    * @param {Event} event - Mouse Event object.
2635    * @return {Object} Object with coordinates of the mouse.
2636    */
2637   getEventPosition: function (e){
2638
2639     var
2640       d = document,
2641       b = d.body,
2642       de = d.documentElement,
2643       axes = this.axes,
2644       plotOffset = this.plotOffset,
2645       lastMousePos = this.lastMousePos,
2646       pointer = E.eventPointer(e),
2647       dx = pointer.x - lastMousePos.pageX,
2648       dy = pointer.y - lastMousePos.pageY,
2649       r, rx, ry;
2650
2651     if ('ontouchstart' in this.el) {
2652       r = D.position(this.overlay);
2653       rx = pointer.x - r.left - plotOffset.left;
2654       ry = pointer.y - r.top - plotOffset.top;
2655     } else {
2656       r = this.overlay.getBoundingClientRect();
2657       rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft;
2658       ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop;
2659     }
2660
2661     return {
2662       x:  axes.x.p2d(rx),
2663       x2: axes.x2.p2d(rx),
2664       y:  axes.y.p2d(ry),
2665       y2: axes.y2.p2d(ry),
2666       relX: rx,
2667       relY: ry,
2668       dX: dx,
2669       dY: dy,
2670       absX: pointer.x,
2671       absY: pointer.y,
2672       pageX: pointer.x,
2673       pageY: pointer.y
2674     };
2675   },
2676   /**
2677    * Observes the 'click' event and fires the 'flotr:click' event.
2678    * @param {Event} event - 'click' Event object.
2679    */
2680   clickHandler: function(event){
2681     if(this.ignoreClick){
2682       this.ignoreClick = false;
2683       return this.ignoreClick;
2684     }
2685     E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]);
2686   },
2687   /**
2688    * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event.
2689    * @param {Event} event - 'mousemove' Event object.
2690    */
2691   mouseMoveHandler: function(event){
2692     if (this.mouseDownMoveHandler) return;
2693     var pos = this.getEventPosition(event);
2694     E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
2695     this.lastMousePos = pos;
2696   },
2697   /**
2698    * Observes the 'mousedown' event.
2699    * @param {Event} event - 'mousedown' Event object.
2700    */
2701   mouseDownHandler: function (event){
2702
2703     /*
2704     // @TODO Context menu?
2705     if(event.isRightClick()) {
2706       event.stop();
2707
2708       var overlay = this.overlay;
2709       overlay.hide();
2710
2711       function cancelContextMenu () {
2712         overlay.show();
2713         E.stopObserving(document, 'mousemove', cancelContextMenu);
2714       }
2715       E.observe(document, 'mousemove', cancelContextMenu);
2716       return;
2717     }
2718     */
2719
2720     if (this.mouseUpHandler) return;
2721     this.mouseUpHandler = _.bind(function (e) {
2722       E.stopObserving(document, 'mouseup', this.mouseUpHandler);
2723       E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler);
2724       this.mouseDownMoveHandler = null;
2725       this.mouseUpHandler = null;
2726       // @TODO why?
2727       //e.stop();
2728       E.fire(this.el, 'flotr:mouseup', [e, this]);
2729     }, this);
2730     this.mouseDownMoveHandler = _.bind(function (e) {
2731         var pos = this.getEventPosition(e);
2732         E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
2733         this.lastMousePos = pos;
2734     }, this);
2735     E.observe(document, 'mouseup', this.mouseUpHandler);
2736     E.observe(document, 'mousemove', this.mouseDownMoveHandler);
2737     E.fire(this.el, 'flotr:mousedown', [event, this]);
2738     this.ignoreClick = false;
2739   },
2740   drawTooltip: function(content, x, y, options) {
2741     var mt = this.getMouseTrack(),
2742         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;',
2743         p = options.position,
2744         m = options.margin,
2745         plotOffset = this.plotOffset;
2746
2747     if(x !== null && y !== null){
2748       if (!options.relative) { // absolute to the canvas
2749              if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
2750         else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
2751              if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
2752         else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
2753       }
2754       else { // relative to the mouse
2755              if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;';
2756         else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;';
2757              if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;';
2758         else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;';
2759       }
2760
2761       mt.style.cssText = style;
2762       D.empty(mt);
2763       D.insert(mt, content);
2764       D.show(mt);
2765     }
2766     else {
2767       D.hide(mt);
2768     }
2769   },
2770
2771   clip: function (ctx) {
2772
2773     var
2774       o   = this.plotOffset,
2775       w   = this.canvasWidth,
2776       h   = this.canvasHeight;
2777
2778     ctx = ctx || this.ctx;
2779
2780     if (
2781       flotr.isIE && flotr.isIE < 9 && // IE w/o canvas
2782       !flotr.isFlashCanvas // But not flash canvas
2783     ) {
2784
2785       // Do not clip excanvas on overlay context
2786       // Allow hits to overflow.
2787       if (ctx === this.octx) {
2788         return;
2789       }
2790
2791       // Clipping for excanvas :-(
2792       ctx.save();
2793       ctx.fillStyle = this.processColor(this.options.ieBackgroundColor);
2794       ctx.fillRect(0, 0, w, o.top);
2795       ctx.fillRect(0, 0, o.left, h);
2796       ctx.fillRect(0, h - o.bottom, w, o.bottom);
2797       ctx.fillRect(w - o.right, 0, o.right,h);
2798       ctx.restore();
2799     } else {
2800       ctx.clearRect(0, 0, w, o.top);
2801       ctx.clearRect(0, 0, o.left, h);
2802       ctx.clearRect(0, h - o.bottom, w, o.bottom);
2803       ctx.clearRect(w - o.right, 0, o.right,h);
2804     }
2805   },
2806
2807   _initMembers: function() {
2808     this._handles = [];
2809     this.lastMousePos = {pageX: null, pageY: null };
2810     this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};
2811     this.ignoreClick = true;
2812     this.prevHit = null;
2813   },
2814
2815   _initGraphTypes: function() {
2816     _.each(flotr.graphTypes, function(handler, graphType){
2817       this[graphType] = flotr.clone(handler);
2818     }, this);
2819   },
2820
2821   _initEvents: function () {
2822
2823     var
2824       el = this.el,
2825       touchendHandler, movement, touchend;
2826
2827     if ('ontouchstart' in el) {
2828
2829       touchendHandler = _.bind(function (e) {
2830         touchend = true;
2831         E.stopObserving(document, 'touchend', touchendHandler);
2832         E.fire(el, 'flotr:mouseup', [event, this]);
2833         this.multitouches = null;
2834
2835         if (!movement) {
2836           this.clickHandler(e);
2837         }
2838       }, this);
2839
2840       this.observe(this.overlay, 'touchstart', _.bind(function (e) {
2841         movement = false;
2842         touchend = false;
2843         this.ignoreClick = false;
2844
2845         if (e.touches && e.touches.length > 1) {
2846           this.multitouches = e.touches;
2847         }
2848
2849         E.fire(el, 'flotr:mousedown', [event, this]);
2850         this.observe(document, 'touchend', touchendHandler);
2851       }, this));
2852
2853       this.observe(this.overlay, 'touchmove', _.bind(function (e) {
2854
2855         var pos = this.getEventPosition(e);
2856
2857         if (this.options.preventDefault) {
2858           e.preventDefault();
2859         }
2860
2861         movement = true;
2862
2863         if (this.multitouches || (e.touches && e.touches.length > 1)) {
2864           this.multitouches = e.touches;
2865         } else {
2866           if (!touchend) {
2867             E.fire(el, 'flotr:mousemove', [event, pos, this]);
2868           }
2869         }
2870         this.lastMousePos = pos;
2871       }, this));
2872
2873     } else {
2874       this.
2875         observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)).
2876         observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)).
2877         observe(this.overlay, 'click', _.bind(this.clickHandler, this)).
2878         observe(el, 'mouseout', function (e) {
2879           E.fire(el, 'flotr:mouseout', e);
2880         });
2881     }
2882   },
2883
2884   /**
2885    * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use
2886    * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements
2887    * are created, the elements are inserted into the container element.
2888    */
2889   _initCanvas: function(){
2890     var el = this.el,
2891       o = this.options,
2892       children = el.children,
2893       removedChildren = [],
2894       child, i,
2895       size, style;
2896
2897     // Empty the el
2898     for (i = children.length; i--;) {
2899       child = children[i];
2900       if (!this.canvas && child.className === 'flotr-canvas') {
2901         this.canvas = child;
2902       } else if (!this.overlay && child.className === 'flotr-overlay') {
2903         this.overlay = child;
2904       } else {
2905         removedChildren.push(child);
2906       }
2907     }
2908     for (i = removedChildren.length; i--;) {
2909       el.removeChild(removedChildren[i]);
2910     }
2911
2912     D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay.
2913     size = {};
2914     size.width = el.clientWidth;
2915     size.height = el.clientHeight;
2916
2917     if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){
2918       throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution;
2919     }
2920
2921     // Main canvas for drawing graph types
2922     this.canvas = getCanvas(this.canvas, 'canvas');
2923     // Overlay canvas for interactive features
2924     this.overlay = getCanvas(this.overlay, 'overlay');
2925     this.ctx = getContext(this.canvas);
2926     this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
2927     this.octx = getContext(this.overlay);
2928     this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height);
2929     this.canvasHeight = size.height;
2930     this.canvasWidth = size.width;
2931     this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions
2932
2933     function getCanvas(canvas, name){
2934       if(!canvas){
2935         canvas = D.create('canvas');
2936         if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') {
2937           FlashCanvas.initElement(canvas);
2938           this.isFlashCanvas = true;
2939         }
2940         canvas.className = 'flotr-'+name;
2941         canvas.style.cssText = 'position:absolute;left:0px;top:0px;';
2942         D.insert(el, canvas);
2943       }
2944       _.each(size, function(size, attribute){
2945         D.show(canvas);
2946         if (name == 'canvas' && canvas.getAttribute(attribute) === size) {
2947           return;
2948         }
2949         canvas.setAttribute(attribute, size * o.resolution);
2950         canvas.style[attribute] = size + 'px';
2951       });
2952       canvas.context_ = null; // Reset the ExCanvas context
2953       return canvas;
2954     }
2955
2956     function getContext(canvas){
2957       if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas
2958       var context = canvas.getContext('2d');
2959       if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution);
2960       return context;
2961     }
2962   },
2963
2964   _initPlugins: function(){
2965     // TODO Should be moved to flotr and mixed in.
2966     _.each(flotr.plugins, function(plugin, name){
2967       _.each(plugin.callbacks, function(fn, c){
2968         this.observe(this.el, c, _.bind(fn, this));
2969       }, this);
2970       this[name] = flotr.clone(plugin);
2971       _.each(this[name], function(fn, p){
2972         if (_.isFunction(fn))
2973           this[name][p] = _.bind(fn, this);
2974       }, this);
2975     }, this);
2976   },
2977
2978   /**
2979    * Sets options and initializes some variables and color specific values, used by the constructor.
2980    * @param {Object} opts - options object
2981    */
2982   _initOptions: function(opts){
2983     var options = flotr.clone(flotr.defaultOptions);
2984     options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis);
2985     options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis);
2986     this.options = flotr.merge(opts || {}, options);
2987
2988     if (this.options.grid.minorVerticalLines === null &&
2989       this.options.xaxis.scaling === 'logarithmic') {
2990       this.options.grid.minorVerticalLines = true;
2991     }
2992     if (this.options.grid.minorHorizontalLines === null &&
2993       this.options.yaxis.scaling === 'logarithmic') {
2994       this.options.grid.minorHorizontalLines = true;
2995     }
2996
2997     E.fire(this.el, 'flotr:afterinitoptions', [this]);
2998
2999     this.axes = flotr.Axis.getAxes(this.options);
3000
3001     // Initialize some variables used throughout this function.
3002     var assignedColors = [],
3003         colors = [],
3004         ln = this.series.length,
3005         neededColors = this.series.length,
3006         oc = this.options.colors,
3007         usedColors = [],
3008         variation = 0,
3009         c, i, j, s;
3010
3011     // Collect user-defined colors from series.
3012     for(i = neededColors - 1; i > -1; --i){
3013       c = this.series[i].color;
3014       if(c){
3015         --neededColors;
3016         if(_.isNumber(c)) assignedColors.push(c);
3017         else usedColors.push(flotr.Color.parse(c));
3018       }
3019     }
3020
3021     // Calculate the number of colors that need to be generated.
3022     for(i = assignedColors.length - 1; i > -1; --i)
3023       neededColors = Math.max(neededColors, assignedColors[i] + 1);
3024
3025     // Generate needed number of colors.
3026     for(i = 0; colors.length < neededColors;){
3027       c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]);
3028
3029       // Make sure each serie gets a different color.
3030       var sign = variation % 2 == 1 ? -1 : 1,
3031           factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
3032       c.scale(factor, factor, factor);
3033
3034       /**
3035        * @todo if we're getting too close to something else, we should probably skip this one
3036        */
3037       colors.push(c);
3038
3039       if(++i >= oc.length){
3040         i = 0;
3041         ++variation;
3042       }
3043     }
3044
3045     // Fill the options with the generated colors.
3046     for(i = 0, j = 0; i < ln; ++i){
3047       s = this.series[i];
3048
3049       // Assign the color.
3050       if (!s.color){
3051         s.color = colors[j++].toString();
3052       }else if(_.isNumber(s.color)){
3053         s.color = colors[s.color].toString();
3054       }
3055
3056       // Every series needs an axis
3057       if (!s.xaxis) s.xaxis = this.axes.x;
3058            if (s.xaxis == 1) s.xaxis = this.axes.x;
3059       else if (s.xaxis == 2) s.xaxis = this.axes.x2;
3060
3061       if (!s.yaxis) s.yaxis = this.axes.y;
3062            if (s.yaxis == 1) s.yaxis = this.axes.y;
3063       else if (s.yaxis == 2) s.yaxis = this.axes.y2;
3064
3065       // Apply missing options to the series.
3066       for (var t in flotr.graphTypes){
3067         s[t] = _.extend(_.clone(this.options[t]), s[t]);
3068       }
3069       s.mouse = _.extend(_.clone(this.options.mouse), s.mouse);
3070
3071       if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize;
3072     }
3073   },
3074
3075   _setEl: function(el) {
3076     if (!el) throw 'The target container doesn\'t exist';
3077     else if (el.graph instanceof Graph) el.graph.destroy();
3078     else if (!el.clientWidth) throw 'The target container must be visible';
3079
3080     el.graph = this;
3081     this.el = el;
3082   }
3083 };
3084
3085 Flotr.Graph = Graph;
3086
3087 })();
3088
3089 /**
3090  * Flotr Axis Library
3091  */
3092
3093 (function () {
3094
3095 var
3096   _ = Flotr._,
3097   LOGARITHMIC = 'logarithmic';
3098
3099 function Axis (o) {
3100
3101   this.orientation = 1;
3102   this.offset = 0;
3103   this.datamin = Number.MAX_VALUE;
3104   this.datamax = -Number.MAX_VALUE;
3105
3106   _.extend(this, o);
3107 }
3108
3109
3110 // Prototype
3111 Axis.prototype = {
3112
3113   setScale : function () {
3114     var
3115       length = this.length,
3116       max = this.max,
3117       min = this.min,
3118       offset = this.offset,
3119       orientation = this.orientation,
3120       options = this.options,
3121       logarithmic = options.scaling === LOGARITHMIC,
3122       scale;
3123
3124     if (logarithmic) {
3125       scale = length / (log(max, options.base) - log(min, options.base));
3126     } else {
3127       scale = length / (max - min);
3128     }
3129     this.scale = scale;
3130
3131     // Logarithmic?
3132     if (logarithmic) {
3133       this.d2p = function (dataValue) {
3134         return offset + orientation * (log(dataValue, options.base) - log(min, options.base)) * scale;
3135       };
3136       this.p2d = function (pointValue) {
3137         return exp((offset + orientation * pointValue) / scale + log(min, options.base), options.base);
3138       };
3139     } else {
3140       this.d2p = function (dataValue) {
3141         return offset + orientation * (dataValue - min) * scale;
3142       };
3143       this.p2d = function (pointValue) {
3144         return (offset + orientation * pointValue) / scale + min;
3145       };
3146     }
3147   },
3148
3149   calculateTicks : function () {
3150     var options = this.options;
3151
3152     this.ticks = [];
3153     this.minorTicks = [];
3154     
3155     // User Ticks
3156     if(options.ticks){
3157       this._cleanUserTicks(options.ticks, this.ticks);
3158       this._cleanUserTicks(options.minorTicks || [], this.minorTicks);
3159     }
3160     else {
3161       if (options.mode == 'time') {
3162         this._calculateTimeTicks();
3163       } else if (options.scaling === 'logarithmic') {
3164         this._calculateLogTicks();
3165       } else {
3166         this._calculateTicks();
3167       }
3168     }
3169
3170     // Ticks to strings
3171     _.each(this.ticks, function (tick) { tick.label += ''; });
3172     _.each(this.minorTicks, function (tick) { tick.label += ''; });
3173   },
3174
3175   /**
3176    * Calculates the range of an axis to apply autoscaling.
3177    */
3178   calculateRange: function () {
3179
3180     if (!this.used) return;
3181
3182     var axis  = this,
3183       o       = axis.options,
3184       min     = o.min !== null ? o.min : axis.datamin,
3185       max     = o.max !== null ? o.max : axis.datamax,
3186       margin  = o.autoscaleMargin;
3187         
3188     if (o.scaling == 'logarithmic') {
3189       if (min <= 0) min = axis.datamin;
3190
3191       // Let it widen later on
3192       if (max <= 0) max = min;
3193     }
3194
3195     if (max == min) {
3196       var widen = max ? 0.01 : 1.00;
3197       if (o.min === null) min -= widen;
3198       if (o.max === null) max += widen;
3199     }
3200
3201     if (o.scaling === 'logarithmic') {
3202       if (min < 0) min = max / o.base;  // Could be the result of widening
3203
3204       var maxexp = Math.log(max);
3205       if (o.base != Math.E) maxexp /= Math.log(o.base);
3206       maxexp = Math.ceil(maxexp);
3207
3208       var minexp = Math.log(min);
3209       if (o.base != Math.E) minexp /= Math.log(o.base);
3210       minexp = Math.ceil(minexp);
3211       
3212       axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals);
3213                         
3214       // Try to determine a suitable amount of miniticks based on the length of a decade
3215       if (o.minorTickFreq === null) {
3216         if (maxexp - minexp > 10)
3217           o.minorTickFreq = 0;
3218         else if (maxexp - minexp > 5)
3219           o.minorTickFreq = 2;
3220         else
3221           o.minorTickFreq = 5;
3222       }
3223     } else {
3224       axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
3225     }
3226
3227     axis.min = min;
3228     axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled
3229
3230     // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it
3231     if(o.min === null && o.autoscale){
3232       axis.min -= axis.tickSize * margin;
3233       // Make sure we don't go below zero if all values are positive.
3234       if(axis.min < 0 && axis.datamin >= 0) axis.min = 0;
3235       axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize);
3236     }
3237     
3238     if(o.max === null && o.autoscale){
3239       axis.max += axis.tickSize * margin;
3240       if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0;        
3241       axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize);
3242     }
3243
3244     if (axis.min == axis.max) axis.max = axis.min + 1;
3245   },
3246
3247   calculateTextDimensions : function (T, options) {
3248
3249     var maxLabel = '',
3250       length,
3251       i;
3252
3253     if (this.options.showLabels) {
3254       for (i = 0; i < this.ticks.length; ++i) {
3255         length = this.ticks[i].label.length;
3256         if (length > maxLabel.length){
3257           maxLabel = this.ticks[i].label;
3258         }
3259       }
3260     }
3261
3262     this.maxLabel = T.dimensions(
3263       maxLabel,
3264       {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)},
3265       'font-size:smaller;',
3266       'flotr-grid-label'
3267     );
3268
3269     this.titleSize = T.dimensions(
3270       this.options.title, 
3271       {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)},
3272       'font-weight:bold;',
3273       'flotr-axis-title'
3274     );
3275   },
3276
3277   _cleanUserTicks : function (ticks, axisTicks) {
3278
3279     var axis = this, options = this.options,
3280       v, i, label, tick;
3281
3282     if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max});
3283
3284     for(i = 0; i < ticks.length; ++i){
3285       tick = ticks[i];
3286       if(typeof(tick) === 'object'){
3287         v = tick[0];
3288         label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max});
3289       } else {
3290         v = tick;
3291         label = options.tickFormatter(v, {min : this.min, max : this.max});
3292       }
3293       axisTicks[i] = { v: v, label: label };
3294     }
3295   },
3296
3297   _calculateTimeTicks : function () {
3298     this.ticks = Flotr.Date.generator(this);
3299   },
3300
3301   _calculateLogTicks : function () {
3302
3303     var axis = this,
3304       o = axis.options,
3305       v,
3306       decadeStart;
3307
3308     var max = Math.log(axis.max);
3309     if (o.base != Math.E) max /= Math.log(o.base);
3310     max = Math.ceil(max);
3311
3312     var min = Math.log(axis.min);
3313     if (o.base != Math.E) min /= Math.log(o.base);
3314     min = Math.ceil(min);
3315     
3316     for (i = min; i < max; i += axis.tickSize) {
3317       decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
3318       // Next decade begins here:
3319       var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize));
3320       var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq;
3321       
3322       axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
3323       for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize)
3324         axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})});
3325     }
3326     
3327     // Always show the value at the would-be start of next decade (end of this decade)
3328     decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
3329     axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
3330   },
3331
3332   _calculateTicks : function () {
3333
3334     var axis      = this,
3335         o         = axis.options,
3336         tickSize  = axis.tickSize,
3337         min       = axis.min,
3338         max       = axis.max,
3339         start     = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size.
3340         decimals,
3341         minorTickSize,
3342         v, v2,
3343         i, j;
3344     
3345     if (o.minorTickFreq)
3346       minorTickSize = tickSize / o.minorTickFreq;
3347                       
3348     // Then store all possible ticks.
3349     for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){
3350       
3351       // Round (this is always needed to fix numerical instability).
3352       decimals = o.tickDecimals;
3353       if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10);
3354       if (decimals < 0) decimals = 0;
3355       
3356       v = v.toFixed(decimals);
3357       axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
3358
3359       if (o.minorTickFreq) {
3360         for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) {
3361           v = v2 + j * minorTickSize;
3362           axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
3363         }
3364       }
3365     }
3366
3367   }
3368 };
3369
3370
3371 // Static Methods
3372 _.extend(Axis, {
3373   getAxes : function (options) {
3374     return {
3375       x:  new Axis({options: options.xaxis,  n: 1, length: this.plotWidth}),
3376       x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}),
3377       y:  new Axis({options: options.yaxis,  n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}),
3378       y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1})
3379     };
3380   }
3381 });
3382
3383
3384 // Helper Methods
3385
3386
3387 function log (value, base) {
3388   value = Math.log(Math.max(value, Number.MIN_VALUE));
3389   if (base !== Math.E) 
3390     value /= Math.log(base);
3391   return value;
3392 }
3393
3394 function exp (value, base) {
3395   return (base === Math.E) ? Math.exp(value) : Math.pow(base, value);
3396 }
3397
3398 Flotr.Axis = Axis;
3399
3400 })();
3401
3402 /**
3403  * Flotr Series Library
3404  */
3405
3406 (function () {
3407
3408 var
3409   _ = Flotr._;
3410
3411 function Series (o) {
3412   _.extend(this, o);
3413 }
3414
3415 Series.prototype = {
3416
3417   getRange: function () {
3418
3419     var
3420       data = this.data,
3421       length = data.length,
3422       xmin = Number.MAX_VALUE,
3423       ymin = Number.MAX_VALUE,
3424       xmax = -Number.MAX_VALUE,
3425       ymax = -Number.MAX_VALUE,
3426       xused = false,
3427       yused = false,
3428       x, y, i;
3429
3430     if (length < 0 || this.hide) return false;
3431
3432     for (i = 0; i < length; i++) {
3433       x = data[i][0];
3434       y = data[i][1];
3435       if (x !== null) {
3436         if (x < xmin) { xmin = x; xused = true; }
3437         if (x > xmax) { xmax = x; xused = true; }
3438       }
3439       if (y !== null) {
3440         if (y < ymin) { ymin = y; yused = true; }
3441         if (y > ymax) { ymax = y; yused = true; }
3442       }
3443     }
3444
3445     return {
3446       xmin : xmin,
3447       xmax : xmax,
3448       ymin : ymin,
3449       ymax : ymax,
3450       xused : xused,
3451       yused : yused
3452     };
3453   }
3454 };
3455
3456 _.extend(Series, {
3457   /**
3458    * Collects dataseries from input and parses the series into the right format. It returns an Array 
3459    * of Objects each having at least the 'data' key set.
3460    * @param {Array, Object} data - Object or array of dataseries
3461    * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})
3462    */
3463   getSeries: function(data){
3464     return _.map(data, function(s){
3465       var series;
3466       if (s.data) {
3467         series = new Series();
3468         _.extend(series, s);
3469       } else {
3470         series = new Series({data:s});
3471       }
3472       return series;
3473     });
3474   }
3475 });
3476
3477 Flotr.Series = Series;
3478
3479 })();
3480
3481 /** Lines **/
3482 Flotr.addType('lines', {
3483   options: {
3484     show: false,           // => setting to true will show lines, false will hide
3485     lineWidth: 2,          // => line width in pixels
3486     fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill
3487     fillBorder: false,     // => draw a border around the fill
3488     fillColor: null,       // => fill color
3489     fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
3490     steps: false,          // => draw steps
3491     stacked: false         // => setting to true will show stacked lines, false will show normal lines
3492   },
3493
3494   stack : {
3495     values : []
3496   },
3497
3498   /**
3499    * Draws lines series in the canvas element.
3500    * @param {Object} options
3501    */
3502   draw : function (options) {
3503
3504     var
3505       context     = options.context,
3506       lineWidth   = options.lineWidth,
3507       shadowSize  = options.shadowSize,
3508       offset;
3509
3510     context.save();
3511     context.lineJoin = 'round';
3512
3513     if (shadowSize) {
3514
3515       context.lineWidth = shadowSize / 2;
3516       offset = lineWidth / 2 + context.lineWidth / 2;
3517       
3518       // @TODO do this instead with a linear gradient
3519       context.strokeStyle = "rgba(0,0,0,0.1)";
3520       this.plot(options, offset + shadowSize / 2, false);
3521
3522       context.strokeStyle = "rgba(0,0,0,0.2)";
3523       this.plot(options, offset, false);
3524     }
3525
3526     context.lineWidth = lineWidth;
3527     context.strokeStyle = options.color;
3528
3529     this.plot(options, 0, true);
3530
3531     context.restore();
3532   },
3533
3534   plot : function (options, shadowOffset, incStack) {
3535
3536     var
3537       context   = options.context,
3538       width     = options.width, 
3539       height    = options.height,
3540       xScale    = options.xScale,
3541       yScale    = options.yScale,
3542       data      = options.data, 
3543       stack     = options.stacked ? this.stack : false,
3544       length    = data.length - 1,
3545       prevx     = null,
3546       prevy     = null,
3547       zero      = yScale(0),
3548       start     = null,
3549       x1, x2, y1, y2, stack1, stack2, i;
3550       
3551     if (length < 1) return;
3552
3553     context.beginPath();
3554
3555     for (i = 0; i < length; ++i) {
3556
3557       // To allow empty values
3558       if (data[i][1] === null || data[i+1][1] === null) {
3559         if (options.fill) {
3560           if (i > 0 && data[i][1] !== null) {
3561             context.stroke();
3562             fill();
3563             start = null;
3564             context.closePath();
3565             context.beginPath();
3566           }
3567         }
3568         continue;
3569       }
3570
3571       // Zero is infinity for log scales
3572       // TODO handle zero for logarithmic
3573       // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue;
3574       // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue;
3575       
3576       x1 = xScale(data[i][0]);
3577       x2 = xScale(data[i+1][0]);
3578
3579       if (start === null) start = data[i];
3580       
3581       if (stack) {
3582         stack1 = stack.values[data[i][0]] || 0;
3583         stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0;
3584         y1 = yScale(data[i][1] + stack1);
3585         y2 = yScale(data[i+1][1] + stack2);
3586         if (incStack) {
3587           data[i].y0 = stack1;
3588           stack.values[data[i][0]] = data[i][1] + stack1;
3589           if (i == length-1) {
3590             data[i+1].y0 = stack2;
3591             stack.values[data[i+1][0]] = data[i+1][1] + stack2;
3592           }
3593         }
3594       } else {
3595         y1 = yScale(data[i][1]);
3596         y2 = yScale(data[i+1][1]);
3597       }
3598
3599       if (
3600         (y1 > height && y2 > height) ||
3601         (y1 < 0 && y2 < 0) ||
3602         (x1 < 0 && x2 < 0) ||
3603         (x1 > width && x2 > width)
3604       ) continue;
3605
3606       if ((prevx != x1) || (prevy != y1 + shadowOffset)) {
3607         context.moveTo(x1, y1 + shadowOffset);
3608       }
3609       
3610       prevx = x2;
3611       prevy = y2 + shadowOffset;
3612       if (options.steps) {
3613         context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset);
3614         context.lineTo(prevx + shadowOffset / 2, prevy);
3615       } else {
3616         context.lineTo(prevx, prevy);
3617       }
3618     }
3619     
3620     if (!options.fill || options.fill && !options.fillBorder) context.stroke();
3621
3622     fill();
3623
3624     function fill () {
3625       // TODO stacked lines
3626       if(!shadowOffset && options.fill && start){
3627         x1 = xScale(start[0]);
3628         context.fillStyle = options.fillStyle;
3629         context.lineTo(x2, zero);
3630         context.lineTo(x1, zero);
3631         context.lineTo(x1, yScale(start[1]));
3632         context.fill();
3633         if (options.fillBorder) {
3634           context.stroke();
3635         }
3636       }
3637     }
3638
3639     context.closePath();
3640   },
3641
3642   // Perform any pre-render precalculations (this should be run on data first)
3643   // - Pie chart total for calculating measures
3644   // - Stacks for lines and bars
3645   // precalculate : function () {
3646   // }
3647   //
3648   //
3649   // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max)
3650   // getBounds : function () {
3651   // }
3652   // getMin : function () {
3653   // }
3654   // getMax : function () {
3655   // }
3656   //
3657   //
3658   // Padding around rendered elements
3659   // getPadding : function () {
3660   // }
3661
3662   extendYRange : function (axis, data, options, lines) {
3663
3664     var o = axis.options;
3665
3666     // If stacked and auto-min
3667     if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) {
3668
3669       var
3670         newmax = axis.max,
3671         newmin = axis.min,
3672         positiveSums = lines.positiveSums || {},
3673         negativeSums = lines.negativeSums || {},
3674         x, j;
3675
3676       for (j = 0; j < data.length; j++) {
3677
3678         x = data[j][0] + '';
3679
3680         // Positive
3681         if (data[j][1] > 0) {
3682           positiveSums[x] = (positiveSums[x] || 0) + data[j][1];
3683           newmax = Math.max(newmax, positiveSums[x]);
3684         }
3685
3686         // Negative
3687         else {
3688           negativeSums[x] = (negativeSums[x] || 0) + data[j][1];
3689           newmin = Math.min(newmin, negativeSums[x]);
3690         }
3691       }
3692
3693       lines.negativeSums = negativeSums;
3694       lines.positiveSums = positiveSums;
3695
3696       axis.max = newmax;
3697       axis.min = newmin;
3698     }
3699
3700     if (options.steps) {
3701
3702       this.hit = function (options) {
3703         var
3704           data = options.data,
3705           args = options.args,
3706           yScale = options.yScale,
3707           mouse = args[0],
3708           length = data.length,
3709           n = args[1],
3710           x = options.xInverse(mouse.relX),
3711           relY = mouse.relY,
3712           i;
3713
3714         for (i = 0; i < length - 1; i++) {
3715           if (x >= data[i][0] && x <= data[i+1][0]) {
3716             if (Math.abs(yScale(data[i][1]) - relY) < 8) {
3717               n.x = data[i][0];
3718               n.y = data[i][1];
3719               n.index = i;
3720               n.seriesIndex = options.index;
3721             }
3722             break;
3723           }
3724         }
3725       };
3726
3727       this.drawHit = function (options) {
3728         var
3729           context = options.context,
3730           args    = options.args,
3731           data    = options.data,
3732           xScale  = options.xScale,
3733           index   = args.index,
3734           x       = xScale(args.x),
3735           y       = options.yScale(args.y),
3736           x2;
3737
3738         if (data.length - 1 > index) {
3739           x2 = options.xScale(data[index + 1][0]);
3740           context.save();
3741           context.strokeStyle = options.color;
3742           context.lineWidth = options.lineWidth;
3743           context.beginPath();
3744           context.moveTo(x, y);
3745           context.lineTo(x2, y);
3746           context.stroke();
3747           context.closePath();
3748           context.restore();
3749         }
3750       };
3751
3752       this.clearHit = function (options) {
3753         var
3754           context = options.context,
3755           args    = options.args,
3756           data    = options.data,
3757           xScale  = options.xScale,
3758           width   = options.lineWidth,
3759           index   = args.index,
3760           x       = xScale(args.x),
3761           y       = options.yScale(args.y),
3762           x2;
3763
3764         if (data.length - 1 > index) {
3765           x2 = options.xScale(data[index + 1][0]);
3766           context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width);
3767         }
3768       };
3769     }
3770   }
3771
3772 });
3773
3774 /** Bars **/
3775 Flotr.addType('bars', {
3776
3777   options: {
3778     show: false,           // => setting to true will show bars, false will hide
3779     lineWidth: 2,          // => in pixels
3780     barWidth: 1,           // => in units of the x axis
3781     fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
3782     fillColor: null,       // => fill color
3783     fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
3784     horizontal: false,     // => horizontal bars (x and y inverted)
3785     stacked: false,        // => stacked bar charts
3786     centered: true,        // => center the bars to their x axis value
3787     topPadding: 0.1,       // => top padding in percent
3788     grouped: false         // => groups bars together which share x value, hit not supported.
3789   },
3790
3791   stack : { 
3792     positive : [],
3793     negative : [],
3794     _positive : [], // Shadow
3795     _negative : []  // Shadow
3796   },
3797
3798   draw : function (options) {
3799     var
3800       context = options.context;
3801
3802     this.current += 1;
3803
3804     context.save();
3805     context.lineJoin = 'miter';
3806     // @TODO linewidth not interpreted the right way.
3807     context.lineWidth = options.lineWidth;
3808     context.strokeStyle = options.color;
3809     if (options.fill) context.fillStyle = options.fillStyle;
3810     
3811     this.plot(options);
3812
3813     context.restore();
3814   },
3815
3816   plot : function (options) {
3817
3818     var
3819       data            = options.data,
3820       context         = options.context,
3821       shadowSize      = options.shadowSize,
3822       i, geometry, left, top, width, height;
3823
3824     if (data.length < 1) return;
3825
3826     this.translate(context, options.horizontal);
3827
3828     for (i = 0; i < data.length; i++) {
3829
3830       geometry = this.getBarGeometry(data[i][0], data[i][1], options);
3831       if (geometry === null) continue;
3832
3833       left    = geometry.left;
3834       top     = geometry.top;
3835       width   = geometry.width;
3836       height  = geometry.height;
3837
3838       if (options.fill) context.fillRect(left, top, width, height);
3839       if (shadowSize) {
3840         context.save();
3841         context.fillStyle = 'rgba(0,0,0,0.05)';
3842         context.fillRect(left + shadowSize, top + shadowSize, width, height);
3843         context.restore();
3844       }
3845       if (options.lineWidth) {
3846         context.strokeRect(left, top, width, height);
3847       }
3848     }
3849   },
3850
3851   translate : function (context, horizontal) {
3852     if (horizontal) {
3853       context.rotate(-Math.PI / 2);
3854       context.scale(-1, 1);
3855     }
3856   },
3857
3858   getBarGeometry : function (x, y, options) {
3859
3860     var
3861       horizontal    = options.horizontal,
3862       barWidth      = options.barWidth,
3863       centered      = options.centered,
3864       stack         = options.stacked ? this.stack : false,
3865       lineWidth     = options.lineWidth,
3866       bisection     = centered ? barWidth / 2 : 0,
3867       xScale        = horizontal ? options.yScale : options.xScale,
3868       yScale        = horizontal ? options.xScale : options.yScale,
3869       xValue        = horizontal ? y : x,
3870       yValue        = horizontal ? x : y,
3871       stackOffset   = 0,
3872       stackValue, left, right, top, bottom;
3873
3874     if (options.grouped) {
3875       this.current / this.groups;
3876       xValue = xValue - bisection;
3877       barWidth = barWidth / this.groups;
3878       bisection = barWidth / 2;
3879       xValue = xValue + barWidth * this.current - bisection;
3880     }
3881
3882     // Stacked bars
3883     if (stack) {
3884       stackValue          = yValue > 0 ? stack.positive : stack.negative;
3885       stackOffset         = stackValue[xValue] || stackOffset;
3886       stackValue[xValue]  = stackOffset + yValue;
3887     }
3888
3889     left    = xScale(xValue - bisection);
3890     right   = xScale(xValue + barWidth - bisection);
3891     top     = yScale(yValue + stackOffset);
3892     bottom  = yScale(stackOffset);
3893
3894     // TODO for test passing... probably looks better without this
3895     if (bottom < 0) bottom = 0;
3896
3897     // TODO Skipping...
3898     // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue;
3899
3900     return (x === null || y === null) ? null : {
3901       x         : xValue,
3902       y         : yValue,
3903       xScale    : xScale,
3904       yScale    : yScale,
3905       top       : top,
3906       left      : Math.min(left, right) - lineWidth / 2,
3907       width     : Math.abs(right - left) - lineWidth,
3908       height    : bottom - top
3909     };
3910   },
3911
3912   hit : function (options) {
3913     var
3914       data = options.data,
3915       args = options.args,
3916       mouse = args[0],
3917       n = args[1],
3918       x = options.xInverse(mouse.relX),
3919       y = options.yInverse(mouse.relY),
3920       hitGeometry = this.getBarGeometry(x, y, options),
3921       width = hitGeometry.width / 2,
3922       left = hitGeometry.left,
3923       height = hitGeometry.y,
3924       geometry, i;
3925
3926     for (i = data.length; i--;) {
3927       geometry = this.getBarGeometry(data[i][0], data[i][1], options);
3928       if (
3929         // Height:
3930         (
3931           // Positive Bars:
3932           (height > 0 && height < geometry.y) ||
3933           // Negative Bars:
3934           (height < 0 && height > geometry.y)
3935         ) &&
3936         // Width:
3937         (Math.abs(left - geometry.left) < width)
3938       ) {
3939         n.x = data[i][0];
3940         n.y = data[i][1];
3941         n.index = i;
3942         n.seriesIndex = options.index;
3943       }
3944     }
3945   },
3946
3947   drawHit : function (options) {
3948     // TODO hits for stacked bars; implement using calculateStack option?
3949     var
3950       context     = options.context,
3951       args        = options.args,
3952       geometry    = this.getBarGeometry(args.x, args.y, options),
3953       left        = geometry.left,
3954       top         = geometry.top,
3955       width       = geometry.width,
3956       height      = geometry.height;
3957
3958     context.save();
3959     context.strokeStyle = options.color;
3960     context.lineWidth = options.lineWidth;
3961     this.translate(context, options.horizontal);
3962
3963     // Draw highlight
3964     context.beginPath();
3965     context.moveTo(left, top + height);
3966     context.lineTo(left, top);
3967     context.lineTo(left + width, top);
3968     context.lineTo(left + width, top + height);
3969     if (options.fill) {
3970       context.fillStyle = options.fillStyle;
3971       context.fill();
3972     }
3973     context.stroke();
3974     context.closePath();
3975
3976     context.restore();
3977   },
3978
3979   clearHit: function (options) {
3980     var
3981       context     = options.context,
3982       args        = options.args,
3983       geometry    = this.getBarGeometry(args.x, args.y, options),
3984       left        = geometry.left,
3985       width       = geometry.width,
3986       top         = geometry.top,
3987       height      = geometry.height,
3988       lineWidth   = 2 * options.lineWidth;
3989
3990     context.save();
3991     this.translate(context, options.horizontal);
3992     context.clearRect(
3993       left - lineWidth,
3994       Math.min(top, top + height) - lineWidth,
3995       width + 2 * lineWidth,
3996       Math.abs(height) + 2 * lineWidth
3997     );
3998     context.restore();
3999   },
4000
4001   extendXRange : function (axis, data, options, bars) {
4002     this._extendRange(axis, data, options, bars);
4003     this.groups = (this.groups + 1) || 1;
4004     this.current = 0;
4005   },
4006
4007   extendYRange : function (axis, data, options, bars) {
4008     this._extendRange(axis, data, options, bars);
4009   },
4010   _extendRange: function (axis, data, options, bars) {
4011
4012     var
4013       max = axis.options.max;
4014
4015     if (_.isNumber(max) || _.isString(max)) return; 
4016
4017     var
4018       newmin = axis.min,
4019       newmax = axis.max,
4020       horizontal = options.horizontal,
4021       orientation = axis.orientation,
4022       positiveSums = this.positiveSums || {},
4023       negativeSums = this.negativeSums || {},
4024       value, datum, index, j;
4025
4026     // Sides of bars
4027     if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) {
4028       if (options.centered) {
4029         newmax = Math.max(axis.datamax + options.barWidth, newmax);
4030         newmin = Math.min(axis.datamin - options.barWidth, newmin);
4031       }
4032     }
4033
4034     if (options.stacked && 
4035         ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){
4036
4037       for (j = data.length; j--;) {
4038         value = data[j][(orientation == 1 ? 1 : 0)]+'';
4039         datum = data[j][(orientation == 1 ? 0 : 1)];
4040
4041         // Positive
4042         if (datum > 0) {
4043           positiveSums[value] = (positiveSums[value] || 0) + datum;
4044           newmax = Math.max(newmax, positiveSums[value]);
4045         }
4046
4047         // Negative
4048         else {
4049           negativeSums[value] = (negativeSums[value] || 0) + datum;
4050           newmin = Math.min(newmin, negativeSums[value]);
4051         }
4052       }
4053     }
4054
4055     // End of bars
4056     if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) {
4057       if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) {
4058         newmax += options.topPadding * (newmax - newmin);
4059       }
4060     }
4061
4062     this.stackMin = newmin;
4063     this.stackMax = newmax;
4064     this.negativeSums = negativeSums;
4065     this.positiveSums = positiveSums;
4066
4067     axis.max = newmax;
4068     axis.min = newmin;
4069   }
4070
4071 });
4072
4073 /** Bubbles **/
4074 Flotr.addType('bubbles', {
4075   options: {
4076     show: false,      // => setting to true will show radar chart, false will hide
4077     lineWidth: 2,     // => line width in pixels
4078     fill: true,       // => true to fill the area from the line to the x axis, false for (transparent) no fill
4079     fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
4080     baseRadius: 2     // => ratio of the radar, against the plot size
4081   },
4082   draw : function (options) {
4083     var
4084       context     = options.context,
4085       shadowSize  = options.shadowSize;
4086
4087     context.save();
4088     context.lineWidth = options.lineWidth;
4089     
4090     // Shadows
4091     context.fillStyle = 'rgba(0,0,0,0.05)';
4092     context.strokeStyle = 'rgba(0,0,0,0.05)';
4093     this.plot(options, shadowSize / 2);
4094     context.strokeStyle = 'rgba(0,0,0,0.1)';
4095     this.plot(options, shadowSize / 4);
4096
4097     // Chart
4098     context.strokeStyle = options.color;
4099     context.fillStyle = options.fillStyle;
4100     this.plot(options);
4101     
4102     context.restore();
4103   },
4104   plot : function (options, offset) {
4105
4106     var
4107       data    = options.data,
4108       context = options.context,
4109       geometry,
4110       i, x, y, z;
4111
4112     offset = offset || 0;
4113     
4114     for (i = 0; i < data.length; ++i){
4115
4116       geometry = this.getGeometry(data[i], options);
4117
4118       context.beginPath();
4119       context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true);
4120       context.stroke();
4121       if (options.fill) context.fill();
4122       context.closePath();
4123     }
4124   },
4125   getGeometry : function (point, options) {
4126     return {
4127       x : options.xScale(point[0]),
4128       y : options.yScale(point[1]),
4129       z : point[2] * options.baseRadius
4130     };
4131   },
4132   hit : function (options) {
4133     var
4134       data = options.data,
4135       args = options.args,
4136       mouse = args[0],
4137       n = args[1],
4138       relX = mouse.relX,
4139       relY = mouse.relY,
4140       distance,
4141       geometry,
4142       dx, dy;
4143
4144     n.best = n.best || Number.MAX_VALUE;
4145
4146     for (i = data.length; i--;) {
4147       geometry = this.getGeometry(data[i], options);
4148
4149       dx = geometry.x - relX;
4150       dy = geometry.y - relY;
4151       distance = Math.sqrt(dx * dx + dy * dy);
4152
4153       if (distance < geometry.z && geometry.z < n.best) {
4154         n.x = data[i][0];
4155         n.y = data[i][1];
4156         n.index = i;
4157         n.seriesIndex = options.index;
4158         n.best = geometry.z;
4159       }
4160     }
4161   },
4162   drawHit : function (options) {
4163
4164     var
4165       context = options.context,
4166       geometry = this.getGeometry(options.data[options.args.index], options);
4167
4168     context.save();
4169     context.lineWidth = options.lineWidth;
4170     context.fillStyle = options.fillStyle;
4171     context.strokeStyle = options.color;
4172     context.beginPath();
4173     context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true);
4174     context.fill();
4175     context.stroke();
4176     context.closePath();
4177     context.restore();
4178   },
4179   clearHit : function (options) {
4180
4181     var
4182       context = options.context,
4183       geometry = this.getGeometry(options.data[options.args.index], options),
4184       offset = geometry.z + options.lineWidth;
4185
4186     context.save();
4187     context.clearRect(
4188       geometry.x - offset, 
4189       geometry.y - offset,
4190       2 * offset,
4191       2 * offset
4192     );
4193     context.restore();
4194   }
4195   // TODO Add a hit calculation method (like pie)
4196 });
4197
4198 /** Candles **/
4199 Flotr.addType('candles', {
4200   options: {
4201     show: false,           // => setting to true will show candle sticks, false will hide
4202     lineWidth: 1,          // => in pixels
4203     wickLineWidth: 1,      // => in pixels
4204     candleWidth: 0.6,      // => in units of the x axis
4205     fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
4206     upFillColor: '#00A8F0',// => up sticks fill color
4207     downFillColor: '#CB4B4B',// => down sticks fill color
4208     fillOpacity: 0.5,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
4209     barcharts: false       // => draw as barcharts (not standard bars but financial barcharts)
4210   },
4211
4212   draw : function (options) {
4213
4214     var
4215       context = options.context;
4216
4217     context.save();
4218     context.lineJoin = 'miter';
4219     context.lineCap = 'butt';
4220     // @TODO linewidth not interpreted the right way.
4221     context.lineWidth = options.wickLineWidth || options.lineWidth;
4222
4223     this.plot(options);
4224
4225     context.restore();
4226   },
4227
4228   plot : function (options) {
4229
4230     var
4231       data          = options.data,
4232       context       = options.context,
4233       xScale        = options.xScale,
4234       yScale        = options.yScale,
4235       width         = options.candleWidth / 2,
4236       shadowSize    = options.shadowSize,
4237       lineWidth     = options.lineWidth,
4238       wickLineWidth = options.wickLineWidth,
4239       pixelOffset   = (wickLineWidth % 2) / 2,
4240       color,
4241       datum, x, y,
4242       open, high, low, close,
4243       left, right, bottom, top, bottom2, top2, reverseLines,
4244       i;
4245
4246     if (data.length < 1) return;
4247
4248     for (i = 0; i < data.length; i++) {
4249       datum   = data[i];
4250       x       = datum[0];
4251       open    = datum[1];
4252       high    = datum[2];
4253       low     = datum[3];
4254       close   = datum[4];
4255       left    = xScale(x - width);
4256       right   = xScale(x + width);
4257       bottom  = yScale(low);
4258       top     = yScale(high);
4259       bottom2 = yScale(Math.min(open, close));
4260       top2    = yScale(Math.max(open, close));
4261
4262       /*
4263       // TODO skipping
4264       if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
4265         continue;
4266       */
4267
4268       color = options[open > close ? 'downFillColor' : 'upFillColor'];
4269
4270       // Fill the candle.
4271       if (options.fill && !options.barcharts) {
4272         context.fillStyle = 'rgba(0,0,0,0.05)';
4273         context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2);
4274         context.save();
4275         context.globalAlpha = options.fillOpacity;
4276         context.fillStyle = color;
4277         context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2);
4278         context.restore();
4279       }
4280
4281       // Draw candle outline/border, high, low.
4282       if (lineWidth || wickLineWidth) {
4283
4284         x = Math.floor((left + right) / 2) + pixelOffset;
4285
4286         context.strokeStyle = color;
4287         context.beginPath();
4288
4289         if (options.barcharts) {
4290           context.moveTo(x, Math.floor(top + lineWidth));
4291           context.lineTo(x, Math.floor(bottom + lineWidth));
4292
4293           reverseLines = open < close;
4294           context.moveTo(reverseLines ? right : left, Math.floor(top2 + lineWidth));
4295           context.lineTo(x, Math.floor(top2 + lineWidth));
4296           context.moveTo(x, Math.floor(bottom2 + lineWidth));
4297           context.lineTo(reverseLines ? left : right, Math.floor(bottom2 + lineWidth));
4298         } else {
4299           context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2);
4300           context.moveTo(x, Math.floor(top2 + lineWidth));
4301           context.lineTo(x, Math.floor(top + lineWidth));
4302           context.moveTo(x, Math.floor(bottom2 + lineWidth));
4303           context.lineTo(x, Math.floor(bottom + lineWidth));
4304         }
4305         
4306         context.closePath();
4307         context.stroke();
4308       }
4309     }
4310   },
4311
4312   hit : function (options) {
4313     var
4314       xScale = options.xScale,
4315       yScale = options.yScale,
4316       data = options.data,
4317       args = options.args,
4318       mouse = args[0],
4319       width = options.candleWidth / 2,
4320       n = args[1],
4321       x = mouse.relX,
4322       y = mouse.relY,
4323       length = data.length,
4324       i, datum,
4325       high, low,
4326       left, right, top, bottom;
4327
4328     for (i = 0; i < length; i++) {
4329       datum   = data[i],
4330       high    = datum[2];
4331       low     = datum[3];
4332       left    = xScale(datum[0] - width);
4333       right   = xScale(datum[0] + width);
4334       bottom  = yScale(low);
4335       top     = yScale(high);
4336
4337       if (x > left && x < right && y > top && y < bottom) {
4338         n.x = datum[0];
4339         n.index = i;
4340         n.seriesIndex = options.index;
4341         return;
4342       }
4343     }
4344   },
4345
4346   drawHit : function (options) {
4347     var
4348       context = options.context;
4349     context.save();
4350     this.plot(
4351       _.defaults({
4352         fill : !!options.fillColor,
4353         upFillColor : options.color,
4354         downFillColor : options.color,
4355         data : [options.data[options.args.index]]
4356       }, options)
4357     );
4358     context.restore();
4359   },
4360
4361   clearHit : function (options) {
4362     var
4363       args = options.args,
4364       context = options.context,
4365       xScale = options.xScale,
4366       yScale = options.yScale,
4367       lineWidth = options.lineWidth,
4368       width = options.candleWidth / 2,
4369       bar = options.data[args.index],
4370       left = xScale(bar[0] - width) - lineWidth,
4371       right = xScale(bar[0] + width) + lineWidth,
4372       top = yScale(bar[2]),
4373       bottom = yScale(bar[3]) + lineWidth;
4374     context.clearRect(left, top, right - left, bottom - top);
4375   },
4376
4377   extendXRange: function (axis, data, options) {
4378     if (axis.options.max === null) {
4379       axis.max = Math.max(axis.datamax + 0.5, axis.max);
4380       axis.min = Math.min(axis.datamin - 0.5, axis.min);
4381     }
4382   }
4383 });
4384
4385 /** Gantt
4386  * Base on data in form [s,y,d] where:
4387  * y - executor or simply y value
4388  * s - task start value
4389  * d - task duration
4390  * **/
4391 Flotr.addType('gantt', {
4392   options: {
4393     show: false,           // => setting to true will show gantt, false will hide
4394     lineWidth: 2,          // => in pixels
4395     barWidth: 1,           // => in units of the x axis
4396     fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
4397     fillColor: null,       // => fill color
4398     fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
4399     centered: true         // => center the bars to their x axis value
4400   },
4401   /**
4402    * Draws gantt series in the canvas element.
4403    * @param {Object} series - Series with options.gantt.show = true.
4404    */
4405   draw: function(series) {
4406     var ctx = this.ctx,
4407       bw = series.gantt.barWidth,
4408       lw = Math.min(series.gantt.lineWidth, bw);
4409     
4410     ctx.save();
4411     ctx.translate(this.plotOffset.left, this.plotOffset.top);
4412     ctx.lineJoin = 'miter';
4413
4414     /**
4415      * @todo linewidth not interpreted the right way.
4416      */
4417     ctx.lineWidth = lw;
4418     ctx.strokeStyle = series.color;
4419     
4420     ctx.save();
4421     this.gantt.plotShadows(series, bw, 0, series.gantt.fill);
4422     ctx.restore();
4423     
4424     if(series.gantt.fill){
4425       var color = series.gantt.fillColor || series.color;
4426       ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity});
4427     }
4428     
4429     this.gantt.plot(series, bw, 0, series.gantt.fill);
4430     ctx.restore();
4431   },
4432   plot: function(series, barWidth, offset, fill){
4433     var data = series.data;
4434     if(data.length < 1) return;
4435     
4436     var xa = series.xaxis,
4437         ya = series.yaxis,
4438         ctx = this.ctx, i;
4439
4440     for(i = 0; i < data.length; i++){
4441       var y = data[i][0],
4442           s = data[i][1],
4443           d = data[i][2],
4444           drawLeft = true, drawTop = true, drawRight = true;
4445       
4446       if (s === null || d === null) continue;
4447
4448       var left = s, 
4449           right = s + d,
4450           bottom = y - (series.gantt.centered ? barWidth/2 : 0), 
4451           top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
4452       
4453       if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
4454         continue;
4455
4456       if(left < xa.min){
4457         left = xa.min;
4458         drawLeft = false;
4459       }
4460
4461       if(right > xa.max){
4462         right = xa.max;
4463         if (xa.lastSerie != series)
4464           drawTop = false;
4465       }
4466
4467       if(bottom < ya.min)
4468         bottom = ya.min;
4469
4470       if(top > ya.max){
4471         top = ya.max;
4472         if (ya.lastSerie != series)
4473           drawTop = false;
4474       }
4475       
4476       /**
4477        * Fill the bar.
4478        */
4479       if(fill){
4480         ctx.beginPath();
4481         ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
4482         ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset);
4483         ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset);
4484         ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset);
4485         ctx.fill();
4486         ctx.closePath();
4487       }
4488
4489       /**
4490        * Draw bar outline/border.
4491        */
4492       if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){
4493         ctx.beginPath();
4494         ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
4495         
4496         ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset);
4497         ctx[drawTop  ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset);
4498         ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset);
4499                  
4500         ctx.stroke();
4501         ctx.closePath();
4502       }
4503     }
4504   },
4505   plotShadows: function(series, barWidth, offset){
4506     var data = series.data;
4507     if(data.length < 1) return;
4508     
4509     var i, y, s, d,
4510         xa = series.xaxis,
4511         ya = series.yaxis,
4512         ctx = this.ctx,
4513         sw = this.options.shadowSize;
4514     
4515     for(i = 0; i < data.length; i++){
4516       y = data[i][0];
4517       s = data[i][1];
4518       d = data[i][2];
4519         
4520       if (s === null || d === null) continue;
4521             
4522       var left = s, 
4523           right = s + d,
4524           bottom = y - (series.gantt.centered ? barWidth/2 : 0), 
4525           top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
4526  
4527       if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
4528         continue;
4529       
4530       if(left < xa.min)   left = xa.min;
4531       if(right > xa.max)  right = xa.max;
4532       if(bottom < ya.min) bottom = ya.min;
4533       if(top > ya.max)    top = ya.max;
4534       
4535       var width =  xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw);
4536       var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw );
4537       
4538       ctx.fillStyle = 'rgba(0,0,0,0.05)';
4539       ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height);
4540     }
4541   },
4542   extendXRange: function(axis) {
4543     if(axis.options.max === null){
4544       var newmin = axis.min,
4545           newmax = axis.max,
4546           i, j, x, s, g,
4547           stackedSumsPos = {},
4548           stackedSumsNeg = {},
4549           lastSerie = null;
4550
4551       for(i = 0; i < this.series.length; ++i){
4552         s = this.series[i];
4553         g = s.gantt;
4554         
4555         if(g.show && s.xaxis == axis) {
4556             for (j = 0; j < s.data.length; j++) {
4557               if (g.show) {
4558                 y = s.data[j][0]+'';
4559                 stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]);
4560                 lastSerie = s;
4561               }
4562             }
4563             for (j in stackedSumsPos) {
4564               newmax = Math.max(stackedSumsPos[j], newmax);
4565             }
4566         }
4567       }
4568       axis.lastSerie = lastSerie;
4569       axis.max = newmax;
4570       axis.min = newmin;
4571     }
4572   },
4573   extendYRange: function(axis){
4574     if(axis.options.max === null){
4575       var newmax = Number.MIN_VALUE,
4576           newmin = Number.MAX_VALUE,
4577           i, j, s, g,
4578           stackedSumsPos = {},
4579           stackedSumsNeg = {},
4580           lastSerie = null;
4581                   
4582       for(i = 0; i < this.series.length; ++i){
4583         s = this.series[i];
4584         g = s.gantt;
4585         
4586         if (g.show && !s.hide && s.yaxis == axis) {
4587           var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE;
4588           for(j=0; j < s.data.length; j++){
4589             datamax = Math.max(datamax,s.data[j][0]);
4590             datamin = Math.min(datamin,s.data[j][0]);
4591           }
4592             
4593           if (g.centered) {
4594             newmax = Math.max(datamax + 0.5, newmax);
4595             newmin = Math.min(datamin - 0.5, newmin);
4596           }
4597         else {
4598           newmax = Math.max(datamax + 1, newmax);
4599             newmin = Math.min(datamin, newmin);
4600           }
4601           // For normal horizontal bars
4602           if (g.barWidth + datamax > newmax){
4603             newmax = axis.max + g.barWidth;
4604           }
4605         }
4606       }
4607       axis.lastSerie = lastSerie;
4608       axis.max = newmax;
4609       axis.min = newmin;
4610       axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals);
4611     }
4612   }
4613 });
4614
4615 /** Markers **/
4616 /**
4617  * Formats the marker labels.
4618  * @param {Object} obj - Marker value Object {x:..,y:..}
4619  * @return {String} Formatted marker string
4620  */
4621 (function () {
4622
4623 Flotr.defaultMarkerFormatter = function(obj){
4624   return (Math.round(obj.y*100)/100)+'';
4625 };
4626
4627 Flotr.addType('markers', {
4628   options: {
4629     show: false,           // => setting to true will show markers, false will hide
4630     lineWidth: 1,          // => line width of the rectangle around the marker
4631     color: '#000000',      // => text color
4632     fill: false,           // => fill or not the marekers' rectangles
4633     fillColor: "#FFFFFF",  // => fill color
4634     fillOpacity: 0.4,      // => fill opacity
4635     stroke: false,         // => draw the rectangle around the markers
4636     position: 'ct',        // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
4637     verticalMargin: 0,     // => the margin between the point and the text.
4638     labelFormatter: Flotr.defaultMarkerFormatter,
4639     fontSize: Flotr.defaultOptions.fontSize,
4640     stacked: false,        // => true if markers should be stacked
4641     stackingType: 'b',     // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
4642     horizontal: false      // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
4643   },
4644
4645   // TODO test stacked markers.
4646   stack : {
4647       positive : [],
4648       negative : [],
4649       values : []
4650   },
4651
4652   draw : function (options) {
4653
4654     var
4655       data            = options.data,
4656       context         = options.context,
4657       stack           = options.stacked ? options.stack : false,
4658       stackType       = options.stackingType,
4659       stackOffsetNeg,
4660       stackOffsetPos,
4661       stackOffset,
4662       i, x, y, label;
4663
4664     context.save();
4665     context.lineJoin = 'round';
4666     context.lineWidth = options.lineWidth;
4667     context.strokeStyle = 'rgba(0,0,0,0.5)';
4668     context.fillStyle = options.fillStyle;
4669
4670     function stackPos (a, b) {
4671       stackOffsetPos = stack.negative[a] || 0;
4672       stackOffsetNeg = stack.positive[a] || 0;
4673       if (b > 0) {
4674         stack.positive[a] = stackOffsetPos + b;
4675         return stackOffsetPos + b;
4676       } else {
4677         stack.negative[a] = stackOffsetNeg + b;
4678         return stackOffsetNeg + b;
4679       }
4680     }
4681
4682     for (i = 0; i < data.length; ++i) {
4683     
4684       x = data[i][0];
4685       y = data[i][1];
4686         
4687       if (stack) {
4688         if (stackType == 'b') {
4689           if (options.horizontal) y = stackPos(y, x);
4690           else x = stackPos(x, y);
4691         } else if (stackType == 'a') {
4692           stackOffset = stack.values[x] || 0;
4693           stack.values[x] = stackOffset + y;
4694           y = stackOffset + y;
4695         }
4696       }
4697
4698       label = options.labelFormatter({x: x, y: y, index: i, data : data});
4699       this.plot(options.xScale(x), options.yScale(y), label, options);
4700     }
4701     context.restore();
4702   },
4703   plot: function(x, y, label, options) {
4704     var context = options.context;
4705     if (isImage(label) && !label.complete) {
4706       throw 'Marker image not loaded.';
4707     } else {
4708       this._plot(x, y, label, options);
4709     }
4710   },
4711
4712   _plot: function(x, y, label, options) {
4713     var context = options.context,
4714         margin = 2,
4715         left = x,
4716         top = y,
4717         dim;
4718
4719     if (isImage(label))
4720       dim = {height : label.height, width: label.width};
4721     else
4722       dim = options.text.canvas(label);
4723
4724     dim.width = Math.floor(dim.width+margin*2);
4725     dim.height = Math.floor(dim.height+margin*2);
4726
4727          if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
4728     else if (options.position.indexOf('l') != -1) left -= dim.width;
4729     
4730          if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
4731     else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin;
4732     else top += options.verticalMargin;
4733     
4734     left = Math.floor(left)+0.5;
4735     top = Math.floor(top)+0.5;
4736     
4737     if(options.fill)
4738       context.fillRect(left, top, dim.width, dim.height);
4739       
4740     if(options.stroke)
4741       context.strokeRect(left, top, dim.width, dim.height);
4742     
4743     if (isImage(label))
4744       context.drawImage(label, parseInt(left+margin, 10), parseInt(top+margin, 10));
4745     else
4746       Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
4747   }
4748 });
4749
4750 function isImage (i) {
4751   return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
4752 }
4753
4754 })();
4755
4756 /**
4757  * Pie
4758  *
4759  * Formats the pies labels.
4760  * @param {Object} slice - Slice object
4761  * @return {String} Formatted pie label string
4762  */
4763 (function () {
4764
4765 var
4766   _ = Flotr._;
4767
4768 Flotr.defaultPieLabelFormatter = function (total, value) {
4769   return (100 * value / total).toFixed(2)+'%';
4770 };
4771
4772 Flotr.addType('pie', {
4773   options: {
4774     show: false,           // => setting to true will show bars, false will hide
4775     lineWidth: 1,          // => in pixels
4776     fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
4777     fillColor: null,       // => fill color
4778     fillOpacity: 0.6,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
4779     explode: 6,            // => the number of pixels the splices will be far from the center
4780     sizeRatio: 0.6,        // => the size ratio of the pie relative to the plot 
4781     startAngle: Math.PI/4, // => the first slice start angle
4782     labelFormatter: Flotr.defaultPieLabelFormatter,
4783     pie3D: false,          // => whether to draw the pie in 3 dimenstions or not (ineffective) 
4784     pie3DviewAngle: (Math.PI/2 * 0.8),
4785     pie3DspliceThickness: 20,
4786     epsilon: 0.1           // => how close do you have to get to hit empty slice
4787   },
4788
4789   draw : function (options) {
4790
4791     // TODO 3D charts what?
4792     var
4793       data          = options.data,
4794       context       = options.context,
4795       lineWidth     = options.lineWidth,
4796       shadowSize    = options.shadowSize,
4797       sizeRatio     = options.sizeRatio,
4798       height        = options.height,
4799       width         = options.width,
4800       explode       = options.explode,
4801       color         = options.color,
4802       fill          = options.fill,
4803       fillStyle     = options.fillStyle,
4804       radius        = Math.min(width, height) * sizeRatio / 2,
4805       value         = data[0][1],
4806       html          = [],
4807       vScale        = 1,//Math.cos(series.pie.viewAngle);
4808       measure       = Math.PI * 2 * value / this.total,
4809       startAngle    = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
4810       endAngle      = startAngle + measure,
4811       bisection     = startAngle + measure / 2,
4812       label         = options.labelFormatter(this.total, value),
4813       //plotTickness  = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;
4814       explodeCoeff  = explode + radius + 4,
4815       distX         = Math.cos(bisection) * explodeCoeff,
4816       distY         = Math.sin(bisection) * explodeCoeff,
4817       textAlign     = distX < 0 ? 'right' : 'left',
4818       textBaseline  = distY > 0 ? 'top' : 'bottom',
4819       style,
4820       x, y;
4821     
4822     context.save();
4823     context.translate(width / 2, height / 2);
4824     context.scale(1, vScale);
4825
4826     x = Math.cos(bisection) * explode;
4827     y = Math.sin(bisection) * explode;
4828
4829     // Shadows
4830     if (shadowSize > 0) {
4831       this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context);
4832       if (fill) {
4833         context.fillStyle = 'rgba(0,0,0,0.1)';
4834         context.fill();
4835       }
4836     }
4837
4838     this.plotSlice(x, y, radius, startAngle, endAngle, context);
4839     if (fill) {
4840       context.fillStyle = fillStyle;
4841       context.fill();
4842     }
4843     context.lineWidth = lineWidth;
4844     context.strokeStyle = color;
4845     context.stroke();
4846
4847     style = {
4848       size : options.fontSize * 1.2,
4849       color : options.fontColor,
4850       weight : 1.5
4851     };
4852
4853     if (label) {
4854       if (options.htmlText || !options.textEnabled) {
4855         divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;';
4856         divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;';
4857         html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>');
4858       }
4859       else {
4860         style.textAlign = textAlign;
4861         style.textBaseline = textBaseline;
4862         Flotr.drawText(context, label, distX, distY, style);
4863       }
4864     }
4865     
4866     if (options.htmlText || !options.textEnabled) {
4867       var div = Flotr.DOM.node('<div style="color:' + options.fontColor + '" class="flotr-labels"></div>');
4868       Flotr.DOM.insert(div, html.join(''));
4869       Flotr.DOM.insert(options.element, div);
4870     }
4871     
4872     context.restore();
4873
4874     // New start angle
4875     this.startAngle = endAngle;
4876     this.slices = this.slices || [];
4877     this.slices.push({
4878       radius : radius,
4879       x : x,
4880       y : y,
4881       explode : explode,
4882       start : startAngle,
4883       end : endAngle
4884     });
4885   },
4886   plotSlice : function (x, y, radius, startAngle, endAngle, context) {
4887     context.beginPath();
4888     context.moveTo(x, y);
4889     context.arc(x, y, radius, startAngle, endAngle, false);
4890     context.lineTo(x, y);
4891     context.closePath();
4892   },
4893   hit : function (options) {
4894
4895     var
4896       data      = options.data[0],
4897       args      = options.args,
4898       index     = options.index,
4899       mouse     = args[0],
4900       n         = args[1],
4901       slice     = this.slices[index],
4902       x         = mouse.relX - options.width / 2,
4903       y         = mouse.relY - options.height / 2,
4904       r         = Math.sqrt(x * x + y * y),
4905       theta     = Math.atan(y / x),
4906       circle    = Math.PI * 2,
4907       explode   = slice.explode || options.explode,
4908       start     = slice.start % circle,
4909       end       = slice.end % circle,
4910       epsilon   = options.epsilon;
4911
4912     if (x < 0) {
4913       theta += Math.PI;
4914     } else if (x > 0 && y < 0) {
4915       theta += circle;
4916     }
4917
4918     if (r < slice.radius + explode && r > explode) {
4919       if (
4920           (theta > start && theta < end) || // Normal Slice
4921           (start > end && (theta < end || theta > start)) || // First slice
4922           // TODO: Document the two cases at the end:
4923           (start === end && ((slice.start === slice.end && Math.abs(theta - start) < epsilon) || (slice.start !== slice.end && Math.abs(theta-start) > epsilon)))
4924          ) {
4925           
4926           // TODO Decouple this from hit plugin (chart shouldn't know what n means)
4927          n.x = data[0];
4928          n.y = data[1];
4929          n.sAngle = start;
4930          n.eAngle = end;
4931          n.index = 0;
4932          n.seriesIndex = index;
4933          n.fraction = data[1] / this.total;
4934       }
4935     }
4936   },
4937   drawHit: function (options) {
4938     var
4939       context = options.context,
4940       slice = this.slices[options.args.seriesIndex];
4941
4942     context.save();
4943     context.translate(options.width / 2, options.height / 2);
4944     this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context);
4945     context.stroke();
4946     context.restore();
4947   },
4948   clearHit : function (options) {
4949     var
4950       context = options.context,
4951       slice = this.slices[options.args.seriesIndex],
4952       padding = 2 * options.lineWidth,
4953       radius = slice.radius + padding;
4954
4955     context.save();
4956     context.translate(options.width / 2, options.height / 2);
4957     context.clearRect(
4958       slice.x - radius,
4959       slice.y - radius,
4960       2 * radius + padding,
4961       2 * radius + padding 
4962     );
4963     context.restore();
4964   },
4965   extendYRange : function (axis, data) {
4966     this.total = (this.total || 0) + data[0][1];
4967   }
4968 });
4969 })();
4970
4971 /** Points **/
4972 Flotr.addType('points', {
4973   options: {
4974     show: false,           // => setting to true will show points, false will hide
4975     radius: 3,             // => point radius (pixels)
4976     lineWidth: 2,          // => line width in pixels
4977     fill: true,            // => true to fill the points with a color, false for (transparent) no fill
4978     fillColor: '#FFFFFF',  // => fill color.  Null to use series color.
4979     fillOpacity: 1,        // => opacity of color inside the points
4980     hitRadius: null        // => override for points hit radius
4981   },
4982
4983   draw : function (options) {
4984     var
4985       context     = options.context,
4986       lineWidth   = options.lineWidth,
4987       shadowSize  = options.shadowSize;
4988
4989     context.save();
4990
4991     if (shadowSize > 0) {
4992       context.lineWidth = shadowSize / 2;
4993       
4994       context.strokeStyle = 'rgba(0,0,0,0.1)';
4995       this.plot(options, shadowSize / 2 + context.lineWidth / 2);
4996
4997       context.strokeStyle = 'rgba(0,0,0,0.2)';
4998       this.plot(options, context.lineWidth / 2);
4999     }
5000
5001     context.lineWidth = options.lineWidth;
5002     context.strokeStyle = options.color;
5003     if (options.fill) context.fillStyle = options.fillStyle;
5004
5005     this.plot(options);
5006     context.restore();
5007   },
5008
5009   plot : function (options, offset) {
5010     var
5011       data    = options.data,
5012       context = options.context,
5013       xScale  = options.xScale,
5014       yScale  = options.yScale,
5015       i, x, y;
5016       
5017     for (i = data.length - 1; i > -1; --i) {
5018       y = data[i][1];
5019       if (y === null) continue;
5020
5021       x = xScale(data[i][0]);
5022       y = yScale(y);
5023
5024       if (x < 0 || x > options.width || y < 0 || y > options.height) continue;
5025       
5026       context.beginPath();
5027       if (offset) {
5028         context.arc(x, y + offset, options.radius, 0, Math.PI, false);
5029       } else {
5030         context.arc(x, y, options.radius, 0, 2 * Math.PI, true);
5031         if (options.fill) context.fill();
5032       }
5033       context.stroke();
5034       context.closePath();
5035     }
5036   }
5037 });
5038
5039 /** Radar **/
5040 Flotr.addType('radar', {
5041   options: {
5042     show: false,           // => setting to true will show radar chart, false will hide
5043     lineWidth: 2,          // => line width in pixels
5044     fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill
5045     fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
5046     radiusRatio: 0.90,      // => ratio of the radar, against the plot size
5047     sensibility: 2         // => the lower this number, the more precise you have to aim to show a value.
5048   },
5049   draw : function (options) {
5050     var
5051       context = options.context,
5052       shadowSize = options.shadowSize;
5053
5054     context.save();
5055     context.translate(options.width / 2, options.height / 2);
5056     context.lineWidth = options.lineWidth;
5057     
5058     // Shadow
5059     context.fillStyle = 'rgba(0,0,0,0.05)';
5060     context.strokeStyle = 'rgba(0,0,0,0.05)';
5061     this.plot(options, shadowSize / 2);
5062     context.strokeStyle = 'rgba(0,0,0,0.1)';
5063     this.plot(options, shadowSize / 4);
5064
5065     // Chart
5066     context.strokeStyle = options.color;
5067     context.fillStyle = options.fillStyle;
5068     this.plot(options);
5069     
5070     context.restore();
5071   },
5072   plot : function (options, offset) {
5073     var
5074       data    = options.data,
5075       context = options.context,
5076       radius  = Math.min(options.height, options.width) * options.radiusRatio / 2,
5077       step    = 2 * Math.PI / data.length,
5078       angle   = -Math.PI / 2,
5079       i, ratio;
5080
5081     offset = offset || 0;
5082
5083     context.beginPath();
5084     for (i = 0; i < data.length; ++i) {
5085       ratio = data[i][1] / this.max;
5086
5087       context[i === 0 ? 'moveTo' : 'lineTo'](
5088         Math.cos(i * step + angle) * radius * ratio + offset,
5089         Math.sin(i * step + angle) * radius * ratio + offset
5090       );
5091     }
5092     context.closePath();
5093     if (options.fill) context.fill();
5094     context.stroke();
5095   },
5096   getGeometry : function (point, options) {
5097     var
5098       radius  = Math.min(options.height, options.width) * options.radiusRatio / 2,
5099       step    = 2 * Math.PI / options.data.length,
5100       angle   = -Math.PI / 2,
5101       ratio = point[1] / this.max;
5102
5103     return {
5104       x : (Math.cos(point[0] * step + angle) * radius * ratio) + options.width / 2,
5105       y : (Math.sin(point[0] * step + angle) * radius * ratio) + options.height / 2
5106     };
5107   },
5108   hit : function (options) {
5109     var
5110       args = options.args,
5111       mouse = args[0],
5112       n = args[1],
5113       relX = mouse.relX,
5114       relY = mouse.relY,
5115       distance,
5116       geometry,
5117       dx, dy;
5118
5119       for (var i = 0; i < n.series.length; i++) {
5120         var serie = n.series[i];
5121         var data = serie.data;
5122
5123         for (var j = data.length; j--;) {
5124           geometry = this.getGeometry(data[j], options);
5125
5126           dx = geometry.x - relX;
5127           dy = geometry.y - relY;
5128           distance = Math.sqrt(dx * dx + dy * dy);
5129
5130           if (distance <  options.sensibility*2) {
5131             n.x = data[j][0];
5132             n.y = data[j][1];
5133             n.index = j;
5134             n.seriesIndex = i;
5135             return n;
5136           }
5137         }
5138       }
5139     },
5140   drawHit : function (options) {
5141     var step = 2 * Math.PI / options.data.length;
5142     var angle   = -Math.PI / 2;
5143     var radius  = Math.min(options.height, options.width) * options.radiusRatio / 2;
5144
5145     var s = options.args.series;
5146     var point_radius = s.points.hitRadius || s.points.radius || s.mouse.radius;
5147
5148     var context = options.context;
5149
5150     context.translate(options.width / 2, options.height / 2);
5151
5152     var j = options.args.index;
5153     var ratio = options.data[j][1] / this.max;
5154     var x = Math.cos(j * step + angle) * radius * ratio;
5155     var y = Math.sin(j * step + angle) * radius * ratio;
5156     context.beginPath();
5157     context.arc(x, y, point_radius , 0, 2 * Math.PI, true);
5158     context.closePath();
5159     context.stroke();
5160   },
5161   clearHit : function (options) {
5162     var step = 2 * Math.PI / options.data.length;
5163     var angle   = -Math.PI / 2;
5164     var radius  = Math.min(options.height, options.width) * options.radiusRatio / 2;
5165
5166     var context = options.context;
5167
5168     var
5169         s = options.args.series,
5170         lw = (s.points ? s.points.lineWidth : 1);
5171         offset = (s.points.hitRadius || s.points.radius || s.mouse.radius) + lw;
5172
5173     context.translate(options.width / 2, options.height / 2);
5174
5175     var j = options.args.index;
5176     var ratio = options.data[j][1] / this.max;
5177     var x = Math.cos(j * step + angle) * radius * ratio;
5178     var y = Math.sin(j * step + angle) * radius * ratio;
5179     context.clearRect(x-offset,y-offset,offset*2,offset*2);
5180   },
5181   extendYRange : function (axis, data) {
5182     this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE);
5183   }
5184 });
5185
5186 Flotr.addType('timeline', {
5187   options: {
5188     show: false,
5189     lineWidth: 1,
5190     barWidth: 0.2,
5191     fill: true,
5192     fillColor: null,
5193     fillOpacity: 0.4,
5194     centered: true
5195   },
5196
5197   draw : function (options) {
5198
5199     var
5200       context = options.context;
5201
5202     context.save();
5203     context.lineJoin    = 'miter';
5204     context.lineWidth   = options.lineWidth;
5205     context.strokeStyle = options.color;
5206     context.fillStyle   = options.fillStyle;
5207
5208     this.plot(options);
5209
5210     context.restore();
5211   },
5212
5213   plot : function (options) {
5214
5215     var
5216       data      = options.data,
5217       context   = options.context,
5218       xScale    = options.xScale,
5219       yScale    = options.yScale,
5220       barWidth  = options.barWidth,
5221       lineWidth = options.lineWidth,
5222       i;
5223
5224     Flotr._.each(data, function (timeline) {
5225
5226       var 
5227         x   = timeline[0],
5228         y   = timeline[1],
5229         w   = timeline[2],
5230         h   = barWidth,
5231
5232         xt  = Math.ceil(xScale(x)),
5233         wt  = Math.ceil(xScale(x + w)) - xt,
5234         yt  = Math.round(yScale(y)),
5235         ht  = Math.round(yScale(y - h)) - yt,
5236
5237         x0  = xt - lineWidth / 2,
5238         y0  = Math.round(yt - ht / 2) - lineWidth / 2;
5239
5240       context.strokeRect(x0, y0, wt, ht);
5241       context.fillRect(x0, y0, wt, ht);
5242
5243     });
5244   },
5245
5246   extendRange : function (series) {
5247
5248     var
5249       data  = series.data,
5250       xa    = series.xaxis,
5251       ya    = series.yaxis,
5252       w     = series.timeline.barWidth;
5253
5254     if (xa.options.min === null)
5255       xa.min = xa.datamin - w / 2;
5256
5257     if (xa.options.max === null) {
5258
5259       var
5260         max = xa.max;
5261
5262       Flotr._.each(data, function (timeline) {
5263         max = Math.max(max, timeline[0] + timeline[2]);
5264       }, this);
5265
5266       xa.max = max + w / 2;
5267     }
5268
5269     if (ya.options.min === null)
5270       ya.min = ya.datamin - w;
5271     if (ya.options.min === null)
5272       ya.max = ya.datamax + w;
5273   }
5274
5275 });
5276
5277 (function () {
5278
5279 var D = Flotr.DOM;
5280
5281 Flotr.addPlugin('crosshair', {
5282   options: {
5283     mode: null,            // => one of null, 'x', 'y' or 'xy'
5284     color: '#FF0000',      // => crosshair color
5285     hideCursor: true       // => hide the cursor when the crosshair is shown
5286   },
5287   callbacks: {
5288     'flotr:mousemove': function(e, pos) {
5289       if (this.options.crosshair.mode) {
5290         this.crosshair.clearCrosshair();
5291         this.crosshair.drawCrosshair(pos);
5292       }
5293     }
5294   },
5295   /**   
5296    * Draws the selection box.
5297    */
5298   drawCrosshair: function(pos) {
5299     var octx = this.octx,
5300       options = this.options.crosshair,
5301       plotOffset = this.plotOffset,
5302       x = plotOffset.left + Math.round(pos.relX) + 0.5,
5303       y = plotOffset.top + Math.round(pos.relY) + 0.5;
5304     
5305     if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) {
5306       this.el.style.cursor = null;
5307       D.removeClass(this.el, 'flotr-crosshair');
5308       return; 
5309     }
5310     
5311     if (options.hideCursor) {
5312       this.el.style.cursor = 'none';
5313       D.addClass(this.el, 'flotr-crosshair');
5314     }
5315     
5316     octx.save();
5317     octx.strokeStyle = options.color;
5318     octx.lineWidth = 1;
5319     octx.beginPath();
5320     
5321     if (options.mode.indexOf('x') != -1) {
5322       octx.moveTo(x, plotOffset.top);
5323       octx.lineTo(x, plotOffset.top + this.plotHeight);
5324     }
5325     
5326     if (options.mode.indexOf('y') != -1) {
5327       octx.moveTo(plotOffset.left, y);
5328       octx.lineTo(plotOffset.left + this.plotWidth, y);
5329     }
5330     
5331     octx.stroke();
5332     octx.restore();
5333   },
5334   /**
5335    * Removes the selection box from the overlay canvas.
5336    */
5337   clearCrosshair: function() {
5338
5339     var
5340       plotOffset = this.plotOffset,
5341       position = this.lastMousePos,
5342       context = this.octx;
5343
5344     if (position) {
5345       context.clearRect(
5346         Math.round(position.relX) + plotOffset.left,
5347         plotOffset.top,
5348         1,
5349         this.plotHeight + 1
5350       );
5351       context.clearRect(
5352         plotOffset.left,
5353         Math.round(position.relY) + plotOffset.top,
5354         this.plotWidth + 1,
5355         1
5356       );    
5357     }
5358   }
5359 });
5360 })();
5361
5362 (function() {
5363
5364 var
5365   D = Flotr.DOM,
5366   _ = Flotr._;
5367
5368 function getImage (type, canvas, context, width, height, background) {
5369
5370   // TODO add scaling for w / h
5371   var
5372     mime = 'image/'+type,
5373     data = context.getImageData(0, 0, width, height),
5374     image = new Image();
5375
5376   context.save();
5377   context.globalCompositeOperation = 'destination-over';
5378   context.fillStyle = background;
5379   context.fillRect(0, 0, width, height);
5380   image.src = canvas.toDataURL(mime);
5381   context.restore();
5382
5383   context.clearRect(0, 0, width, height);
5384   context.putImageData(data, 0, 0);
5385
5386   return image;
5387 }
5388
5389 Flotr.addPlugin('download', {
5390
5391   saveImage: function (type, width, height, replaceCanvas) {
5392     var
5393       grid = this.options.grid,
5394       image;
5395
5396     if (Flotr.isIE && Flotr.isIE < 9) {
5397       image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>';
5398       return window.open().document.write(image);
5399     }
5400
5401     if (type !== 'jpeg' && type !== 'png') return;
5402
5403     image = getImage(
5404       type, this.canvas, this.ctx,
5405       this.canvasWidth, this.canvasHeight,
5406       grid && grid.backgroundColor || '#ffffff'
5407     );
5408
5409     if (_.isElement(image) && replaceCanvas) {
5410       this.download.restoreCanvas();
5411       D.hide(this.canvas);
5412       D.hide(this.overlay);
5413       D.setStyles({position: 'absolute'});
5414       D.insert(this.el, image);
5415       this.saveImageElement = image;
5416     } else {
5417       return window.open(image.src);
5418     }
5419   },
5420
5421   restoreCanvas: function() {
5422     D.show(this.canvas);
5423     D.show(this.overlay);
5424     if (this.saveImageElement) this.el.removeChild(this.saveImageElement);
5425     this.saveImageElement = null;
5426   }
5427 });
5428
5429 })();
5430
5431 (function () {
5432
5433 var E = Flotr.EventAdapter,
5434     _ = Flotr._;
5435
5436 Flotr.addPlugin('graphGrid', {
5437
5438   callbacks: {
5439     'flotr:beforedraw' : function () {
5440       this.graphGrid.drawGrid();
5441     },
5442     'flotr:afterdraw' : function () {
5443       this.graphGrid.drawOutline();
5444     }
5445   },
5446
5447   drawGrid: function(){
5448
5449     var
5450       ctx = this.ctx,
5451       options = this.options,
5452       grid = options.grid,
5453       verticalLines = grid.verticalLines,
5454       horizontalLines = grid.horizontalLines,
5455       minorVerticalLines = grid.minorVerticalLines,
5456       minorHorizontalLines = grid.minorHorizontalLines,
5457       plotHeight = this.plotHeight,
5458       plotWidth = this.plotWidth,
5459       a, v, i, j;
5460         
5461     if(verticalLines || minorVerticalLines || 
5462            horizontalLines || minorHorizontalLines){
5463       E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]);
5464     }
5465     ctx.save();
5466     ctx.lineWidth = 1;
5467     ctx.strokeStyle = grid.tickColor;
5468     
5469     function circularHorizontalTicks (ticks) {
5470       for(i = 0; i < ticks.length; ++i){
5471         var ratio = ticks[i].v / a.max;
5472         for(j = 0; j <= sides; ++j){
5473           ctx[j === 0 ? 'moveTo' : 'lineTo'](
5474             Math.cos(j*coeff+angle)*radius*ratio,
5475             Math.sin(j*coeff+angle)*radius*ratio
5476           );
5477         }
5478       }
5479     }
5480     function drawGridLines (ticks, callback) {
5481       _.each(_.pluck(ticks, 'v'), function(v){
5482         // Don't show lines on upper and lower bounds.
5483         if ((v <= a.min || v >= a.max) || 
5484             (v == a.min || v == a.max) && grid.outlineWidth)
5485           return;
5486         callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2);
5487       });
5488     }
5489     function drawVerticalLines (x) {
5490       ctx.moveTo(x, 0);
5491       ctx.lineTo(x, plotHeight);
5492     }
5493     function drawHorizontalLines (y) {
5494       ctx.moveTo(0, y);
5495       ctx.lineTo(plotWidth, y);
5496     }
5497
5498     if (grid.circular) {
5499       ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2);
5500       var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2,
5501           sides = this.axes.x.ticks.length,
5502           coeff = 2*(Math.PI/sides),
5503           angle = -Math.PI/2;
5504       
5505       // Draw grid lines in vertical direction.
5506       ctx.beginPath();
5507       
5508       a = this.axes.y;
5509
5510       if(horizontalLines){
5511         circularHorizontalTicks(a.ticks);
5512       }
5513       if(minorHorizontalLines){
5514         circularHorizontalTicks(a.minorTicks);
5515       }
5516       
5517       if(verticalLines){
5518         _.times(sides, function(i){
5519           ctx.moveTo(0, 0);
5520           ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
5521         });
5522       }
5523       ctx.stroke();
5524     }
5525     else {
5526       ctx.translate(this.plotOffset.left, this.plotOffset.top);
5527   
5528       // Draw grid background, if present in options.
5529       if(grid.backgroundColor){
5530         ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight});
5531         ctx.fillRect(0, 0, plotWidth, plotHeight);
5532       }
5533       
5534       ctx.beginPath();
5535
5536       a = this.axes.x;
5537       if (verticalLines)        drawGridLines(a.ticks, drawVerticalLines);
5538       if (minorVerticalLines)   drawGridLines(a.minorTicks, drawVerticalLines);
5539
5540       a = this.axes.y;
5541       if (horizontalLines)      drawGridLines(a.ticks, drawHorizontalLines);
5542       if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines);
5543
5544       ctx.stroke();
5545     }
5546     
5547     ctx.restore();
5548     if(verticalLines || minorVerticalLines ||
5549        horizontalLines || minorHorizontalLines){
5550       E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]);
5551     }
5552   }, 
5553
5554   drawOutline: function(){
5555     var
5556       that = this,
5557       options = that.options,
5558       grid = options.grid,
5559       outline = grid.outline,
5560       ctx = that.ctx,
5561       backgroundImage = grid.backgroundImage,
5562       plotOffset = that.plotOffset,
5563       leftOffset = plotOffset.left,
5564       topOffset = plotOffset.top,
5565       plotWidth = that.plotWidth,
5566       plotHeight = that.plotHeight,
5567       v, img, src, left, top, globalAlpha;
5568     
5569     if (!grid.outlineWidth) return;
5570     
5571     ctx.save();
5572     
5573     if (grid.circular) {
5574       ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2);
5575       var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2,
5576           sides = this.axes.x.ticks.length,
5577           coeff = 2*(Math.PI/sides),
5578           angle = -Math.PI/2;
5579       
5580       // Draw axis/grid border.
5581       ctx.beginPath();
5582       ctx.lineWidth = grid.outlineWidth;
5583       ctx.strokeStyle = grid.color;
5584       ctx.lineJoin = 'round';
5585       
5586       for(i = 0; i <= sides; ++i){
5587         ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
5588       }
5589       //ctx.arc(0, 0, radius, 0, Math.PI*2, true);
5590
5591       ctx.stroke();
5592     }
5593     else {
5594       ctx.translate(leftOffset, topOffset);
5595       
5596       // Draw axis/grid border.
5597       var lw = grid.outlineWidth,
5598           orig = 0.5-lw+((lw+1)%2/2),
5599           lineTo = 'lineTo',
5600           moveTo = 'moveTo';
5601       ctx.lineWidth = lw;
5602       ctx.strokeStyle = grid.color;
5603       ctx.lineJoin = 'miter';
5604       ctx.beginPath();
5605       ctx.moveTo(orig, orig);
5606       plotWidth = plotWidth - (lw / 2) % 1;
5607       plotHeight = plotHeight + lw / 2;
5608       ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig);
5609       ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight);
5610       ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight);
5611       ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig);
5612       ctx.stroke();
5613       ctx.closePath();
5614     }
5615     
5616     ctx.restore();
5617
5618     if (backgroundImage) {
5619
5620       src = backgroundImage.src || backgroundImage;
5621       left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left;
5622       top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top;
5623       img = new Image();
5624
5625       img.onload = function() {
5626         ctx.save();
5627         if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha;
5628         ctx.globalCompositeOperation = 'destination-over';
5629         ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight);
5630         ctx.restore();
5631       };
5632
5633       img.src = src;
5634     }
5635   }
5636 });
5637
5638 })();
5639
5640 (function () {
5641
5642 var
5643   D = Flotr.DOM,
5644   _ = Flotr._,
5645   flotr = Flotr,
5646   S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
5647
5648 Flotr.addPlugin('hit', {
5649   callbacks: {
5650     'flotr:mousemove': function(e, pos) {
5651       this.hit.track(pos);
5652     },
5653     'flotr:click': function(pos) {
5654       var
5655         hit = this.hit.track(pos);
5656       if (hit && !_.isUndefined(hit.index)) pos.hit = hit;
5657     },
5658     'flotr:mouseout': function(e) {
5659       if (e.relatedTarget !== this.mouseTrack) {
5660         this.hit.clearHit();
5661       }
5662     },
5663     'flotr:destroy': function() {
5664       if (this.options.mouse.container) {
5665         D.remove(this.mouseTrack);
5666       }
5667       this.mouseTrack = null;
5668     }
5669   },
5670   track : function (pos) {
5671     if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) {
5672       return this.hit.hit(pos);
5673     }
5674   },
5675   /**
5676    * Try a method on a graph type.  If the method exists, execute it.
5677    * @param {Object} series
5678    * @param {String} method  Method name.
5679    * @param {Array} args  Arguments applied to method.
5680    * @return executed successfully or failed.
5681    */
5682   executeOnType: function(s, method, args){
5683     var
5684       success = false,
5685       options;
5686
5687     if (!_.isArray(s)) s = [s];
5688
5689     function e(s, index) {
5690       _.each(_.keys(flotr.graphTypes), function (type) {
5691         if (s[type] && s[type].show && !s.hide && this[type][method]) {
5692           options = this.getOptions(s, type);
5693
5694           options.fill = !!s.mouse.fillColor;
5695           options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
5696           options.color = s.mouse.lineColor;
5697           options.context = this.octx;
5698           options.index = index;
5699
5700           if (args) options.args = args;
5701           this[type][method].call(this[type], options);
5702           success = true;
5703         }
5704       }, this);
5705     }
5706     _.each(s, e, this);
5707
5708     return success;
5709   },
5710   /**
5711    * Updates the mouse tracking point on the overlay.
5712    */
5713   drawHit: function(n){
5714     var octx = this.octx,
5715       s = n.series;
5716
5717     if (s.mouse.lineColor) {
5718       octx.save();
5719       octx.lineWidth = (s.points ? s.points.lineWidth : 1);
5720       octx.strokeStyle = s.mouse.lineColor;
5721       octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
5722       octx.translate(this.plotOffset.left, this.plotOffset.top);
5723
5724       if (!this.hit.executeOnType(s, 'drawHit', n)) {
5725         var
5726           xa = n.xaxis,
5727           ya = n.yaxis;
5728
5729         octx.beginPath();
5730           // TODO fix this (points) should move to general testable graph mixin
5731           octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.hitRadius || s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true);
5732           octx.fill();
5733           octx.stroke();
5734         octx.closePath();
5735       }
5736       octx.restore();
5737       this.clip(octx);
5738     }
5739     this.prevHit = n;
5740   },
5741   /**
5742    * Removes the mouse tracking point from the overlay.
5743    */
5744   clearHit: function(){
5745     var prev = this.prevHit,
5746         octx = this.octx,
5747         plotOffset = this.plotOffset;
5748     octx.save();
5749     octx.translate(plotOffset.left, plotOffset.top);
5750     if (prev) {
5751       if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) {
5752         // TODO fix this (points) should move to general testable graph mixin
5753         var
5754           s = prev.series,
5755           lw = (s.points ? s.points.lineWidth : 1);
5756           offset = (s.points.hitRadius || s.points.radius || s.mouse.radius) + lw;
5757         octx.clearRect(
5758           prev.xaxis.d2p(prev.x) - offset,
5759           prev.yaxis.d2p(prev.y) - offset,
5760           offset*2,
5761           offset*2
5762         );
5763       }
5764       D.hide(this.mouseTrack);
5765       this.prevHit = null;
5766     }
5767     octx.restore();
5768   },
5769   /**
5770    * Retrieves the nearest data point from the mouse cursor. If it's within
5771    * a certain range, draw a point on the overlay canvas and display the x and y
5772    * value of the data.
5773    * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
5774    */
5775   hit : function (mouse) {
5776
5777     var
5778       options = this.options,
5779       prevHit = this.prevHit,
5780       closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis, n;
5781
5782     if (this.series.length === 0) return;
5783
5784     // Nearest data element.
5785     // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse,
5786     // xaxis, yaxis, series, index, seriesIndex
5787     n = {
5788       relX : mouse.relX,
5789       relY : mouse.relY,
5790       absX : mouse.absX,
5791       absY : mouse.absY,
5792       series: this.series
5793     };
5794
5795     if (options.mouse.trackY &&
5796         !options.mouse.trackAll &&
5797         this.hit.executeOnType(this.series, 'hit', [mouse, n]) &&
5798         !_.isUndefined(n.seriesIndex))
5799       {
5800       series    = this.series[n.seriesIndex];
5801       n.series  = series;
5802       n.mouse   = series.mouse;
5803       n.xaxis   = series.xaxis;
5804       n.yaxis   = series.yaxis;
5805     } else {
5806
5807       closest = this.hit.closest(mouse);
5808
5809       if (closest) {
5810
5811         closest     = options.mouse.trackY ? closest.point : closest.x;
5812         seriesIndex = closest.seriesIndex;
5813         series      = this.series[seriesIndex];
5814         xaxis       = series.xaxis;
5815         yaxis       = series.yaxis;
5816         sensibility = 2 * series.mouse.sensibility;
5817
5818         if
5819           (options.mouse.trackAll ||
5820           (closest.distanceX < sensibility / xaxis.scale &&
5821           (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale)))
5822         {
5823           n.series      = series;
5824           n.xaxis       = series.xaxis;
5825           n.yaxis       = series.yaxis;
5826           n.mouse       = series.mouse;
5827           n.x           = closest.x;
5828           n.y           = closest.y;
5829           n.dist        = closest.distance;
5830           n.index       = closest.dataIndex;
5831           n.seriesIndex = seriesIndex;
5832         }
5833       }
5834     }
5835
5836     if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) {
5837       this.hit.clearHit();
5838       if (n.series && n.mouse && n.mouse.track) {
5839         this.hit.drawMouseTrack(n);
5840         this.hit.drawHit(n);
5841         Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]);
5842       }
5843     }
5844
5845     return n;
5846   },
5847
5848   closest : function (mouse) {
5849
5850     var
5851       series    = this.series,
5852       options   = this.options,
5853       relX      = mouse.relX,
5854       relY      = mouse.relY,
5855       compare   = Number.MAX_VALUE,
5856       compareX  = Number.MAX_VALUE,
5857       closest   = {},
5858       closestX  = {},
5859       check     = false,
5860       serie, data,
5861       distance, distanceX, distanceY,
5862       mouseX, mouseY,
5863       x, y, i, j;
5864
5865     function setClosest (o) {
5866       o.distance = distance;
5867       o.distanceX = distanceX;
5868       o.distanceY = distanceY;
5869       o.seriesIndex = i;
5870       o.dataIndex = j;
5871       o.x = x;
5872       o.y = y;
5873       check = true;
5874     }
5875
5876     for (i = 0; i < series.length; i++) {
5877
5878       serie = series[i];
5879       data = serie.data;
5880       mouseX = serie.xaxis.p2d(relX);
5881       mouseY = serie.yaxis.p2d(relY);
5882
5883       if (serie.hide) continue;
5884
5885       for (j = data.length; j--;) {
5886
5887         x = data[j][0];
5888         y = data[j][1];
5889         // Add stack offset if exists
5890         if (data[j].y0) y += data[j].y0;
5891
5892         if (x === null || y === null) continue;
5893
5894         // don't check if the point isn't visible in the current range
5895         if (x < serie.xaxis.min || x > serie.xaxis.max) continue;
5896
5897         distanceX = Math.abs(x - mouseX);
5898         distanceY = Math.abs(y - mouseY);
5899
5900         // Skip square root for speed
5901         distance = distanceX * distanceX + distanceY * distanceY;
5902
5903         if (distance < compare) {
5904           compare = distance;
5905           setClosest(closest);
5906         }
5907
5908         if (distanceX < compareX) {
5909           compareX = distanceX;
5910           setClosest(closestX);
5911         }
5912       }
5913     }
5914
5915     return check ? {
5916       point : closest,
5917       x : closestX
5918     } : false;
5919   },
5920
5921   drawMouseTrack : function (n) {
5922
5923     var
5924       pos         = '', 
5925       s           = n.series,
5926       p           = n.mouse.position, 
5927       m           = n.mouse.margin,
5928       x           = n.x,
5929       y           = n.y,
5930       elStyle     = S_MOUSETRACK,
5931       mouseTrack  = this.mouseTrack,
5932       plotOffset  = this.plotOffset,
5933       left        = plotOffset.left,
5934       right       = plotOffset.right,
5935       bottom      = plotOffset.bottom,
5936       top         = plotOffset.top,
5937       decimals    = n.mouse.trackDecimals,
5938       options     = this.options,
5939       container   = options.mouse.container,
5940       oTop        = 0,
5941       oLeft       = 0,
5942       offset, size, content;
5943
5944     // Create
5945     if (!mouseTrack) {
5946       mouseTrack = D.node('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');
5947       this.mouseTrack = mouseTrack;
5948       D.insert(container || this.el, mouseTrack);
5949     }
5950
5951     // Fill tracker:
5952     if (!decimals || decimals < 0) decimals = 0;
5953     if (x && x.toFixed) x = x.toFixed(decimals);
5954     if (y && y.toFixed) y = y.toFixed(decimals);
5955     content = n.mouse.trackFormatter({
5956       x: x,
5957       y: y,
5958       series: n.series,
5959       index: n.index,
5960       nearest: n,
5961       fraction: n.fraction
5962     });
5963     if (_.isNull(content) || _.isUndefined(content)) {
5964       D.hide(mouseTrack);
5965       return;
5966     } else {
5967       mouseTrack.innerHTML = content;
5968       D.show(mouseTrack);
5969     }
5970
5971     // Positioning
5972     if (!p) {
5973       return;
5974     }
5975     size = D.size(mouseTrack);
5976     if (container) {
5977       offset = D.position(this.el);
5978       oTop = offset.top;
5979       oLeft = offset.left;
5980     }
5981
5982     if (!n.mouse.relative) { // absolute to the canvas
5983       pos += 'top:';
5984       if      (p.charAt(0) == 'n') pos += (oTop + m + top);
5985       else if (p.charAt(0) == 's') pos += (oTop - m + top + this.plotHeight - size.height);
5986       pos += 'px;bottom:auto;left:';
5987       if      (p.charAt(1) == 'e') pos += (oLeft - m + left + this.plotWidth - size.width);
5988       else if (p.charAt(1) == 'w') pos += (oLeft + m + left);
5989       pos += 'px;right:auto;';
5990
5991     // Pie
5992     } else if (s.pie && s.pie.show) {
5993       var center = {
5994           x: (this.plotWidth)/2,
5995           y: (this.plotHeight)/2
5996         },
5997         radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2,
5998         bisection = n.sAngle<n.eAngle ? (n.sAngle + n.eAngle) / 2: (n.sAngle + n.eAngle + 2* Math.PI) / 2;
5999       
6000       pos += 'bottom:' + (m - top - center.y - Math.sin(bisection) * radius/2 + this.canvasHeight) + 'px;top:auto;';
6001       pos += 'left:' + (m + left + center.x + Math.cos(bisection) * radius/2) + 'px;right:auto;';
6002
6003     // Default
6004     } else {
6005       pos += 'top:';
6006       if (/n/.test(p)) pos += (oTop - m + top + n.yaxis.d2p(n.y) - size.height);
6007       else             pos += (oTop + m + top + n.yaxis.d2p(n.y));
6008       pos += 'px;bottom:auto;left:';
6009       if (/w/.test(p)) pos += (oLeft - m + left + n.xaxis.d2p(n.x) - size.width);
6010       else             pos += (oLeft + m + left + n.xaxis.d2p(n.x));
6011       pos += 'px;right:auto;';
6012     }
6013
6014     // Set position
6015     mouseTrack.style.cssText = elStyle + pos;
6016
6017     if (n.mouse.relative) {
6018       if (!/[ew]/.test(p)) {
6019         // Center Horizontally
6020         mouseTrack.style.left =
6021           (oLeft + left + n.xaxis.d2p(n.x) - D.size(mouseTrack).width / 2) + 'px';
6022       } else
6023       if (!/[ns]/.test(p)) {
6024         // Center Vertically
6025         mouseTrack.style.top =
6026           (oTop + top + n.yaxis.d2p(n.y) - D.size(mouseTrack).height / 2) + 'px';
6027       }
6028     }
6029   }
6030
6031 });
6032 })();
6033
6034 /** 
6035  * Selection Handles Plugin
6036  *
6037  *
6038  * Options
6039  *  show - True enables the handles plugin.
6040  *  drag - Left and Right drag handles
6041  *  scroll - Scrolling handle
6042  */
6043 (function () {
6044
6045 function isLeftClick (e, type) {
6046   return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1));
6047 }
6048
6049 function boundX(x, graph) {
6050   return Math.min(Math.max(0, x), graph.plotWidth - 1);
6051 }
6052
6053 function boundY(y, graph) {
6054   return Math.min(Math.max(0, y), graph.plotHeight);
6055 }
6056
6057 var
6058   D = Flotr.DOM,
6059   E = Flotr.EventAdapter,
6060   _ = Flotr._;
6061
6062
6063 Flotr.addPlugin('selection', {
6064
6065   options: {
6066     pinchOnly: null,       // Only select on pinch
6067     mode: null,            // => one of null, 'x', 'y' or 'xy'
6068     color: '#B6D9FF',      // => selection box color
6069     fps: 20                // => frames-per-second
6070   },
6071
6072   callbacks: {
6073     'flotr:mouseup' : function (event) {
6074
6075       var
6076         options = this.options.selection,
6077         selection = this.selection,
6078         pointer = this.getEventPosition(event);
6079
6080       if (!options || !options.mode) return;
6081       if (selection.interval) clearInterval(selection.interval);
6082
6083       if (this.multitouches) {
6084         selection.updateSelection();
6085       } else
6086       if (!options.pinchOnly) {
6087         selection.setSelectionPos(selection.selection.second, pointer);
6088       }
6089       selection.clearSelection();
6090
6091       if(selection.selecting && selection.selectionIsSane()){
6092         selection.drawSelection();
6093         selection.fireSelectEvent();
6094         this.ignoreClick = true;
6095       }
6096     },
6097     'flotr:mousedown' : function (event) {
6098
6099       var
6100         options = this.options.selection,
6101         selection = this.selection,
6102         pointer = this.getEventPosition(event);
6103
6104       if (!options || !options.mode) return;
6105       if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return;
6106       if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer);
6107       if (selection.interval) clearInterval(selection.interval);
6108
6109       this.lastMousePos.pageX = null;
6110       selection.selecting = false;
6111       selection.interval = setInterval(
6112         _.bind(selection.updateSelection, this),
6113         1000 / options.fps
6114       );
6115     },
6116     'flotr:destroy' : function (event) {
6117       clearInterval(this.selection.interval);
6118     }
6119   },
6120
6121   // TODO This isn't used.  Maybe it belongs in the draw area and fire select event methods?
6122   getArea: function() {
6123
6124     var
6125       s = this.selection.selection,
6126       a = this.axes,
6127       first = s.first,
6128       second = s.second,
6129       x1, x2, y1, y2;
6130
6131     x1 = a.x.p2d(s.first.x);
6132     x2 = a.x.p2d(s.second.x);
6133     y1 = a.y.p2d(s.first.y);
6134     y2 = a.y.p2d(s.second.y);
6135
6136     return {
6137       x1 : Math.min(x1, x2),
6138       y1 : Math.min(y1, y2),
6139       x2 : Math.max(x1, x2),
6140       y2 : Math.max(y1, y2),
6141       xfirst : x1,
6142       xsecond : x2,
6143       yfirst : y1,
6144       ysecond : y2
6145     };
6146   },
6147
6148   selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}},
6149   prevSelection: null,
6150   interval: null,
6151
6152   /**
6153    * Fires the 'flotr:select' event when the user made a selection.
6154    */
6155   fireSelectEvent: function(name){
6156     var
6157       area = this.selection.getArea();
6158     name = name || 'select';
6159     area.selection = this.selection.selection;
6160     E.fire(this.el, 'flotr:'+name, [area, this]);
6161   },
6162
6163   /**
6164    * Allows the user the manually select an area.
6165    * @param {Object} area - Object with coordinates to select.
6166    */
6167   setSelection: function(area, preventEvent){
6168     var options = this.options,
6169       xa = this.axes.x,
6170       ya = this.axes.y,
6171       vertScale = ya.scale,
6172       hozScale = xa.scale,
6173       selX = options.selection.mode.indexOf('x') != -1,
6174       selY = options.selection.mode.indexOf('y') != -1,
6175       s = this.selection.selection;
6176     
6177     this.selection.clearSelection();
6178
6179     s.first.y  = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this);
6180     s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this);
6181     s.first.x  = boundX((selY && !selX) ? 0 : (area.x1 - xa.min) * hozScale, this);
6182     s.second.x = boundX((selY && !selX) ? this.plotWidth : (area.x2 - xa.min) * hozScale, this);
6183     
6184     this.selection.drawSelection();
6185     if (!preventEvent)
6186       this.selection.fireSelectEvent();
6187   },
6188
6189   /**
6190    * Calculates the position of the selection.
6191    * @param {Object} pos - Position object.
6192    * @param {Event} event - Event object.
6193    */
6194   setSelectionPos: function(pos, pointer) {
6195     var mode = this.options.selection.mode,
6196         selection = this.selection.selection;
6197
6198     if(mode.indexOf('x') == -1) {
6199       pos.x = (pos == selection.first) ? 0 : this.plotWidth;         
6200     }else{
6201       pos.x = boundX(pointer.relX, this);
6202     }
6203
6204     if (mode.indexOf('y') == -1) {
6205       pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1;
6206     }else{
6207       pos.y = boundY(pointer.relY, this);
6208     }
6209   },
6210   /**
6211    * Draws the selection box.
6212    */
6213   drawSelection: function() {
6214
6215     this.selection.fireSelectEvent('selecting');
6216
6217     var s = this.selection.selection,
6218       octx = this.octx,
6219       options = this.options,
6220       plotOffset = this.plotOffset,
6221       prevSelection = this.selection.prevSelection;
6222     
6223     if (prevSelection &&
6224       s.first.x == prevSelection.first.x &&
6225       s.first.y == prevSelection.first.y && 
6226       s.second.x == prevSelection.second.x &&
6227       s.second.y == prevSelection.second.y) {
6228       return;
6229     }
6230
6231     octx.save();
6232     octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8});
6233     octx.lineWidth = 1;
6234     octx.lineJoin = 'miter';
6235     octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4});
6236
6237     this.selection.prevSelection = {
6238       first: { x: s.first.x, y: s.first.y },
6239       second: { x: s.second.x, y: s.second.y }
6240     };
6241
6242     var x = Math.min(s.first.x, s.second.x),
6243         y = Math.min(s.first.y, s.second.y),
6244         w = Math.abs(s.second.x - s.first.x),
6245         h = Math.abs(s.second.y - s.first.y);
6246
6247     octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
6248     octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
6249     octx.restore();
6250   },
6251
6252   /**
6253    * Updates (draws) the selection box.
6254    */
6255   updateSelection: function(){
6256     if (!this.lastMousePos.pageX) return;
6257
6258     this.selection.selecting = true;
6259
6260     if (this.multitouches) {
6261       this.selection.setSelectionPos(this.selection.selection.first,  this.getEventPosition(this.multitouches[0]));
6262       this.selection.setSelectionPos(this.selection.selection.second,  this.getEventPosition(this.multitouches[1]));
6263     } else
6264     if (this.options.selection.pinchOnly) {
6265       return;
6266     } else {
6267       this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos);
6268     }
6269
6270     this.selection.clearSelection();
6271     
6272     if(this.selection.selectionIsSane()) {
6273       this.selection.drawSelection();
6274     }
6275   },
6276
6277   /**
6278    * Removes the selection box from the overlay canvas.
6279    */
6280   clearSelection: function() {
6281     if (!this.selection.prevSelection) return;
6282       
6283     var prevSelection = this.selection.prevSelection,
6284       lw = 1,
6285       plotOffset = this.plotOffset,
6286       x = Math.min(prevSelection.first.x, prevSelection.second.x),
6287       y = Math.min(prevSelection.first.y, prevSelection.second.y),
6288       w = Math.abs(prevSelection.second.x - prevSelection.first.x),
6289       h = Math.abs(prevSelection.second.y - prevSelection.first.y);
6290     
6291     this.octx.clearRect(x + plotOffset.left - lw + 0.5,
6292                         y + plotOffset.top - lw,
6293                         w + 2 * lw + 0.5,
6294                         h + 2 * lw + 0.5);
6295     
6296     this.selection.prevSelection = null;
6297   },
6298   /**
6299    * Determines whether or not the selection is sane and should be drawn.
6300    * @return {Boolean} - True when sane, false otherwise.
6301    */
6302   selectionIsSane: function(){
6303     var s = this.selection.selection;
6304     return Math.abs(s.second.x - s.first.x) >= 5 || 
6305            Math.abs(s.second.y - s.first.y) >= 5;
6306   }
6307
6308 });
6309
6310 })();
6311
6312 (function () {
6313
6314 var D = Flotr.DOM;
6315
6316 Flotr.addPlugin('labels', {
6317
6318   callbacks : {
6319     'flotr:afterdraw' : function () {
6320       this.labels.draw();
6321     }
6322   },
6323
6324   draw: function(){
6325     // Construct fixed width label boxes, which can be styled easily.
6326     var
6327       axis, tick, left, top, xBoxWidth,
6328       radius, sides, coeff, angle,
6329       div, i, html = '',
6330       noLabels = 0,
6331       options  = this.options,
6332       ctx      = this.ctx,
6333       a        = this.axes,
6334       style    = { size: options.fontSize };
6335
6336     for (i = 0; i < a.x.ticks.length; ++i){
6337       if (a.x.ticks[i].label) { ++noLabels; }
6338     }
6339     xBoxWidth = this.plotWidth / noLabels;
6340
6341     if (options.grid.circular) {
6342       ctx.save();
6343       ctx.translate(this.plotOffset.left + this.plotWidth / 2,
6344           this.plotOffset.top + this.plotHeight / 2);
6345
6346       radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize;
6347       sides  = this.axes.x.ticks.length;
6348       coeff  = 2 * (Math.PI / sides);
6349       angle  = -Math.PI / 2;
6350
6351       drawLabelCircular(this, a.x, false);
6352       drawLabelCircular(this, a.x, true);
6353       drawLabelCircular(this, a.y, false);
6354       drawLabelCircular(this, a.y, true);
6355       ctx.restore();
6356     }
6357
6358     if (!options.HtmlText && this.textEnabled) {
6359       drawLabelNoHtmlText(this, a.x, 'center', 'top');
6360       drawLabelNoHtmlText(this, a.x2, 'center', 'bottom');
6361       drawLabelNoHtmlText(this, a.y, 'right', 'middle');
6362       drawLabelNoHtmlText(this, a.y2, 'left', 'middle');
6363     
6364     } else if ((
6365         a.x.options.showLabels ||
6366         a.x2.options.showLabels ||
6367         a.y.options.showLabels ||
6368         a.y2.options.showLabels) &&
6369         !options.grid.circular
6370       ) {
6371
6372       html = '';
6373
6374       drawLabelHtml(this, a.x);
6375       drawLabelHtml(this, a.x2);
6376       drawLabelHtml(this, a.y);
6377       drawLabelHtml(this, a.y2);
6378
6379       ctx.stroke();
6380       ctx.restore();
6381       div = D.create('div');
6382       D.setStyles(div, {
6383         fontSize: 'smaller',
6384         color: options.grid.color
6385       });
6386       div.className = 'flotr-labels';
6387       D.insert(this.el, div);
6388       D.insert(div, html);
6389     }
6390
6391     function drawLabelCircular (graph, axis, minorTicks) {
6392       var
6393         ticks   = minorTicks ? axis.minorTicks : axis.ticks,
6394         isX     = axis.orientation === 1,
6395         isFirst = axis.n === 1,
6396         style, offset;
6397
6398       style = {
6399         color        : axis.options.color || options.grid.color,
6400         angle        : Flotr.toRad(axis.options.labelsAngle),
6401         textBaseline : 'middle'
6402       };
6403
6404       for (i = 0; i < ticks.length &&
6405           (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){
6406         tick = ticks[i];
6407         tick.label += '';
6408         if (!tick.label || !tick.label.length) { continue; }
6409
6410         x = Math.cos(i * coeff + angle) * radius;
6411         y = Math.sin(i * coeff + angle) * radius;
6412
6413         style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left';
6414
6415         Flotr.drawText(
6416           ctx, tick.label,
6417           isX ? x : 3,
6418           isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize),
6419           style
6420         );
6421       }
6422     }
6423
6424     function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline)  {
6425       var
6426         isX     = axis.orientation === 1,
6427         isFirst = axis.n === 1,
6428         style, offset;
6429
6430       style = {
6431         color        : axis.options.color || options.grid.color,
6432         textAlign    : textAlign,
6433         textBaseline : textBaseline,
6434         angle : Flotr.toRad(axis.options.labelsAngle)
6435       };
6436       style = Flotr.getBestTextAlign(style.angle, style);
6437
6438       for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) {
6439
6440         tick = axis.ticks[i];
6441         if (!tick.label || !tick.label.length) { continue; }
6442
6443         offset = axis.d2p(tick.v);
6444         if (offset < 0 ||
6445             offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; }
6446
6447         Flotr.drawText(
6448           ctx, tick.label,
6449           leftOffset(graph, isX, isFirst, offset),
6450           topOffset(graph, isX, isFirst, offset),
6451           style
6452         );
6453
6454         // Only draw on axis y2
6455         if (!isX && !isFirst) {
6456           ctx.save();
6457           ctx.strokeStyle = style.color;
6458           ctx.beginPath();
6459           ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v));
6460           ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v));
6461           ctx.stroke();
6462           ctx.restore();
6463         }
6464       }
6465
6466       function continueShowingLabels (axis) {
6467         return axis.options.showLabels && axis.used;
6468       }
6469       function leftOffset (graph, isX, isFirst, offset) {
6470         return graph.plotOffset.left +
6471           (isX ? offset :
6472             (isFirst ?
6473               -options.grid.labelMargin :
6474               options.grid.labelMargin + graph.plotWidth));
6475       }
6476       function topOffset (graph, isX, isFirst, offset) {
6477         return graph.plotOffset.top +
6478           (isX ? options.grid.labelMargin : offset) +
6479           ((isX && isFirst) ? graph.plotHeight : 0);
6480       }
6481     }
6482
6483     function drawLabelHtml (graph, axis) {
6484       var
6485         isX     = axis.orientation === 1,
6486         isFirst = axis.n === 1,
6487         name = '',
6488         left, style, top,
6489         offset = graph.plotOffset;
6490
6491       if (!isX && !isFirst) {
6492         ctx.save();
6493         ctx.strokeStyle = axis.options.color || options.grid.color;
6494         ctx.beginPath();
6495       }
6496
6497       if (axis.options.showLabels && (isFirst ? true : axis.used)) {
6498         for (i = 0; i < axis.ticks.length; ++i) {
6499           tick = axis.ticks[i];
6500           if (!tick.label || !tick.label.length ||
6501               ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) ||
6502               ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) {
6503             continue;
6504           }
6505           top = offset.top +
6506             (isX ?
6507               ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) :
6508               axis.d2p(tick.v) - axis.maxLabel.height / 2);
6509           left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0;
6510
6511           name = '';
6512           if (i === 0) {
6513             name = ' first';
6514           } else if (i === axis.ticks.length - 1) {
6515             name = ' last';
6516           }
6517           name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y';
6518
6519           html += [
6520             '<div style="position:absolute; text-align:' + (isX ? 'center' : 'right') + '; ',
6521             'top:' + top + 'px; ',
6522             ((!isX && !isFirst) ? 'right:' : 'left:') + left + 'px; ',
6523             'width:' + (isX ? xBoxWidth : ((isFirst ? offset.left : offset.right) - options.grid.labelMargin)) + 'px; ',
6524             axis.options.color ? ('color:' + axis.options.color + '; ') : ' ',
6525             '" class="flotr-grid-label' + name + '">' + tick.label + '</div>'
6526           ].join(' ');
6527           
6528           if (!isX && !isFirst) {
6529             ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v));
6530             ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v));
6531           }
6532         }
6533       }
6534     }
6535   }
6536
6537 });
6538 })();
6539
6540 (function () {
6541
6542 var
6543   D = Flotr.DOM,
6544   _ = Flotr._;
6545
6546 Flotr.addPlugin('legend', {
6547   options: {
6548     show: true,            // => setting to true will show the legend, hide otherwise
6549     noColumns: 1,          // => number of colums in legend table // @todo: doesn't work for HtmlText = false
6550     labelFormatter: function(v){return v;}, // => fn: string -> string
6551     labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes
6552     labelBoxWidth: 14,
6553     labelBoxHeight: 10,
6554     labelBoxMargin: 5,
6555     container: null,       // => container (as jQuery object) to put legend in, null means default on top of graph
6556     position: 'nw',        // => position of default legend container within plot
6557     margin: 5,             // => distance from grid edge to default legend container within plot
6558     backgroundColor: '#F0F0F0', // => Legend background color.
6559     backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background
6560   },
6561   callbacks: {
6562     'flotr:afterinit': function() {
6563       this.legend.insertLegend();
6564     },
6565     'flotr:destroy': function() {
6566       var markup = this.legend.markup;
6567       if (markup) {
6568         this.legend.markup = null;
6569         D.remove(markup);
6570       }
6571     }
6572   },
6573   /**
6574    * Adds a legend div to the canvas container or draws it on the canvas.
6575    */
6576   insertLegend: function(){
6577
6578     if(!this.options.legend.show)
6579       return;
6580
6581     var series      = this.series,
6582       plotOffset    = this.plotOffset,
6583       options       = this.options,
6584       legend        = options.legend,
6585       fragments     = [],
6586       rowStarted    = false, 
6587       ctx           = this.ctx,
6588       itemCount     = _.filter(series, function(s) {return (s.label && !s.hide);}).length,
6589       p             = legend.position, 
6590       m             = legend.margin,
6591       opacity       = legend.backgroundOpacity,
6592       i, label, color;
6593
6594     if (itemCount) {
6595
6596       var lbw = legend.labelBoxWidth,
6597           lbh = legend.labelBoxHeight,
6598           lbm = legend.labelBoxMargin,
6599           offsetX = plotOffset.left + m,
6600           offsetY = plotOffset.top + m,
6601           labelMaxWidth = 0,
6602           style = {
6603             size: options.fontSize*1.1,
6604             color: options.grid.color
6605           };
6606
6607       // We calculate the labels' max width
6608       for(i = series.length - 1; i > -1; --i){
6609         if(!series[i].label || series[i].hide) continue;
6610         label = legend.labelFormatter(series[i].label);
6611         labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width);
6612       }
6613
6614       var legendWidth  = Math.round(lbw + lbm*3 + labelMaxWidth),
6615           legendHeight = Math.round(itemCount*(lbm+lbh) + lbm);
6616
6617       // Default Opacity
6618       if (!opacity && opacity !== 0) {
6619         opacity = 0.1;
6620       }
6621
6622       if (!options.HtmlText && this.textEnabled && !legend.container) {
6623         
6624         if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);
6625         if(p.charAt(0) == 'c') offsetY = plotOffset.top + (this.plotHeight/2) - (m + (legendHeight/2));
6626         if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);
6627         
6628         // Legend box
6629         color = this.processColor(legend.backgroundColor, { opacity : opacity });
6630
6631         ctx.fillStyle = color;
6632         ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);
6633         ctx.strokeStyle = legend.labelBoxBorderColor;
6634         ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);
6635         
6636         // Legend labels
6637         var x = offsetX + lbm;
6638         var y = offsetY + lbm;
6639         for(i = 0; i < series.length; i++){
6640           if(!series[i].label || series[i].hide) continue;
6641           label = legend.labelFormatter(series[i].label);
6642           
6643           ctx.fillStyle = series[i].color;
6644           ctx.fillRect(x, y, lbw-1, lbh-1);
6645           
6646           ctx.strokeStyle = legend.labelBoxBorderColor;
6647           ctx.lineWidth = 1;
6648           ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);
6649           
6650           // Legend text
6651           Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style);
6652           
6653           y += lbh + lbm;
6654         }
6655       }
6656       else {
6657         for(i = 0; i < series.length; ++i){
6658           if(!series[i].label || series[i].hide) continue;
6659           
6660           if(i % legend.noColumns === 0){
6661             fragments.push(rowStarted ? '</tr><tr>' : '<tr>');
6662             rowStarted = true;
6663           }
6664
6665           var s = series[i],
6666             boxWidth = legend.labelBoxWidth,
6667             boxHeight = legend.labelBoxHeight;
6668
6669           label = legend.labelFormatter(s.label);
6670           color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';';
6671           
6672           fragments.push(
6673             '<td class="flotr-legend-color-box">',
6674               '<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">',
6675                 '<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border
6676                   '<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', color, '"></div>', // Background
6677                 '</div>',
6678               '</div>',
6679             '</td>',
6680             '<td class="flotr-legend-label">', label, '</td>'
6681           );
6682         }
6683         if(rowStarted) fragments.push('</tr>');
6684           
6685         if(fragments.length > 0){
6686           var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>';
6687           if(legend.container){
6688             table = D.node(table);
6689             this.legend.markup = table;
6690             D.insert(legend.container, table);
6691           }
6692           else {
6693             var styles = {position: 'absolute', 'zIndex': '2', 'border' : '1px solid ' + legend.labelBoxBorderColor};
6694
6695                  if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; }
6696             else if(p.charAt(0) == 'c') { styles.top = (m + (this.plotHeight - legendHeight) / 2) + 'px'; styles.bottom = 'auto'; }
6697             else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; }
6698                  if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; }
6699             else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; }
6700
6701             var div = D.create('div'), size;
6702             div.className = 'flotr-legend';
6703             D.setStyles(div, styles);
6704             D.insert(div, table);
6705             D.insert(this.el, div);
6706             
6707             if (!opacity) return;
6708
6709             var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff';
6710
6711             _.extend(styles, D.size(div), {
6712               'backgroundColor': c,
6713               'zIndex' : '',
6714               'border' : ''
6715             });
6716             styles.width += 'px';
6717             styles.height += 'px';
6718
6719              // Put in the transparent background separately to avoid blended labels and
6720             div = D.create('div');
6721             div.className = 'flotr-legend-bg';
6722             D.setStyles(div, styles);
6723             D.opacity(div, opacity);
6724             D.insert(div, ' ');
6725             D.insert(this.el, div);
6726           }
6727         }
6728       }
6729     }
6730   }
6731 });
6732 })();
6733
6734 /** Spreadsheet **/
6735 (function() {
6736
6737 function getRowLabel(value){
6738   if (this.options.spreadsheet.tickFormatter){
6739     //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
6740     return this.options.spreadsheet.tickFormatter(value);
6741   }
6742   else {
6743     var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
6744     if (t) {
6745       return t.label;
6746     }
6747     return value;
6748   }
6749 }
6750
6751 var
6752   D = Flotr.DOM,
6753   _ = Flotr._;
6754
6755 Flotr.addPlugin('spreadsheet', {
6756   options: {
6757     show: false,           // => show the data grid using two tabs
6758     tabGraphLabel: 'Graph',
6759     tabDataLabel: 'Data',
6760     toolbarDownload: 'Download CSV', // @todo: add better language support
6761     toolbarSelectAll: 'Select all',
6762     csvFileSeparator: ',',
6763     decimalSeparator: '.',
6764     tickFormatter: null,
6765     initialTab: 'graph'
6766   },
6767   /**
6768    * Builds the tabs in the DOM
6769    */
6770   callbacks: {
6771     'flotr:afterconstruct': function(){
6772       // @TODO necessary?
6773       //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');
6774       
6775       if (!this.options.spreadsheet.show) return;
6776       
6777       var ss = this.spreadsheet,
6778         container = D.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),
6779         graph = D.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+'</div>'),
6780         data = D.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+'</div>'),
6781         offset;
6782
6783       ss.tabsContainer = container;
6784       ss.tabs = { graph : graph, data : data };
6785
6786       D.insert(container, graph);
6787       D.insert(container, data);
6788       D.insert(this.el, container);
6789
6790       offset = D.size(data).height + 2;
6791       this.plotOffset.bottom += offset;
6792
6793       D.setStyles(container, {top: this.canvasHeight-offset+'px'});
6794
6795       this.
6796         observe(graph, 'click',  function(){ss.showTab('graph');}).
6797         observe(data, 'click', function(){ss.showTab('data');});
6798       if (this.options.spreadsheet.initialTab !== 'graph'){
6799         ss.showTab(this.options.spreadsheet.initialTab);
6800       }
6801     }
6802   },
6803   /**
6804    * Builds a matrix of the data to make the correspondance between the x values and the y values :
6805    * X value => Y values from the axes
6806    * @return {Array} The data grid
6807    */
6808   loadDataGrid: function(){
6809     if (this.seriesData) return this.seriesData;
6810
6811     var s = this.series,
6812         rows = {};
6813
6814     /* The data grid is a 2 dimensions array. There is a row for each X value.
6815      * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
6816     **/
6817     _.each(s, function(serie, i){
6818       _.each(serie.data, function (v) {
6819         var x = v[0],
6820             y = v[1],
6821             r = rows[x];
6822         if (r) {
6823           r[i+1] = y;
6824         } else {
6825           var newRow = [];
6826           newRow[0] = x;
6827           newRow[i+1] = y;
6828           rows[x] = newRow;
6829         }
6830       });
6831     });
6832
6833     // The data grid is sorted by x value
6834     this.seriesData = _.sortBy(rows, function(row, x){
6835       return parseInt(x, 10);
6836     });
6837     return this.seriesData;
6838   },
6839   /**
6840    * Constructs the data table for the spreadsheet
6841    * @todo make a spreadsheet manager (Flotr.Spreadsheet)
6842    * @return {Element} The resulting table element
6843    */
6844   constructDataGrid: function(){
6845     // If the data grid has already been built, nothing to do here
6846     if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;
6847     
6848     var s = this.series,
6849         datagrid = this.spreadsheet.loadDataGrid(),
6850         colgroup = ['<colgroup><col />'],
6851         buttonDownload, buttonSelect, t;
6852     
6853     // First row : series' labels
6854     var html = ['<table class="flotr-datagrid"><tr class="first-row">'];
6855     html.push('<th>&nbsp;</th>');
6856     _.each(s, function(serie,i){
6857       html.push('<th scope="col">'+(serie.label || String.fromCharCode(65+i))+'</th>');
6858       colgroup.push('<col />');
6859     });
6860     html.push('</tr>');
6861     // Data rows
6862     _.each(datagrid, function(row){
6863       html.push('<tr>');
6864       _.times(s.length+1, function(i){
6865         var tag = 'td',
6866             value = row[i],
6867             // TODO: do we really want to handle problems with floating point
6868             // precision here?
6869             content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
6870         if (i === 0) {
6871           tag = 'th';
6872           var label = getRowLabel.call(this, content);
6873           if (label) content = label;
6874         }
6875
6876         html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');
6877       }, this);
6878       html.push('</tr>');
6879     }, this);
6880     colgroup.push('</colgroup>');
6881     t = D.node(html.join(''));
6882
6883     /**
6884      * @TODO disabled this
6885     if (!Flotr.isIE || Flotr.isIE == 9) {
6886       function handleMouseout(){
6887         t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
6888       }
6889       function handleMouseover(e){
6890         var td = e.element(),
6891           siblings = td.previousSiblings();
6892         t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
6893         t.select('colgroup col')[siblings.length].addClassName('hover');
6894       }
6895       _.each(t.select('td'), function(td) {
6896         Flotr.EventAdapter.
6897           observe(td, 'mouseover', handleMouseover).
6898           observe(td, 'mouseout', handleMouseout);
6899       });
6900     }
6901     */
6902
6903     buttonDownload = D.node(
6904       '<button type="button" class="flotr-datagrid-toolbar-button">' +
6905       this.options.spreadsheet.toolbarDownload +
6906       '</button>');
6907
6908     buttonSelect = D.node(
6909       '<button type="button" class="flotr-datagrid-toolbar-button">' +
6910       this.options.spreadsheet.toolbarSelectAll+
6911       '</button>');
6912
6913     this.
6914       observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
6915       observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));
6916
6917     var toolbar = D.node('<div class="flotr-datagrid-toolbar"></div>');
6918     D.insert(toolbar, buttonDownload);
6919     D.insert(toolbar, buttonSelect);
6920
6921     var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
6922         container = D.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+
6923           this.canvasWidth+'px;height:'+containerHeight+'px;overflow:auto;z-index:10"></div>');
6924
6925     D.insert(container, toolbar);
6926     D.insert(container, t);
6927     D.insert(this.el, container);
6928     this.spreadsheet.datagrid = t;
6929     this.spreadsheet.container = container;
6930
6931     return t;
6932   },  
6933   /**
6934    * Shows the specified tab, by its name
6935    * @todo make a tab manager (Flotr.Tabs)
6936    * @param {String} tabName - The tab name
6937    */
6938   showTab: function(tabName){
6939     if (this.spreadsheet.activeTab === tabName){
6940       return;
6941     }
6942     switch(tabName) {
6943       case 'graph':
6944         D.hide(this.spreadsheet.container);
6945         D.removeClass(this.spreadsheet.tabs.data, 'selected');
6946         D.addClass(this.spreadsheet.tabs.graph, 'selected');
6947       break;
6948       case 'data':
6949         if (!this.spreadsheet.datagrid)
6950           this.spreadsheet.constructDataGrid();
6951         D.show(this.spreadsheet.container);
6952         D.addClass(this.spreadsheet.tabs.data, 'selected');
6953         D.removeClass(this.spreadsheet.tabs.graph, 'selected');
6954       break;
6955       default:
6956         throw 'Illegal tab name: ' + tabName;
6957     }
6958     this.spreadsheet.activeTab = tabName;
6959   },
6960   /**
6961    * Selects the data table in the DOM for copy/paste
6962    */
6963   selectAllData: function(){
6964     if (this.spreadsheet.tabs) {
6965       var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();
6966
6967       this.spreadsheet.showTab('data');
6968       
6969       // deferred to be able to select the table
6970       setTimeout(function () {
6971         if ((doc = node.ownerDocument) && (win = doc.defaultView) && 
6972             win.getSelection && doc.createRange && 
6973             (selection = window.getSelection()) && 
6974             selection.removeAllRanges) {
6975             range = doc.createRange();
6976             range.selectNode(node);
6977             selection.removeAllRanges();
6978             selection.addRange(range);
6979         }
6980         else if (document.body && document.body.createTextRange && 
6981                 (range = document.body.createTextRange())) {
6982             range.moveToElementText(node);
6983             range.select();
6984         }
6985       }, 0);
6986       return true;
6987     }
6988     else return false;
6989   },
6990   /**
6991    * Converts the data into CSV in order to download a file
6992    */
6993   downloadCSV: function(){
6994     var csv = '',
6995         series = this.series,
6996         options = this.options,
6997         dg = this.spreadsheet.loadDataGrid(),
6998         separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);
6999     
7000     if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
7001       throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
7002     }
7003     
7004     // The first row
7005     _.each(series, function(serie, i){
7006       csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
7007     });
7008
7009     csv += "%0D%0A"; // \r\n
7010     
7011     // For each row
7012     csv += _.reduce(dg, function(memo, row){
7013       var rowLabel = getRowLabel.call(this, row[0]) || '';
7014       rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
7015       var numbers = row.slice(1).join(separator);
7016       if (options.spreadsheet.decimalSeparator !== '.') {
7017         numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
7018       }
7019       return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
7020     }, '', this);
7021
7022     if (Flotr.isIE && Flotr.isIE < 9) {
7023       csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
7024       window.open().document.write(csv);
7025     }
7026     else window.open('data:text/csv,'+csv);
7027   }
7028 });
7029 })();
7030
7031 (function () {
7032
7033 var D = Flotr.DOM;
7034
7035 Flotr.addPlugin('titles', {
7036   callbacks: {
7037     'flotr:afterdraw': function() {
7038       this.titles.drawTitles();
7039     }
7040   },
7041   /**
7042    * Draws the title and the subtitle
7043    */
7044   drawTitles : function () {
7045     var html,
7046         options = this.options,
7047         margin = options.grid.labelMargin,
7048         ctx = this.ctx,
7049         a = this.axes;
7050     
7051     if (!options.HtmlText && this.textEnabled) {
7052       var style = {
7053         size: options.fontSize,
7054         color: options.grid.color,
7055         textAlign: 'center'
7056       };
7057       
7058       // Add subtitle
7059       if (options.subtitle){
7060         Flotr.drawText(
7061           ctx, options.subtitle,
7062           this.plotOffset.left + this.plotWidth/2, 
7063           this.titleHeight + this.subtitleHeight - 2,
7064           style
7065         );
7066       }
7067       
7068       style.weight = 1.5;
7069       style.size *= 1.5;
7070       
7071       // Add title
7072       if (options.title){
7073         Flotr.drawText(
7074           ctx, options.title,
7075           this.plotOffset.left + this.plotWidth/2, 
7076           this.titleHeight - 2,
7077           style
7078         );
7079       }
7080       
7081       style.weight = 1.8;
7082       style.size *= 0.8;
7083       
7084       // Add x axis title
7085       if (a.x.options.title && a.x.used){
7086         style.textAlign = a.x.options.titleAlign || 'center';
7087         style.textBaseline = 'top';
7088         style.angle = Flotr.toRad(a.x.options.titleAngle);
7089         style = Flotr.getBestTextAlign(style.angle, style);
7090         Flotr.drawText(
7091           ctx, a.x.options.title,
7092           this.plotOffset.left + this.plotWidth/2, 
7093           this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,
7094           style
7095         );
7096       }
7097       
7098       // Add x2 axis title
7099       if (a.x2.options.title && a.x2.used){
7100         style.textAlign = a.x2.options.titleAlign || 'center';
7101         style.textBaseline = 'bottom';
7102         style.angle = Flotr.toRad(a.x2.options.titleAngle);
7103         style = Flotr.getBestTextAlign(style.angle, style);
7104         Flotr.drawText(
7105           ctx, a.x2.options.title,
7106           this.plotOffset.left + this.plotWidth/2, 
7107           this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,
7108           style
7109         );
7110       }
7111       
7112       // Add y axis title
7113       if (a.y.options.title && a.y.used){
7114         style.textAlign = a.y.options.titleAlign || 'right';
7115         style.textBaseline = 'middle';
7116         style.angle = Flotr.toRad(a.y.options.titleAngle);
7117         style = Flotr.getBestTextAlign(style.angle, style);
7118         Flotr.drawText(
7119           ctx, a.y.options.title,
7120           this.plotOffset.left - a.y.maxLabel.width - 2 * margin, 
7121           this.plotOffset.top + this.plotHeight / 2,
7122           style
7123         );
7124       }
7125       
7126       // Add y2 axis title
7127       if (a.y2.options.title && a.y2.used){
7128         style.textAlign = a.y2.options.titleAlign || 'left';
7129         style.textBaseline = 'middle';
7130         style.angle = Flotr.toRad(a.y2.options.titleAngle);
7131         style = Flotr.getBestTextAlign(style.angle, style);
7132         Flotr.drawText(
7133           ctx, a.y2.options.title,
7134           this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, 
7135           this.plotOffset.top + this.plotHeight / 2,
7136           style
7137         );
7138       }
7139     } 
7140     else {
7141       html = [];
7142       
7143       // Add title
7144       if (options.title)
7145         html.push(
7146           '<div style="position:absolute;top:0;left:', 
7147           this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:',
7148           this.plotWidth,'px;" class="flotr-title">', options.title, '</div>'
7149         );
7150       
7151       // Add subtitle
7152       if (options.subtitle)
7153         html.push(
7154           '<div style="position:absolute;top:', this.titleHeight, 'px;left:', 
7155           this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:',
7156           this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>'
7157         );
7158
7159       html.push('</div>');
7160       
7161       html.push('<div class="flotr-axis-title" style="font-weight:bold;">');
7162       
7163       // Add x axis title
7164       if (a.x.options.title && a.x.used)
7165         html.push(
7166           '<div style="position:absolute;top:', 
7167           (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), 
7168           'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, 
7169           'px;text-align:', a.x.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-x1">', a.x.options.title, '</div>'
7170         );
7171       
7172       // Add x2 axis title
7173       if (a.x2.options.title && a.x2.used)
7174         html.push(
7175           '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', 
7176           this.plotWidth, 'px;text-align:', a.x2.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-x2">', a.x2.options.title, '</div>'
7177         );
7178       
7179       // Add y axis title
7180       if (a.y.options.title && a.y.used)
7181         html.push(
7182           '<div style="position:absolute;top:', 
7183           (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
7184           'px;left:0;text-align:', a.y.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-y1">', a.y.options.title, '</div>'
7185         );
7186       
7187       // Add y2 axis title
7188       if (a.y2.options.title && a.y2.used)
7189         html.push(
7190           '<div style="position:absolute;top:', 
7191           (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
7192           'px;right:0;text-align:', a.y2.options.titleAlign, ';" class="flotr-axis-title flotr-axis-title-y2">', a.y2.options.title, '</div>'
7193         );
7194       
7195       html = html.join('');
7196
7197       var div = D.create('div');
7198       D.setStyles({
7199         color: options.grid.color 
7200       });
7201       div.className = 'flotr-titles';
7202       D.insert(this.el, div);
7203       D.insert(div, html);
7204     }
7205   }
7206 });
7207 })();