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