2013-12-15 13:55:50 +01:00
/ * !
* Chart . js
* http : //chartjs.org/
2016-09-11 16:30:23 +02:00
* Version : 2.2 . 2
2013-12-15 13:55:50 +01:00
*
2016-09-11 16:30:23 +02:00
* Copyright 2016 Nick Downie
2013-12-15 13:55:50 +01:00
* Released under the MIT license
2016-09-11 16:30:23 +02:00
* https : //github.com/chartjs/Chart.js/blob/master/LICENSE.md
2013-12-15 13:55:50 +01:00
* /
2016-09-11 16:30:23 +02:00
( function ( f ) { if ( typeof exports === "object" && typeof module !== "undefined" ) { module . exports = f ( ) } else if ( typeof define === "function" && define . amd ) { define ( [ ] , f ) } else { var g ; if ( typeof window !== "undefined" ) { g = window } else if ( typeof global !== "undefined" ) { g = global } else if ( typeof self !== "undefined" ) { g = self } else { g = this } g . Chart = f ( ) } } ) ( function ( ) { var define , module , exports ; return ( function e ( t , n , r ) { function s ( o , u ) { if ( ! n [ o ] ) { if ( ! t [ o ] ) { var a = typeof require == "function" && require ; if ( ! u && a ) return a ( o , ! 0 ) ; if ( i ) return i ( o , ! 0 ) ; var f = new Error ( "Cannot find module '" + o + "'" ) ; throw f . code = "MODULE_NOT_FOUND" , f } var l = n [ o ] = { exports : { } } ; t [ o ] [ 0 ] . call ( l . exports , function ( e ) { var n = t [ o ] [ 1 ] [ e ] ; return s ( n ? n : e ) } , l , l . exports , e , t , n , r ) } return n [ o ] . exports } var i = typeof require == "function" && require ; for ( var o = 0 ; o < r . length ; o ++ ) s ( r [ o ] ) ; return s } ) ( { 1 : [ function ( require , module , exports ) {
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
} , { } ] , 2 : [ function ( require , module , exports ) {
/* MIT license */
var colorNames = require ( 6 ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
module . exports = {
getRgba : getRgba ,
getHsla : getHsla ,
getRgb : getRgb ,
getHsl : getHsl ,
getHwb : getHwb ,
getAlpha : getAlpha ,
hexString : hexString ,
rgbString : rgbString ,
rgbaString : rgbaString ,
percentString : percentString ,
percentaString : percentaString ,
hslString : hslString ,
hslaString : hslaString ,
hwbString : hwbString ,
keyword : keyword
}
function getRgba ( string ) {
if ( ! string ) {
return ;
}
var abbr = /^#([a-fA-F0-9]{3})$/ ,
hex = /^#([a-fA-F0-9]{6})$/ ,
rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/ ,
per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/ ,
keyword = /(\w+)/ ;
var rgb = [ 0 , 0 , 0 ] ,
a = 1 ,
match = string . match ( abbr ) ;
if ( match ) {
match = match [ 1 ] ;
for ( var i = 0 ; i < rgb . length ; i ++ ) {
rgb [ i ] = parseInt ( match [ i ] + match [ i ] , 16 ) ;
}
}
else if ( match = string . match ( hex ) ) {
match = match [ 1 ] ;
for ( var i = 0 ; i < rgb . length ; i ++ ) {
rgb [ i ] = parseInt ( match . slice ( i * 2 , i * 2 + 2 ) , 16 ) ;
}
}
else if ( match = string . match ( rgba ) ) {
for ( var i = 0 ; i < rgb . length ; i ++ ) {
rgb [ i ] = parseInt ( match [ i + 1 ] ) ;
}
a = parseFloat ( match [ 4 ] ) ;
}
else if ( match = string . match ( per ) ) {
for ( var i = 0 ; i < rgb . length ; i ++ ) {
rgb [ i ] = Math . round ( parseFloat ( match [ i + 1 ] ) * 2.55 ) ;
}
a = parseFloat ( match [ 4 ] ) ;
}
else if ( match = string . match ( keyword ) ) {
if ( match [ 1 ] == "transparent" ) {
return [ 0 , 0 , 0 , 0 ] ;
}
rgb = colorNames [ match [ 1 ] ] ;
if ( ! rgb ) {
return ;
}
}
for ( var i = 0 ; i < rgb . length ; i ++ ) {
rgb [ i ] = scale ( rgb [ i ] , 0 , 255 ) ;
}
if ( ! a && a != 0 ) {
a = 1 ;
}
else {
a = scale ( a , 0 , 1 ) ;
}
rgb [ 3 ] = a ;
return rgb ;
}
function getHsla ( string ) {
if ( ! string ) {
return ;
}
var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/ ;
var match = string . match ( hsl ) ;
if ( match ) {
var alpha = parseFloat ( match [ 4 ] ) ;
var h = scale ( parseInt ( match [ 1 ] ) , 0 , 360 ) ,
s = scale ( parseFloat ( match [ 2 ] ) , 0 , 100 ) ,
l = scale ( parseFloat ( match [ 3 ] ) , 0 , 100 ) ,
a = scale ( isNaN ( alpha ) ? 1 : alpha , 0 , 1 ) ;
return [ h , s , l , a ] ;
}
}
function getHwb ( string ) {
if ( ! string ) {
return ;
}
var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/ ;
var match = string . match ( hwb ) ;
if ( match ) {
var alpha = parseFloat ( match [ 4 ] ) ;
var h = scale ( parseInt ( match [ 1 ] ) , 0 , 360 ) ,
w = scale ( parseFloat ( match [ 2 ] ) , 0 , 100 ) ,
b = scale ( parseFloat ( match [ 3 ] ) , 0 , 100 ) ,
a = scale ( isNaN ( alpha ) ? 1 : alpha , 0 , 1 ) ;
return [ h , w , b , a ] ;
}
}
function getRgb ( string ) {
var rgba = getRgba ( string ) ;
return rgba && rgba . slice ( 0 , 3 ) ;
}
function getHsl ( string ) {
var hsla = getHsla ( string ) ;
return hsla && hsla . slice ( 0 , 3 ) ;
}
function getAlpha ( string ) {
var vals = getRgba ( string ) ;
if ( vals ) {
return vals [ 3 ] ;
}
else if ( vals = getHsla ( string ) ) {
return vals [ 3 ] ;
}
else if ( vals = getHwb ( string ) ) {
return vals [ 3 ] ;
}
}
// generators
function hexString ( rgb ) {
return "#" + hexDouble ( rgb [ 0 ] ) + hexDouble ( rgb [ 1 ] )
+ hexDouble ( rgb [ 2 ] ) ;
}
function rgbString ( rgba , alpha ) {
if ( alpha < 1 || ( rgba [ 3 ] && rgba [ 3 ] < 1 ) ) {
return rgbaString ( rgba , alpha ) ;
}
return "rgb(" + rgba [ 0 ] + ", " + rgba [ 1 ] + ", " + rgba [ 2 ] + ")" ;
}
function rgbaString ( rgba , alpha ) {
if ( alpha === undefined ) {
alpha = ( rgba [ 3 ] !== undefined ? rgba [ 3 ] : 1 ) ;
}
return "rgba(" + rgba [ 0 ] + ", " + rgba [ 1 ] + ", " + rgba [ 2 ]
+ ", " + alpha + ")" ;
}
function percentString ( rgba , alpha ) {
if ( alpha < 1 || ( rgba [ 3 ] && rgba [ 3 ] < 1 ) ) {
return percentaString ( rgba , alpha ) ;
}
var r = Math . round ( rgba [ 0 ] / 255 * 100 ) ,
g = Math . round ( rgba [ 1 ] / 255 * 100 ) ,
b = Math . round ( rgba [ 2 ] / 255 * 100 ) ;
return "rgb(" + r + "%, " + g + "%, " + b + "%)" ;
}
function percentaString ( rgba , alpha ) {
var r = Math . round ( rgba [ 0 ] / 255 * 100 ) ,
g = Math . round ( rgba [ 1 ] / 255 * 100 ) ,
b = Math . round ( rgba [ 2 ] / 255 * 100 ) ;
return "rgba(" + r + "%, " + g + "%, " + b + "%, " + ( alpha || rgba [ 3 ] || 1 ) + ")" ;
}
function hslString ( hsla , alpha ) {
if ( alpha < 1 || ( hsla [ 3 ] && hsla [ 3 ] < 1 ) ) {
return hslaString ( hsla , alpha ) ;
}
return "hsl(" + hsla [ 0 ] + ", " + hsla [ 1 ] + "%, " + hsla [ 2 ] + "%)" ;
}
function hslaString ( hsla , alpha ) {
if ( alpha === undefined ) {
alpha = ( hsla [ 3 ] !== undefined ? hsla [ 3 ] : 1 ) ;
}
return "hsla(" + hsla [ 0 ] + ", " + hsla [ 1 ] + "%, " + hsla [ 2 ] + "%, "
+ alpha + ")" ;
}
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
// (hwb have alpha optional & 1 is default value)
function hwbString ( hwb , alpha ) {
if ( alpha === undefined ) {
alpha = ( hwb [ 3 ] !== undefined ? hwb [ 3 ] : 1 ) ;
}
return "hwb(" + hwb [ 0 ] + ", " + hwb [ 1 ] + "%, " + hwb [ 2 ] + "%"
+ ( alpha !== undefined && alpha !== 1 ? ", " + alpha : "" ) + ")" ;
}
function keyword ( rgb ) {
return reverseNames [ rgb . slice ( 0 , 3 ) ] ;
}
// helpers
function scale ( num , min , max ) {
return Math . min ( Math . max ( min , num ) , max ) ;
}
function hexDouble ( num ) {
var str = num . toString ( 16 ) . toUpperCase ( ) ;
return ( str . length < 2 ) ? "0" + str : str ;
}
//create a list of reverse color names
var reverseNames = { } ;
for ( var name in colorNames ) {
reverseNames [ colorNames [ name ] ] = name ;
}
} , { "6" : 6 } ] , 3 : [ function ( require , module , exports ) {
/* MIT license */
var convert = require ( 5 ) ;
var string = require ( 2 ) ;
var Color = function ( obj ) {
if ( obj instanceof Color ) {
return obj ;
}
if ( ! ( this instanceof Color ) ) {
return new Color ( obj ) ;
}
this . values = {
rgb : [ 0 , 0 , 0 ] ,
hsl : [ 0 , 0 , 0 ] ,
hsv : [ 0 , 0 , 0 ] ,
hwb : [ 0 , 0 , 0 ] ,
cmyk : [ 0 , 0 , 0 , 0 ] ,
alpha : 1
} ;
// parse Color() argument
var vals ;
if ( typeof obj === 'string' ) {
vals = string . getRgba ( obj ) ;
if ( vals ) {
this . setValues ( 'rgb' , vals ) ;
} else if ( vals = string . getHsla ( obj ) ) {
this . setValues ( 'hsl' , vals ) ;
} else if ( vals = string . getHwb ( obj ) ) {
this . setValues ( 'hwb' , vals ) ;
} else {
throw new Error ( 'Unable to parse color from string "' + obj + '"' ) ;
}
} else if ( typeof obj === 'object' ) {
vals = obj ;
if ( vals . r !== undefined || vals . red !== undefined ) {
this . setValues ( 'rgb' , vals ) ;
} else if ( vals . l !== undefined || vals . lightness !== undefined ) {
this . setValues ( 'hsl' , vals ) ;
} else if ( vals . v !== undefined || vals . value !== undefined ) {
this . setValues ( 'hsv' , vals ) ;
} else if ( vals . w !== undefined || vals . whiteness !== undefined ) {
this . setValues ( 'hwb' , vals ) ;
} else if ( vals . c !== undefined || vals . cyan !== undefined ) {
this . setValues ( 'cmyk' , vals ) ;
} else {
throw new Error ( 'Unable to parse color from object ' + JSON . stringify ( obj ) ) ;
}
}
} ;
Color . prototype = {
rgb : function ( ) {
return this . setSpace ( 'rgb' , arguments ) ;
} ,
hsl : function ( ) {
return this . setSpace ( 'hsl' , arguments ) ;
} ,
hsv : function ( ) {
return this . setSpace ( 'hsv' , arguments ) ;
} ,
hwb : function ( ) {
return this . setSpace ( 'hwb' , arguments ) ;
} ,
cmyk : function ( ) {
return this . setSpace ( 'cmyk' , arguments ) ;
} ,
rgbArray : function ( ) {
return this . values . rgb ;
} ,
hslArray : function ( ) {
return this . values . hsl ;
} ,
hsvArray : function ( ) {
return this . values . hsv ;
} ,
hwbArray : function ( ) {
var values = this . values ;
if ( values . alpha !== 1 ) {
return values . hwb . concat ( [ values . alpha ] ) ;
}
return values . hwb ;
} ,
cmykArray : function ( ) {
return this . values . cmyk ;
} ,
rgbaArray : function ( ) {
var values = this . values ;
return values . rgb . concat ( [ values . alpha ] ) ;
} ,
hslaArray : function ( ) {
var values = this . values ;
return values . hsl . concat ( [ values . alpha ] ) ;
} ,
alpha : function ( val ) {
if ( val === undefined ) {
return this . values . alpha ;
}
this . setValues ( 'alpha' , val ) ;
return this ;
} ,
red : function ( val ) {
return this . setChannel ( 'rgb' , 0 , val ) ;
} ,
green : function ( val ) {
return this . setChannel ( 'rgb' , 1 , val ) ;
} ,
blue : function ( val ) {
return this . setChannel ( 'rgb' , 2 , val ) ;
} ,
hue : function ( val ) {
if ( val ) {
val %= 360 ;
val = val < 0 ? 360 + val : val ;
}
return this . setChannel ( 'hsl' , 0 , val ) ;
} ,
saturation : function ( val ) {
return this . setChannel ( 'hsl' , 1 , val ) ;
} ,
lightness : function ( val ) {
return this . setChannel ( 'hsl' , 2 , val ) ;
} ,
saturationv : function ( val ) {
return this . setChannel ( 'hsv' , 1 , val ) ;
} ,
whiteness : function ( val ) {
return this . setChannel ( 'hwb' , 1 , val ) ;
} ,
blackness : function ( val ) {
return this . setChannel ( 'hwb' , 2 , val ) ;
} ,
value : function ( val ) {
return this . setChannel ( 'hsv' , 2 , val ) ;
} ,
cyan : function ( val ) {
return this . setChannel ( 'cmyk' , 0 , val ) ;
} ,
magenta : function ( val ) {
return this . setChannel ( 'cmyk' , 1 , val ) ;
} ,
yellow : function ( val ) {
return this . setChannel ( 'cmyk' , 2 , val ) ;
} ,
black : function ( val ) {
return this . setChannel ( 'cmyk' , 3 , val ) ;
} ,
hexString : function ( ) {
return string . hexString ( this . values . rgb ) ;
} ,
rgbString : function ( ) {
return string . rgbString ( this . values . rgb , this . values . alpha ) ;
} ,
rgbaString : function ( ) {
return string . rgbaString ( this . values . rgb , this . values . alpha ) ;
} ,
percentString : function ( ) {
return string . percentString ( this . values . rgb , this . values . alpha ) ;
} ,
hslString : function ( ) {
return string . hslString ( this . values . hsl , this . values . alpha ) ;
} ,
hslaString : function ( ) {
return string . hslaString ( this . values . hsl , this . values . alpha ) ;
} ,
hwbString : function ( ) {
return string . hwbString ( this . values . hwb , this . values . alpha ) ;
} ,
keyword : function ( ) {
return string . keyword ( this . values . rgb , this . values . alpha ) ;
} ,
rgbNumber : function ( ) {
var rgb = this . values . rgb ;
return ( rgb [ 0 ] << 16 ) | ( rgb [ 1 ] << 8 ) | rgb [ 2 ] ;
} ,
luminosity : function ( ) {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
var rgb = this . values . rgb ;
var lum = [ ] ;
for ( var i = 0 ; i < rgb . length ; i ++ ) {
var chan = rgb [ i ] / 255 ;
lum [ i ] = ( chan <= 0.03928 ) ? chan / 12.92 : Math . pow ( ( ( chan + 0.055 ) / 1.055 ) , 2.4 ) ;
}
return 0.2126 * lum [ 0 ] + 0.7152 * lum [ 1 ] + 0.0722 * lum [ 2 ] ;
} ,
contrast : function ( color2 ) {
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
var lum1 = this . luminosity ( ) ;
var lum2 = color2 . luminosity ( ) ;
if ( lum1 > lum2 ) {
return ( lum1 + 0.05 ) / ( lum2 + 0.05 ) ;
}
return ( lum2 + 0.05 ) / ( lum1 + 0.05 ) ;
} ,
level : function ( color2 ) {
var contrastRatio = this . contrast ( color2 ) ;
if ( contrastRatio >= 7.1 ) {
return 'AAA' ;
}
return ( contrastRatio >= 4.5 ) ? 'AA' : '' ;
} ,
dark : function ( ) {
// YIQ equation from http://24ways.org/2010/calculating-color-contrast
var rgb = this . values . rgb ;
var yiq = ( rgb [ 0 ] * 299 + rgb [ 1 ] * 587 + rgb [ 2 ] * 114 ) / 1000 ;
return yiq < 128 ;
} ,
light : function ( ) {
return ! this . dark ( ) ;
} ,
negate : function ( ) {
var rgb = [ ] ;
for ( var i = 0 ; i < 3 ; i ++ ) {
rgb [ i ] = 255 - this . values . rgb [ i ] ;
}
this . setValues ( 'rgb' , rgb ) ;
return this ;
} ,
lighten : function ( ratio ) {
var hsl = this . values . hsl ;
hsl [ 2 ] += hsl [ 2 ] * ratio ;
this . setValues ( 'hsl' , hsl ) ;
return this ;
} ,
darken : function ( ratio ) {
var hsl = this . values . hsl ;
hsl [ 2 ] -= hsl [ 2 ] * ratio ;
this . setValues ( 'hsl' , hsl ) ;
return this ;
} ,
saturate : function ( ratio ) {
var hsl = this . values . hsl ;
hsl [ 1 ] += hsl [ 1 ] * ratio ;
this . setValues ( 'hsl' , hsl ) ;
return this ;
} ,
desaturate : function ( ratio ) {
var hsl = this . values . hsl ;
hsl [ 1 ] -= hsl [ 1 ] * ratio ;
this . setValues ( 'hsl' , hsl ) ;
return this ;
} ,
whiten : function ( ratio ) {
var hwb = this . values . hwb ;
hwb [ 1 ] += hwb [ 1 ] * ratio ;
this . setValues ( 'hwb' , hwb ) ;
return this ;
} ,
blacken : function ( ratio ) {
var hwb = this . values . hwb ;
hwb [ 2 ] += hwb [ 2 ] * ratio ;
this . setValues ( 'hwb' , hwb ) ;
return this ;
} ,
greyscale : function ( ) {
var rgb = this . values . rgb ;
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
var val = rgb [ 0 ] * 0.3 + rgb [ 1 ] * 0.59 + rgb [ 2 ] * 0.11 ;
this . setValues ( 'rgb' , [ val , val , val ] ) ;
return this ;
} ,
clearer : function ( ratio ) {
var alpha = this . values . alpha ;
this . setValues ( 'alpha' , alpha - ( alpha * ratio ) ) ;
return this ;
} ,
opaquer : function ( ratio ) {
var alpha = this . values . alpha ;
this . setValues ( 'alpha' , alpha + ( alpha * ratio ) ) ;
return this ;
} ,
rotate : function ( degrees ) {
var hsl = this . values . hsl ;
var hue = ( hsl [ 0 ] + degrees ) % 360 ;
hsl [ 0 ] = hue < 0 ? 360 + hue : hue ;
this . setValues ( 'hsl' , hsl ) ;
return this ;
} ,
/ * *
* Ported from sass implementation in C
* https : //github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
* /
mix : function ( mixinColor , weight ) {
var color1 = this ;
var color2 = mixinColor ;
var p = weight === undefined ? 0.5 : weight ;
var w = 2 * p - 1 ;
var a = color1 . alpha ( ) - color2 . alpha ( ) ;
var w1 = ( ( ( w * a === - 1 ) ? w : ( w + a ) / ( 1 + w * a ) ) + 1 ) / 2.0 ;
var w2 = 1 - w1 ;
return this
. rgb (
w1 * color1 . red ( ) + w2 * color2 . red ( ) ,
w1 * color1 . green ( ) + w2 * color2 . green ( ) ,
w1 * color1 . blue ( ) + w2 * color2 . blue ( )
)
. alpha ( color1 . alpha ( ) * p + color2 . alpha ( ) * ( 1 - p ) ) ;
} ,
toJSON : function ( ) {
return this . rgb ( ) ;
} ,
clone : function ( ) {
// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
// making the final build way to big to embed in Chart.js. So let's do it manually,
// assuming that values to clone are 1 dimension arrays containing only numbers,
// except 'alpha' which is a number.
var result = new Color ( ) ;
var source = this . values ;
var target = result . values ;
var value , type ;
for ( var prop in source ) {
if ( source . hasOwnProperty ( prop ) ) {
value = source [ prop ] ;
type = ( { } ) . toString . call ( value ) ;
if ( type === '[object Array]' ) {
target [ prop ] = value . slice ( 0 ) ;
} else if ( type === '[object Number]' ) {
target [ prop ] = value ;
} else {
console . error ( 'unexpected color value:' , value ) ;
}
}
}
return result ;
}
} ;
Color . prototype . spaces = {
rgb : [ 'red' , 'green' , 'blue' ] ,
hsl : [ 'hue' , 'saturation' , 'lightness' ] ,
hsv : [ 'hue' , 'saturation' , 'value' ] ,
hwb : [ 'hue' , 'whiteness' , 'blackness' ] ,
cmyk : [ 'cyan' , 'magenta' , 'yellow' , 'black' ]
} ;
Color . prototype . maxes = {
rgb : [ 255 , 255 , 255 ] ,
hsl : [ 360 , 100 , 100 ] ,
hsv : [ 360 , 100 , 100 ] ,
hwb : [ 360 , 100 , 100 ] ,
cmyk : [ 100 , 100 , 100 , 100 ]
} ;
Color . prototype . getValues = function ( space ) {
var values = this . values ;
var vals = { } ;
for ( var i = 0 ; i < space . length ; i ++ ) {
vals [ space . charAt ( i ) ] = values [ space ] [ i ] ;
}
if ( values . alpha !== 1 ) {
vals . a = values . alpha ;
}
// {r: 255, g: 255, b: 255, a: 0.4}
return vals ;
} ;
Color . prototype . setValues = function ( space , vals ) {
var values = this . values ;
var spaces = this . spaces ;
var maxes = this . maxes ;
var alpha = 1 ;
var i ;
if ( space === 'alpha' ) {
alpha = vals ;
} else if ( vals . length ) {
// [10, 10, 10]
values [ space ] = vals . slice ( 0 , space . length ) ;
alpha = vals [ space . length ] ;
} else if ( vals [ space . charAt ( 0 ) ] !== undefined ) {
// {r: 10, g: 10, b: 10}
for ( i = 0 ; i < space . length ; i ++ ) {
values [ space ] [ i ] = vals [ space . charAt ( i ) ] ;
}
alpha = vals . a ;
} else if ( vals [ spaces [ space ] [ 0 ] ] !== undefined ) {
// {red: 10, green: 10, blue: 10}
var chans = spaces [ space ] ;
for ( i = 0 ; i < space . length ; i ++ ) {
values [ space ] [ i ] = vals [ chans [ i ] ] ;
}
alpha = vals . alpha ;
}
values . alpha = Math . max ( 0 , Math . min ( 1 , ( alpha === undefined ? values . alpha : alpha ) ) ) ;
if ( space === 'alpha' ) {
return false ;
}
var capped ;
// cap values of the space prior converting all values
for ( i = 0 ; i < space . length ; i ++ ) {
capped = Math . max ( 0 , Math . min ( maxes [ space ] [ i ] , values [ space ] [ i ] ) ) ;
values [ space ] [ i ] = Math . round ( capped ) ;
}
// convert to all the other color spaces
for ( var sname in spaces ) {
if ( sname !== space ) {
values [ sname ] = convert [ space ] [ sname ] ( values [ space ] ) ;
}
}
return true ;
} ;
Color . prototype . setSpace = function ( space , args ) {
var vals = args [ 0 ] ;
if ( vals === undefined ) {
// color.rgb()
return this . getValues ( space ) ;
}
// color.rgb(10, 10, 10)
if ( typeof vals === 'number' ) {
vals = Array . prototype . slice . call ( args ) ;
}
this . setValues ( space , vals ) ;
return this ;
} ;
Color . prototype . setChannel = function ( space , index , val ) {
var svalues = this . values [ space ] ;
if ( val === undefined ) {
// color.red()
return svalues [ index ] ;
} else if ( val === svalues [ index ] ) {
// color.red(color.red())
return this ;
}
// color.red(100)
svalues [ index ] = val ;
this . setValues ( space , svalues ) ;
return this ;
} ;
if ( typeof window !== 'undefined' ) {
window . Color = Color ;
}
module . exports = Color ;
} , { "2" : 2 , "5" : 5 } ] , 4 : [ function ( require , module , exports ) {
/* MIT license */
module . exports = {
rgb2hsl : rgb2hsl ,
rgb2hsv : rgb2hsv ,
rgb2hwb : rgb2hwb ,
rgb2cmyk : rgb2cmyk ,
rgb2keyword : rgb2keyword ,
rgb2xyz : rgb2xyz ,
rgb2lab : rgb2lab ,
rgb2lch : rgb2lch ,
hsl2rgb : hsl2rgb ,
hsl2hsv : hsl2hsv ,
hsl2hwb : hsl2hwb ,
hsl2cmyk : hsl2cmyk ,
hsl2keyword : hsl2keyword ,
hsv2rgb : hsv2rgb ,
hsv2hsl : hsv2hsl ,
hsv2hwb : hsv2hwb ,
hsv2cmyk : hsv2cmyk ,
hsv2keyword : hsv2keyword ,
hwb2rgb : hwb2rgb ,
hwb2hsl : hwb2hsl ,
hwb2hsv : hwb2hsv ,
hwb2cmyk : hwb2cmyk ,
hwb2keyword : hwb2keyword ,
cmyk2rgb : cmyk2rgb ,
cmyk2hsl : cmyk2hsl ,
cmyk2hsv : cmyk2hsv ,
cmyk2hwb : cmyk2hwb ,
cmyk2keyword : cmyk2keyword ,
keyword2rgb : keyword2rgb ,
keyword2hsl : keyword2hsl ,
keyword2hsv : keyword2hsv ,
keyword2hwb : keyword2hwb ,
keyword2cmyk : keyword2cmyk ,
keyword2lab : keyword2lab ,
keyword2xyz : keyword2xyz ,
xyz2rgb : xyz2rgb ,
xyz2lab : xyz2lab ,
xyz2lch : xyz2lch ,
lab2xyz : lab2xyz ,
lab2rgb : lab2rgb ,
lab2lch : lab2lch ,
lch2lab : lch2lab ,
lch2xyz : lch2xyz ,
lch2rgb : lch2rgb
}
function rgb2hsl ( rgb ) {
var r = rgb [ 0 ] / 255 ,
g = rgb [ 1 ] / 255 ,
b = rgb [ 2 ] / 255 ,
min = Math . min ( r , g , b ) ,
max = Math . max ( r , g , b ) ,
delta = max - min ,
h , s , l ;
if ( max == min )
h = 0 ;
else if ( r == max )
h = ( g - b ) / delta ;
else if ( g == max )
h = 2 + ( b - r ) / delta ;
else if ( b == max )
h = 4 + ( r - g ) / delta ;
h = Math . min ( h * 60 , 360 ) ;
if ( h < 0 )
h += 360 ;
l = ( min + max ) / 2 ;
if ( max == min )
s = 0 ;
else if ( l <= 0.5 )
s = delta / ( max + min ) ;
else
s = delta / ( 2 - max - min ) ;
return [ h , s * 100 , l * 100 ] ;
}
function rgb2hsv ( rgb ) {
var r = rgb [ 0 ] ,
g = rgb [ 1 ] ,
b = rgb [ 2 ] ,
min = Math . min ( r , g , b ) ,
max = Math . max ( r , g , b ) ,
delta = max - min ,
h , s , v ;
if ( max == 0 )
s = 0 ;
else
s = ( delta / max * 1000 ) / 10 ;
if ( max == min )
h = 0 ;
else if ( r == max )
h = ( g - b ) / delta ;
else if ( g == max )
h = 2 + ( b - r ) / delta ;
else if ( b == max )
h = 4 + ( r - g ) / delta ;
h = Math . min ( h * 60 , 360 ) ;
if ( h < 0 )
h += 360 ;
v = ( ( max / 255 ) * 1000 ) / 10 ;
return [ h , s , v ] ;
}
function rgb2hwb ( rgb ) {
var r = rgb [ 0 ] ,
g = rgb [ 1 ] ,
b = rgb [ 2 ] ,
h = rgb2hsl ( rgb ) [ 0 ] ,
w = 1 / 255 * Math . min ( r , Math . min ( g , b ) ) ,
b = 1 - 1 / 255 * Math . max ( r , Math . max ( g , b ) ) ;
return [ h , w * 100 , b * 100 ] ;
}
function rgb2cmyk ( rgb ) {
var r = rgb [ 0 ] / 255 ,
g = rgb [ 1 ] / 255 ,
b = rgb [ 2 ] / 255 ,
c , m , y , k ;
k = Math . min ( 1 - r , 1 - g , 1 - b ) ;
c = ( 1 - r - k ) / ( 1 - k ) || 0 ;
m = ( 1 - g - k ) / ( 1 - k ) || 0 ;
y = ( 1 - b - k ) / ( 1 - k ) || 0 ;
return [ c * 100 , m * 100 , y * 100 , k * 100 ] ;
}
function rgb2keyword ( rgb ) {
return reverseKeywords [ JSON . stringify ( rgb ) ] ;
}
function rgb2xyz ( rgb ) {
var r = rgb [ 0 ] / 255 ,
g = rgb [ 1 ] / 255 ,
b = rgb [ 2 ] / 255 ;
// assume sRGB
r = r > 0.04045 ? Math . pow ( ( ( r + 0.055 ) / 1.055 ) , 2.4 ) : ( r / 12.92 ) ;
g = g > 0.04045 ? Math . pow ( ( ( g + 0.055 ) / 1.055 ) , 2.4 ) : ( g / 12.92 ) ;
b = b > 0.04045 ? Math . pow ( ( ( b + 0.055 ) / 1.055 ) , 2.4 ) : ( b / 12.92 ) ;
var x = ( r * 0.4124 ) + ( g * 0.3576 ) + ( b * 0.1805 ) ;
var y = ( r * 0.2126 ) + ( g * 0.7152 ) + ( b * 0.0722 ) ;
var z = ( r * 0.0193 ) + ( g * 0.1192 ) + ( b * 0.9505 ) ;
return [ x * 100 , y * 100 , z * 100 ] ;
}
function rgb2lab ( rgb ) {
var xyz = rgb2xyz ( rgb ) ,
x = xyz [ 0 ] ,
y = xyz [ 1 ] ,
z = xyz [ 2 ] ,
l , a , b ;
x /= 95.047 ;
y /= 100 ;
z /= 108.883 ;
x = x > 0.008856 ? Math . pow ( x , 1 / 3 ) : ( 7.787 * x ) + ( 16 / 116 ) ;
y = y > 0.008856 ? Math . pow ( y , 1 / 3 ) : ( 7.787 * y ) + ( 16 / 116 ) ;
z = z > 0.008856 ? Math . pow ( z , 1 / 3 ) : ( 7.787 * z ) + ( 16 / 116 ) ;
l = ( 116 * y ) - 16 ;
a = 500 * ( x - y ) ;
b = 200 * ( y - z ) ;
return [ l , a , b ] ;
}
function rgb2lch ( args ) {
return lab2lch ( rgb2lab ( args ) ) ;
}
function hsl2rgb ( hsl ) {
var h = hsl [ 0 ] / 360 ,
s = hsl [ 1 ] / 100 ,
l = hsl [ 2 ] / 100 ,
t1 , t2 , t3 , rgb , val ;
if ( s == 0 ) {
val = l * 255 ;
return [ val , val , val ] ;
}
if ( l < 0.5 )
t2 = l * ( 1 + s ) ;
else
t2 = l + s - l * s ;
t1 = 2 * l - t2 ;
rgb = [ 0 , 0 , 0 ] ;
for ( var i = 0 ; i < 3 ; i ++ ) {
t3 = h + 1 / 3 * - ( i - 1 ) ;
t3 < 0 && t3 ++ ;
t3 > 1 && t3 -- ;
if ( 6 * t3 < 1 )
val = t1 + ( t2 - t1 ) * 6 * t3 ;
else if ( 2 * t3 < 1 )
val = t2 ;
else if ( 3 * t3 < 2 )
val = t1 + ( t2 - t1 ) * ( 2 / 3 - t3 ) * 6 ;
else
val = t1 ;
rgb [ i ] = val * 255 ;
}
return rgb ;
}
function hsl2hsv ( hsl ) {
var h = hsl [ 0 ] ,
s = hsl [ 1 ] / 100 ,
l = hsl [ 2 ] / 100 ,
sv , v ;
if ( l === 0 ) {
// no need to do calc on black
// also avoids divide by 0 error
return [ 0 , 0 , 0 ] ;
}
l *= 2 ;
s *= ( l <= 1 ) ? l : 2 - l ;
v = ( l + s ) / 2 ;
sv = ( 2 * s ) / ( l + s ) ;
return [ h , sv * 100 , v * 100 ] ;
}
function hsl2hwb ( args ) {
return rgb2hwb ( hsl2rgb ( args ) ) ;
}
function hsl2cmyk ( args ) {
return rgb2cmyk ( hsl2rgb ( args ) ) ;
}
function hsl2keyword ( args ) {
return rgb2keyword ( hsl2rgb ( args ) ) ;
}
function hsv2rgb ( hsv ) {
var h = hsv [ 0 ] / 60 ,
s = hsv [ 1 ] / 100 ,
v = hsv [ 2 ] / 100 ,
hi = Math . floor ( h ) % 6 ;
var f = h - Math . floor ( h ) ,
p = 255 * v * ( 1 - s ) ,
q = 255 * v * ( 1 - ( s * f ) ) ,
t = 255 * v * ( 1 - ( s * ( 1 - f ) ) ) ,
v = 255 * v ;
switch ( hi ) {
case 0 :
return [ v , t , p ] ;
case 1 :
return [ q , v , p ] ;
case 2 :
return [ p , v , t ] ;
case 3 :
return [ p , q , v ] ;
case 4 :
return [ t , p , v ] ;
case 5 :
return [ v , p , q ] ;
}
}
function hsv2hsl ( hsv ) {
var h = hsv [ 0 ] ,
s = hsv [ 1 ] / 100 ,
v = hsv [ 2 ] / 100 ,
sl , l ;
l = ( 2 - s ) * v ;
sl = s * v ;
sl /= ( l <= 1 ) ? l : 2 - l ;
sl = sl || 0 ;
l /= 2 ;
return [ h , sl * 100 , l * 100 ] ;
}
function hsv2hwb ( args ) {
return rgb2hwb ( hsv2rgb ( args ) )
}
function hsv2cmyk ( args ) {
return rgb2cmyk ( hsv2rgb ( args ) ) ;
}
function hsv2keyword ( args ) {
return rgb2keyword ( hsv2rgb ( args ) ) ;
}
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
function hwb2rgb ( hwb ) {
var h = hwb [ 0 ] / 360 ,
wh = hwb [ 1 ] / 100 ,
bl = hwb [ 2 ] / 100 ,
ratio = wh + bl ,
i , v , f , n ;
// wh + bl cant be > 1
if ( ratio > 1 ) {
wh /= ratio ;
bl /= ratio ;
}
i = Math . floor ( 6 * h ) ;
v = 1 - bl ;
f = 6 * h - i ;
if ( ( i & 0x01 ) != 0 ) {
f = 1 - f ;
}
n = wh + f * ( v - wh ) ; // linear interpolation
switch ( i ) {
default :
case 6 :
case 0 : r = v ; g = n ; b = wh ; break ;
case 1 : r = n ; g = v ; b = wh ; break ;
case 2 : r = wh ; g = v ; b = n ; break ;
case 3 : r = wh ; g = n ; b = v ; break ;
case 4 : r = n ; g = wh ; b = v ; break ;
case 5 : r = v ; g = wh ; b = n ; break ;
}
return [ r * 255 , g * 255 , b * 255 ] ;
}
function hwb2hsl ( args ) {
return rgb2hsl ( hwb2rgb ( args ) ) ;
}
function hwb2hsv ( args ) {
return rgb2hsv ( hwb2rgb ( args ) ) ;
}
function hwb2cmyk ( args ) {
return rgb2cmyk ( hwb2rgb ( args ) ) ;
}
function hwb2keyword ( args ) {
return rgb2keyword ( hwb2rgb ( args ) ) ;
}
function cmyk2rgb ( cmyk ) {
var c = cmyk [ 0 ] / 100 ,
m = cmyk [ 1 ] / 100 ,
y = cmyk [ 2 ] / 100 ,
k = cmyk [ 3 ] / 100 ,
r , g , b ;
r = 1 - Math . min ( 1 , c * ( 1 - k ) + k ) ;
g = 1 - Math . min ( 1 , m * ( 1 - k ) + k ) ;
b = 1 - Math . min ( 1 , y * ( 1 - k ) + k ) ;
return [ r * 255 , g * 255 , b * 255 ] ;
}
function cmyk2hsl ( args ) {
return rgb2hsl ( cmyk2rgb ( args ) ) ;
}
function cmyk2hsv ( args ) {
return rgb2hsv ( cmyk2rgb ( args ) ) ;
}
function cmyk2hwb ( args ) {
return rgb2hwb ( cmyk2rgb ( args ) ) ;
}
function cmyk2keyword ( args ) {
return rgb2keyword ( cmyk2rgb ( args ) ) ;
}
function xyz2rgb ( xyz ) {
var x = xyz [ 0 ] / 100 ,
y = xyz [ 1 ] / 100 ,
z = xyz [ 2 ] / 100 ,
r , g , b ;
r = ( x * 3.2406 ) + ( y * - 1.5372 ) + ( z * - 0.4986 ) ;
g = ( x * - 0.9689 ) + ( y * 1.8758 ) + ( z * 0.0415 ) ;
b = ( x * 0.0557 ) + ( y * - 0.2040 ) + ( z * 1.0570 ) ;
// assume sRGB
r = r > 0.0031308 ? ( ( 1.055 * Math . pow ( r , 1.0 / 2.4 ) ) - 0.055 )
: r = ( r * 12.92 ) ;
g = g > 0.0031308 ? ( ( 1.055 * Math . pow ( g , 1.0 / 2.4 ) ) - 0.055 )
: g = ( g * 12.92 ) ;
b = b > 0.0031308 ? ( ( 1.055 * Math . pow ( b , 1.0 / 2.4 ) ) - 0.055 )
: b = ( b * 12.92 ) ;
r = Math . min ( Math . max ( 0 , r ) , 1 ) ;
g = Math . min ( Math . max ( 0 , g ) , 1 ) ;
b = Math . min ( Math . max ( 0 , b ) , 1 ) ;
return [ r * 255 , g * 255 , b * 255 ] ;
}
function xyz2lab ( xyz ) {
var x = xyz [ 0 ] ,
y = xyz [ 1 ] ,
z = xyz [ 2 ] ,
l , a , b ;
x /= 95.047 ;
y /= 100 ;
z /= 108.883 ;
x = x > 0.008856 ? Math . pow ( x , 1 / 3 ) : ( 7.787 * x ) + ( 16 / 116 ) ;
y = y > 0.008856 ? Math . pow ( y , 1 / 3 ) : ( 7.787 * y ) + ( 16 / 116 ) ;
z = z > 0.008856 ? Math . pow ( z , 1 / 3 ) : ( 7.787 * z ) + ( 16 / 116 ) ;
l = ( 116 * y ) - 16 ;
a = 500 * ( x - y ) ;
b = 200 * ( y - z ) ;
return [ l , a , b ] ;
}
function xyz2lch ( args ) {
return lab2lch ( xyz2lab ( args ) ) ;
}
function lab2xyz ( lab ) {
var l = lab [ 0 ] ,
a = lab [ 1 ] ,
b = lab [ 2 ] ,
x , y , z , y2 ;
if ( l <= 8 ) {
y = ( l * 100 ) / 903.3 ;
y2 = ( 7.787 * ( y / 100 ) ) + ( 16 / 116 ) ;
} else {
y = 100 * Math . pow ( ( l + 16 ) / 116 , 3 ) ;
y2 = Math . pow ( y / 100 , 1 / 3 ) ;
}
x = x / 95.047 <= 0.008856 ? x = ( 95.047 * ( ( a / 500 ) + y2 - ( 16 / 116 ) ) ) / 7.787 : 95.047 * Math . pow ( ( a / 500 ) + y2 , 3 ) ;
z = z / 108.883 <= 0.008859 ? z = ( 108.883 * ( y2 - ( b / 200 ) - ( 16 / 116 ) ) ) / 7.787 : 108.883 * Math . pow ( y2 - ( b / 200 ) , 3 ) ;
return [ x , y , z ] ;
}
function lab2lch ( lab ) {
var l = lab [ 0 ] ,
a = lab [ 1 ] ,
b = lab [ 2 ] ,
hr , h , c ;
hr = Math . atan2 ( b , a ) ;
h = hr * 360 / 2 / Math . PI ;
if ( h < 0 ) {
h += 360 ;
}
c = Math . sqrt ( a * a + b * b ) ;
return [ l , c , h ] ;
}
function lab2rgb ( args ) {
return xyz2rgb ( lab2xyz ( args ) ) ;
}
function lch2lab ( lch ) {
var l = lch [ 0 ] ,
c = lch [ 1 ] ,
h = lch [ 2 ] ,
a , b , hr ;
hr = h / 360 * 2 * Math . PI ;
a = c * Math . cos ( hr ) ;
b = c * Math . sin ( hr ) ;
return [ l , a , b ] ;
}
function lch2xyz ( args ) {
return lab2xyz ( lch2lab ( args ) ) ;
}
function lch2rgb ( args ) {
return lab2rgb ( lch2lab ( args ) ) ;
}
function keyword2rgb ( keyword ) {
return cssKeywords [ keyword ] ;
}
function keyword2hsl ( args ) {
return rgb2hsl ( keyword2rgb ( args ) ) ;
}
function keyword2hsv ( args ) {
return rgb2hsv ( keyword2rgb ( args ) ) ;
}
function keyword2hwb ( args ) {
return rgb2hwb ( keyword2rgb ( args ) ) ;
}
function keyword2cmyk ( args ) {
return rgb2cmyk ( keyword2rgb ( args ) ) ;
}
function keyword2lab ( args ) {
return rgb2lab ( keyword2rgb ( args ) ) ;
}
function keyword2xyz ( args ) {
return rgb2xyz ( keyword2rgb ( args ) ) ;
}
var cssKeywords = {
aliceblue : [ 240 , 248 , 255 ] ,
antiquewhite : [ 250 , 235 , 215 ] ,
aqua : [ 0 , 255 , 255 ] ,
aquamarine : [ 127 , 255 , 212 ] ,
azure : [ 240 , 255 , 255 ] ,
beige : [ 245 , 245 , 220 ] ,
bisque : [ 255 , 228 , 196 ] ,
black : [ 0 , 0 , 0 ] ,
blanchedalmond : [ 255 , 235 , 205 ] ,
blue : [ 0 , 0 , 255 ] ,
blueviolet : [ 138 , 43 , 226 ] ,
brown : [ 165 , 42 , 42 ] ,
burlywood : [ 222 , 184 , 135 ] ,
cadetblue : [ 95 , 158 , 160 ] ,
chartreuse : [ 127 , 255 , 0 ] ,
chocolate : [ 210 , 105 , 30 ] ,
coral : [ 255 , 127 , 80 ] ,
cornflowerblue : [ 100 , 149 , 237 ] ,
cornsilk : [ 255 , 248 , 220 ] ,
crimson : [ 220 , 20 , 60 ] ,
cyan : [ 0 , 255 , 255 ] ,
darkblue : [ 0 , 0 , 139 ] ,
darkcyan : [ 0 , 139 , 139 ] ,
darkgoldenrod : [ 184 , 134 , 11 ] ,
darkgray : [ 169 , 169 , 169 ] ,
darkgreen : [ 0 , 100 , 0 ] ,
darkgrey : [ 169 , 169 , 169 ] ,
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 ] ,
darkseagreen : [ 143 , 188 , 143 ] ,
darkslateblue : [ 72 , 61 , 139 ] ,
darkslategray : [ 47 , 79 , 79 ] ,
darkslategrey : [ 47 , 79 , 79 ] ,
darkturquoise : [ 0 , 206 , 209 ] ,
darkviolet : [ 148 , 0 , 211 ] ,
deeppink : [ 255 , 20 , 147 ] ,
deepskyblue : [ 0 , 191 , 255 ] ,
dimgray : [ 105 , 105 , 105 ] ,
dimgrey : [ 105 , 105 , 105 ] ,
dodgerblue : [ 30 , 144 , 255 ] ,
firebrick : [ 178 , 34 , 34 ] ,
floralwhite : [ 255 , 250 , 240 ] ,
forestgreen : [ 34 , 139 , 34 ] ,
fuchsia : [ 255 , 0 , 255 ] ,
gainsboro : [ 220 , 220 , 220 ] ,
ghostwhite : [ 248 , 248 , 255 ] ,
gold : [ 255 , 215 , 0 ] ,
goldenrod : [ 218 , 165 , 32 ] ,
gray : [ 128 , 128 , 128 ] ,
green : [ 0 , 128 , 0 ] ,
greenyellow : [ 173 , 255 , 47 ] ,
grey : [ 128 , 128 , 128 ] ,
honeydew : [ 240 , 255 , 240 ] ,
hotpink : [ 255 , 105 , 180 ] ,
indianred : [ 205 , 92 , 92 ] ,
indigo : [ 75 , 0 , 130 ] ,
ivory : [ 255 , 255 , 240 ] ,
khaki : [ 240 , 230 , 140 ] ,
lavender : [ 230 , 230 , 250 ] ,
lavenderblush : [ 255 , 240 , 245 ] ,
lawngreen : [ 124 , 252 , 0 ] ,
lemonchiffon : [ 255 , 250 , 205 ] ,
lightblue : [ 173 , 216 , 230 ] ,
lightcoral : [ 240 , 128 , 128 ] ,
lightcyan : [ 224 , 255 , 255 ] ,
lightgoldenrodyellow : [ 250 , 250 , 210 ] ,
lightgray : [ 211 , 211 , 211 ] ,
lightgreen : [ 144 , 238 , 144 ] ,
lightgrey : [ 211 , 211 , 211 ] ,
lightpink : [ 255 , 182 , 193 ] ,
lightsalmon : [ 255 , 160 , 122 ] ,
lightseagreen : [ 32 , 178 , 170 ] ,
lightskyblue : [ 135 , 206 , 250 ] ,
lightslategray : [ 119 , 136 , 153 ] ,
lightslategrey : [ 119 , 136 , 153 ] ,
lightsteelblue : [ 176 , 196 , 222 ] ,
lightyellow : [ 255 , 255 , 224 ] ,
lime : [ 0 , 255 , 0 ] ,
limegreen : [ 50 , 205 , 50 ] ,
linen : [ 250 , 240 , 230 ] ,
magenta : [ 255 , 0 , 255 ] ,
maroon : [ 128 , 0 , 0 ] ,
mediumaquamarine : [ 102 , 205 , 170 ] ,
mediumblue : [ 0 , 0 , 205 ] ,
mediumorchid : [ 186 , 85 , 211 ] ,
mediumpurple : [ 147 , 112 , 219 ] ,
mediumseagreen : [ 60 , 179 , 113 ] ,
mediumslateblue : [ 123 , 104 , 238 ] ,
mediumspringgreen : [ 0 , 250 , 154 ] ,
mediumturquoise : [ 72 , 209 , 204 ] ,
mediumvioletred : [ 199 , 21 , 133 ] ,
midnightblue : [ 25 , 25 , 112 ] ,
mintcream : [ 245 , 255 , 250 ] ,
mistyrose : [ 255 , 228 , 225 ] ,
moccasin : [ 255 , 228 , 181 ] ,
navajowhite : [ 255 , 222 , 173 ] ,
navy : [ 0 , 0 , 128 ] ,
oldlace : [ 253 , 245 , 230 ] ,
olive : [ 128 , 128 , 0 ] ,
olivedrab : [ 107 , 142 , 35 ] ,
orange : [ 255 , 165 , 0 ] ,
orangered : [ 255 , 69 , 0 ] ,
orchid : [ 218 , 112 , 214 ] ,
palegoldenrod : [ 238 , 232 , 170 ] ,
palegreen : [ 152 , 251 , 152 ] ,
paleturquoise : [ 175 , 238 , 238 ] ,
palevioletred : [ 219 , 112 , 147 ] ,
papayawhip : [ 255 , 239 , 213 ] ,
peachpuff : [ 255 , 218 , 185 ] ,
peru : [ 205 , 133 , 63 ] ,
pink : [ 255 , 192 , 203 ] ,
plum : [ 221 , 160 , 221 ] ,
powderblue : [ 176 , 224 , 230 ] ,
purple : [ 128 , 0 , 128 ] ,
rebeccapurple : [ 102 , 51 , 153 ] ,
red : [ 255 , 0 , 0 ] ,
rosybrown : [ 188 , 143 , 143 ] ,
royalblue : [ 65 , 105 , 225 ] ,
saddlebrown : [ 139 , 69 , 19 ] ,
salmon : [ 250 , 128 , 114 ] ,
sandybrown : [ 244 , 164 , 96 ] ,
seagreen : [ 46 , 139 , 87 ] ,
seashell : [ 255 , 245 , 238 ] ,
sienna : [ 160 , 82 , 45 ] ,
silver : [ 192 , 192 , 192 ] ,
skyblue : [ 135 , 206 , 235 ] ,
slateblue : [ 106 , 90 , 205 ] ,
slategray : [ 112 , 128 , 144 ] ,
slategrey : [ 112 , 128 , 144 ] ,
snow : [ 255 , 250 , 250 ] ,
springgreen : [ 0 , 255 , 127 ] ,
steelblue : [ 70 , 130 , 180 ] ,
tan : [ 210 , 180 , 140 ] ,
teal : [ 0 , 128 , 128 ] ,
thistle : [ 216 , 191 , 216 ] ,
tomato : [ 255 , 99 , 71 ] ,
turquoise : [ 64 , 224 , 208 ] ,
violet : [ 238 , 130 , 238 ] ,
wheat : [ 245 , 222 , 179 ] ,
white : [ 255 , 255 , 255 ] ,
whitesmoke : [ 245 , 245 , 245 ] ,
yellow : [ 255 , 255 , 0 ] ,
yellowgreen : [ 154 , 205 , 50 ]
} ;
var reverseKeywords = { } ;
for ( var key in cssKeywords ) {
reverseKeywords [ JSON . stringify ( cssKeywords [ key ] ) ] = key ;
}
} , { } ] , 5 : [ function ( require , module , exports ) {
var conversions = require ( 4 ) ;
var convert = function ( ) {
return new Converter ( ) ;
}
for ( var func in conversions ) {
// export Raw versions
convert [ func + "Raw" ] = ( function ( func ) {
// accept array or plain args
return function ( arg ) {
if ( typeof arg == "number" )
arg = Array . prototype . slice . call ( arguments ) ;
return conversions [ func ] ( arg ) ;
}
} ) ( func ) ;
var pair = /(\w+)2(\w+)/ . exec ( func ) ,
from = pair [ 1 ] ,
to = pair [ 2 ] ;
// export rgb2hsl and ["rgb"]["hsl"]
convert [ from ] = convert [ from ] || { } ;
convert [ from ] [ to ] = convert [ func ] = ( function ( func ) {
return function ( arg ) {
if ( typeof arg == "number" )
arg = Array . prototype . slice . call ( arguments ) ;
var val = conversions [ func ] ( arg ) ;
if ( typeof val == "string" || val === undefined )
return val ; // keyword
for ( var i = 0 ; i < val . length ; i ++ )
val [ i ] = Math . round ( val [ i ] ) ;
return val ;
}
} ) ( func ) ;
}
/* Converter does lazy conversion and caching */
var Converter = function ( ) {
this . convs = { } ;
} ;
/ * E i t h e r g e t t h e v a l u e s f o r a s p a c e o r
set the values for a space , depending on args * /
Converter . prototype . routeSpace = function ( space , args ) {
var values = args [ 0 ] ;
if ( values === undefined ) {
// color.rgb()
return this . getValues ( space ) ;
}
// color.rgb(10, 10, 10)
if ( typeof values == "number" ) {
values = Array . prototype . slice . call ( args ) ;
}
return this . setValues ( space , values ) ;
} ;
/* Set the values for a space, invalidating cache */
Converter . prototype . setValues = function ( space , values ) {
this . space = space ;
this . convs = { } ;
this . convs [ space ] = values ;
return this ;
} ;
/ * G e t t h e v a l u e s f o r a s p a c e . I f t h e r e ' s a l r e a d y
a conversion for the space , fetch it , otherwise
compute it * /
Converter . prototype . getValues = function ( space ) {
var vals = this . convs [ space ] ;
if ( ! vals ) {
var fspace = this . space ,
from = this . convs [ fspace ] ;
vals = convert [ fspace ] [ space ] ( from ) ;
this . convs [ space ] = vals ;
}
return vals ;
} ;
[ "rgb" , "hsl" , "hsv" , "cmyk" , "keyword" ] . forEach ( function ( space ) {
Converter . prototype [ space ] = function ( vals ) {
return this . routeSpace ( space , arguments ) ;
}
} ) ;
module . exports = convert ;
} , { "4" : 4 } ] , 6 : [ function ( require , module , exports ) {
module . exports = {
"aliceblue" : [ 240 , 248 , 255 ] ,
"antiquewhite" : [ 250 , 235 , 215 ] ,
"aqua" : [ 0 , 255 , 255 ] ,
"aquamarine" : [ 127 , 255 , 212 ] ,
"azure" : [ 240 , 255 , 255 ] ,
"beige" : [ 245 , 245 , 220 ] ,
"bisque" : [ 255 , 228 , 196 ] ,
"black" : [ 0 , 0 , 0 ] ,
"blanchedalmond" : [ 255 , 235 , 205 ] ,
"blue" : [ 0 , 0 , 255 ] ,
"blueviolet" : [ 138 , 43 , 226 ] ,
"brown" : [ 165 , 42 , 42 ] ,
"burlywood" : [ 222 , 184 , 135 ] ,
"cadetblue" : [ 95 , 158 , 160 ] ,
"chartreuse" : [ 127 , 255 , 0 ] ,
"chocolate" : [ 210 , 105 , 30 ] ,
"coral" : [ 255 , 127 , 80 ] ,
"cornflowerblue" : [ 100 , 149 , 237 ] ,
"cornsilk" : [ 255 , 248 , 220 ] ,
"crimson" : [ 220 , 20 , 60 ] ,
"cyan" : [ 0 , 255 , 255 ] ,
"darkblue" : [ 0 , 0 , 139 ] ,
"darkcyan" : [ 0 , 139 , 139 ] ,
"darkgoldenrod" : [ 184 , 134 , 11 ] ,
"darkgray" : [ 169 , 169 , 169 ] ,
"darkgreen" : [ 0 , 100 , 0 ] ,
"darkgrey" : [ 169 , 169 , 169 ] ,
"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 ] ,
"darkseagreen" : [ 143 , 188 , 143 ] ,
"darkslateblue" : [ 72 , 61 , 139 ] ,
"darkslategray" : [ 47 , 79 , 79 ] ,
"darkslategrey" : [ 47 , 79 , 79 ] ,
"darkturquoise" : [ 0 , 206 , 209 ] ,
"darkviolet" : [ 148 , 0 , 211 ] ,
"deeppink" : [ 255 , 20 , 147 ] ,
"deepskyblue" : [ 0 , 191 , 255 ] ,
"dimgray" : [ 105 , 105 , 105 ] ,
"dimgrey" : [ 105 , 105 , 105 ] ,
"dodgerblue" : [ 30 , 144 , 255 ] ,
"firebrick" : [ 178 , 34 , 34 ] ,
"floralwhite" : [ 255 , 250 , 240 ] ,
"forestgreen" : [ 34 , 139 , 34 ] ,
"fuchsia" : [ 255 , 0 , 255 ] ,
"gainsboro" : [ 220 , 220 , 220 ] ,
"ghostwhite" : [ 248 , 248 , 255 ] ,
"gold" : [ 255 , 215 , 0 ] ,
"goldenrod" : [ 218 , 165 , 32 ] ,
"gray" : [ 128 , 128 , 128 ] ,
"green" : [ 0 , 128 , 0 ] ,
"greenyellow" : [ 173 , 255 , 47 ] ,
"grey" : [ 128 , 128 , 128 ] ,
"honeydew" : [ 240 , 255 , 240 ] ,
"hotpink" : [ 255 , 105 , 180 ] ,
"indianred" : [ 205 , 92 , 92 ] ,
"indigo" : [ 75 , 0 , 130 ] ,
"ivory" : [ 255 , 255 , 240 ] ,
"khaki" : [ 240 , 230 , 140 ] ,
"lavender" : [ 230 , 230 , 250 ] ,
"lavenderblush" : [ 255 , 240 , 245 ] ,
"lawngreen" : [ 124 , 252 , 0 ] ,
"lemonchiffon" : [ 255 , 250 , 205 ] ,
"lightblue" : [ 173 , 216 , 230 ] ,
"lightcoral" : [ 240 , 128 , 128 ] ,
"lightcyan" : [ 224 , 255 , 255 ] ,
"lightgoldenrodyellow" : [ 250 , 250 , 210 ] ,
"lightgray" : [ 211 , 211 , 211 ] ,
"lightgreen" : [ 144 , 238 , 144 ] ,
"lightgrey" : [ 211 , 211 , 211 ] ,
"lightpink" : [ 255 , 182 , 193 ] ,
"lightsalmon" : [ 255 , 160 , 122 ] ,
"lightseagreen" : [ 32 , 178 , 170 ] ,
"lightskyblue" : [ 135 , 206 , 250 ] ,
"lightslategray" : [ 119 , 136 , 153 ] ,
"lightslategrey" : [ 119 , 136 , 153 ] ,
"lightsteelblue" : [ 176 , 196 , 222 ] ,
"lightyellow" : [ 255 , 255 , 224 ] ,
"lime" : [ 0 , 255 , 0 ] ,
"limegreen" : [ 50 , 205 , 50 ] ,
"linen" : [ 250 , 240 , 230 ] ,
"magenta" : [ 255 , 0 , 255 ] ,
"maroon" : [ 128 , 0 , 0 ] ,
"mediumaquamarine" : [ 102 , 205 , 170 ] ,
"mediumblue" : [ 0 , 0 , 205 ] ,
"mediumorchid" : [ 186 , 85 , 211 ] ,
"mediumpurple" : [ 147 , 112 , 219 ] ,
"mediumseagreen" : [ 60 , 179 , 113 ] ,
"mediumslateblue" : [ 123 , 104 , 238 ] ,
"mediumspringgreen" : [ 0 , 250 , 154 ] ,
"mediumturquoise" : [ 72 , 209 , 204 ] ,
"mediumvioletred" : [ 199 , 21 , 133 ] ,
"midnightblue" : [ 25 , 25 , 112 ] ,
"mintcream" : [ 245 , 255 , 250 ] ,
"mistyrose" : [ 255 , 228 , 225 ] ,
"moccasin" : [ 255 , 228 , 181 ] ,
"navajowhite" : [ 255 , 222 , 173 ] ,
"navy" : [ 0 , 0 , 128 ] ,
"oldlace" : [ 253 , 245 , 230 ] ,
"olive" : [ 128 , 128 , 0 ] ,
"olivedrab" : [ 107 , 142 , 35 ] ,
"orange" : [ 255 , 165 , 0 ] ,
"orangered" : [ 255 , 69 , 0 ] ,
"orchid" : [ 218 , 112 , 214 ] ,
"palegoldenrod" : [ 238 , 232 , 170 ] ,
"palegreen" : [ 152 , 251 , 152 ] ,
"paleturquoise" : [ 175 , 238 , 238 ] ,
"palevioletred" : [ 219 , 112 , 147 ] ,
"papayawhip" : [ 255 , 239 , 213 ] ,
"peachpuff" : [ 255 , 218 , 185 ] ,
"peru" : [ 205 , 133 , 63 ] ,
"pink" : [ 255 , 192 , 203 ] ,
"plum" : [ 221 , 160 , 221 ] ,
"powderblue" : [ 176 , 224 , 230 ] ,
"purple" : [ 128 , 0 , 128 ] ,
"rebeccapurple" : [ 102 , 51 , 153 ] ,
"red" : [ 255 , 0 , 0 ] ,
"rosybrown" : [ 188 , 143 , 143 ] ,
"royalblue" : [ 65 , 105 , 225 ] ,
"saddlebrown" : [ 139 , 69 , 19 ] ,
"salmon" : [ 250 , 128 , 114 ] ,
"sandybrown" : [ 244 , 164 , 96 ] ,
"seagreen" : [ 46 , 139 , 87 ] ,
"seashell" : [ 255 , 245 , 238 ] ,
"sienna" : [ 160 , 82 , 45 ] ,
"silver" : [ 192 , 192 , 192 ] ,
"skyblue" : [ 135 , 206 , 235 ] ,
"slateblue" : [ 106 , 90 , 205 ] ,
"slategray" : [ 112 , 128 , 144 ] ,
"slategrey" : [ 112 , 128 , 144 ] ,
"snow" : [ 255 , 250 , 250 ] ,
"springgreen" : [ 0 , 255 , 127 ] ,
"steelblue" : [ 70 , 130 , 180 ] ,
"tan" : [ 210 , 180 , 140 ] ,
"teal" : [ 0 , 128 , 128 ] ,
"thistle" : [ 216 , 191 , 216 ] ,
"tomato" : [ 255 , 99 , 71 ] ,
"turquoise" : [ 64 , 224 , 208 ] ,
"violet" : [ 238 , 130 , 238 ] ,
"wheat" : [ 245 , 222 , 179 ] ,
"white" : [ 255 , 255 , 255 ] ,
"whitesmoke" : [ 245 , 245 , 245 ] ,
"yellow" : [ 255 , 255 , 0 ] ,
"yellowgreen" : [ 154 , 205 , 50 ]
} ;
} , { } ] , 7 : [ function ( require , module , exports ) {
/ * *
* @ namespace Chart
* /
var Chart = require ( 27 ) ( ) ;
require ( 26 ) ( Chart ) ;
require ( 22 ) ( Chart ) ;
require ( 25 ) ( Chart ) ;
require ( 21 ) ( Chart ) ;
require ( 23 ) ( Chart ) ;
require ( 24 ) ( Chart ) ;
require ( 28 ) ( Chart ) ;
require ( 32 ) ( Chart ) ;
require ( 30 ) ( Chart ) ;
require ( 31 ) ( Chart ) ;
require ( 33 ) ( Chart ) ;
require ( 29 ) ( Chart ) ;
require ( 34 ) ( Chart ) ;
require ( 35 ) ( Chart ) ;
require ( 36 ) ( Chart ) ;
require ( 37 ) ( Chart ) ;
require ( 38 ) ( Chart ) ;
require ( 41 ) ( Chart ) ;
require ( 39 ) ( Chart ) ;
require ( 40 ) ( Chart ) ;
require ( 42 ) ( Chart ) ;
require ( 43 ) ( Chart ) ;
require ( 44 ) ( Chart ) ;
// Controllers must be loaded after elements
// See Chart.core.datasetController.dataElementType
require ( 15 ) ( Chart ) ;
require ( 16 ) ( Chart ) ;
require ( 17 ) ( Chart ) ;
require ( 18 ) ( Chart ) ;
require ( 19 ) ( Chart ) ;
require ( 20 ) ( Chart ) ;
require ( 8 ) ( Chart ) ;
require ( 9 ) ( Chart ) ;
require ( 10 ) ( Chart ) ;
require ( 11 ) ( Chart ) ;
require ( 12 ) ( Chart ) ;
require ( 13 ) ( Chart ) ;
require ( 14 ) ( Chart ) ;
window . Chart = module . exports = Chart ;
} , { "10" : 10 , "11" : 11 , "12" : 12 , "13" : 13 , "14" : 14 , "15" : 15 , "16" : 16 , "17" : 17 , "18" : 18 , "19" : 19 , "20" : 20 , "21" : 21 , "22" : 22 , "23" : 23 , "24" : 24 , "25" : 25 , "26" : 26 , "27" : 27 , "28" : 28 , "29" : 29 , "30" : 30 , "31" : 31 , "32" : 32 , "33" : 33 , "34" : 34 , "35" : 35 , "36" : 36 , "37" : 37 , "38" : 38 , "39" : 39 , "40" : 40 , "41" : 41 , "42" : 42 , "43" : 43 , "44" : 44 , "8" : 8 , "9" : 9 } ] , 8 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
Chart . Bar = function ( context , config ) {
config . type = 'bar' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 9 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
Chart . Bubble = function ( context , config ) {
config . type = 'bubble' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 10 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
Chart . Doughnut = function ( context , config ) {
config . type = 'doughnut' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 11 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
Chart . Line = function ( context , config ) {
config . type = 'line' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 12 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
Chart . PolarArea = function ( context , config ) {
config . type = 'polarArea' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 13 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
Chart . Radar = function ( context , config ) {
config . options = Chart . helpers . configMerge ( { aspectRatio : 1 } , config . options ) ;
config . type = 'radar' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 14 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var defaultConfig = {
hover : {
mode : 'single'
} ,
scales : {
xAxes : [ {
type : "linear" , // scatter should not use a category axis
position : "bottom" ,
id : "x-axis-1" // need an ID so datasets can reference the scale
} ] ,
yAxes : [ {
type : "linear" ,
position : "left" ,
id : "y-axis-1"
} ]
} ,
tooltips : {
callbacks : {
title : function ( ) {
// Title doesn't make sense for scatter since we format the data as a point
return '' ;
} ,
label : function ( tooltipItem ) {
return '(' + tooltipItem . xLabel + ', ' + tooltipItem . yLabel + ')' ;
}
}
}
} ;
// Register the default config for this type
Chart . defaults . scatter = defaultConfig ;
// Scatter charts use line controllers
Chart . controllers . scatter = Chart . controllers . line ;
Chart . Scatter = function ( context , config ) {
config . type = 'scatter' ;
return new Chart ( context , config ) ;
} ;
} ;
} , { } ] , 15 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . bar = {
hover : {
mode : "label"
} ,
scales : {
xAxes : [ {
type : "category" ,
// Specific to Bar Controller
categoryPercentage : 0.8 ,
barPercentage : 0.9 ,
// grid line settings
gridLines : {
offsetGridLines : true
}
} ] ,
yAxes : [ {
type : "linear"
} ]
}
} ;
Chart . controllers . bar = Chart . DatasetController . extend ( {
dataElementType : Chart . elements . Rectangle ,
initialize : function ( chart , datasetIndex ) {
Chart . DatasetController . prototype . initialize . call ( this , chart , datasetIndex ) ;
// Use this to indicate that this is a bar dataset.
this . getMeta ( ) . bar = true ;
} ,
// Get the number of datasets that display bars. We use this to correctly calculate the bar width
getBarCount : function ( ) {
var me = this ;
var barCount = 0 ;
helpers . each ( me . chart . data . datasets , function ( dataset , datasetIndex ) {
var meta = me . chart . getDatasetMeta ( datasetIndex ) ;
if ( meta . bar && me . chart . isDatasetVisible ( datasetIndex ) ) {
++ barCount ;
}
} , me ) ;
return barCount ;
} ,
update : function ( reset ) {
var me = this ;
helpers . each ( me . getMeta ( ) . data , function ( rectangle , index ) {
me . updateElement ( rectangle , index , reset ) ;
} , me ) ;
} ,
updateElement : function ( rectangle , index , reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var scaleBase = yScale . getBasePixel ( ) ;
var rectangleElementOptions = me . chart . options . elements . rectangle ;
var custom = rectangle . custom || { } ;
var dataset = me . getDataset ( ) ;
helpers . extend ( rectangle , {
// Utility
_xScale : xScale ,
_yScale : yScale ,
_datasetIndex : me . index ,
_index : index ,
// Desired view properties
_model : {
x : me . calculateBarX ( index , me . index ) ,
y : reset ? scaleBase : me . calculateBarY ( index , me . index ) ,
// Tooltip
label : me . chart . data . labels [ index ] ,
datasetLabel : dataset . label ,
// Appearance
base : reset ? scaleBase : me . calculateBarBase ( me . index , index ) ,
width : me . calculateBarWidth ( index ) ,
backgroundColor : custom . backgroundColor ? custom . backgroundColor : helpers . getValueAtIndexOrDefault ( dataset . backgroundColor , index , rectangleElementOptions . backgroundColor ) ,
borderSkipped : custom . borderSkipped ? custom . borderSkipped : rectangleElementOptions . borderSkipped ,
borderColor : custom . borderColor ? custom . borderColor : helpers . getValueAtIndexOrDefault ( dataset . borderColor , index , rectangleElementOptions . borderColor ) ,
borderWidth : custom . borderWidth ? custom . borderWidth : helpers . getValueAtIndexOrDefault ( dataset . borderWidth , index , rectangleElementOptions . borderWidth )
}
} ) ;
rectangle . pivot ( ) ;
} ,
calculateBarBase : function ( datasetIndex , index ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var base = 0 ;
if ( yScale . options . stacked ) {
var chart = me . chart ;
var datasets = chart . data . datasets ;
var value = Number ( datasets [ datasetIndex ] . data [ index ] ) ;
for ( var i = 0 ; i < datasetIndex ; i ++ ) {
var currentDs = datasets [ i ] ;
var currentDsMeta = chart . getDatasetMeta ( i ) ;
if ( currentDsMeta . bar && currentDsMeta . yAxisID === yScale . id && chart . isDatasetVisible ( i ) ) {
var currentVal = Number ( currentDs . data [ index ] ) ;
base += value < 0 ? Math . min ( currentVal , 0 ) : Math . max ( currentVal , 0 ) ;
}
}
return yScale . getPixelForValue ( base ) ;
}
return yScale . getBasePixel ( ) ;
} ,
getRuler : function ( index ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var datasetCount = me . getBarCount ( ) ;
var tickWidth ;
if ( xScale . options . type === 'category' ) {
tickWidth = xScale . getPixelForTick ( index + 1 ) - xScale . getPixelForTick ( index ) ;
} else {
// Average width
tickWidth = xScale . width / xScale . ticks . length ;
}
var categoryWidth = tickWidth * xScale . options . categoryPercentage ;
var categorySpacing = ( tickWidth - ( tickWidth * xScale . options . categoryPercentage ) ) / 2 ;
var fullBarWidth = categoryWidth / datasetCount ;
if ( xScale . ticks . length !== me . chart . data . labels . length ) {
var perc = xScale . ticks . length / me . chart . data . labels . length ;
fullBarWidth = fullBarWidth * perc ;
}
var barWidth = fullBarWidth * xScale . options . barPercentage ;
var barSpacing = fullBarWidth - ( fullBarWidth * xScale . options . barPercentage ) ;
return {
datasetCount : datasetCount ,
tickWidth : tickWidth ,
categoryWidth : categoryWidth ,
categorySpacing : categorySpacing ,
fullBarWidth : fullBarWidth ,
barWidth : barWidth ,
barSpacing : barSpacing
} ;
} ,
calculateBarWidth : function ( index ) {
var xScale = this . getScaleForId ( this . getMeta ( ) . xAxisID ) ;
if ( xScale . options . barThickness ) {
return xScale . options . barThickness ;
}
var ruler = this . getRuler ( index ) ;
return xScale . options . stacked ? ruler . categoryWidth : ruler . barWidth ;
} ,
// Get bar index from the given dataset index accounting for the fact that not all bars are visible
getBarIndex : function ( datasetIndex ) {
var barIndex = 0 ;
var meta , j ;
for ( j = 0 ; j < datasetIndex ; ++ j ) {
meta = this . chart . getDatasetMeta ( j ) ;
if ( meta . bar && this . chart . isDatasetVisible ( j ) ) {
++ barIndex ;
}
}
return barIndex ;
} ,
calculateBarX : function ( index , datasetIndex ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var barIndex = me . getBarIndex ( datasetIndex ) ;
var ruler = me . getRuler ( index ) ;
var leftTick = xScale . getPixelForValue ( null , index , datasetIndex , me . chart . isCombo ) ;
leftTick -= me . chart . isCombo ? ( ruler . tickWidth / 2 ) : 0 ;
if ( xScale . options . stacked ) {
return leftTick + ( ruler . categoryWidth / 2 ) + ruler . categorySpacing ;
}
return leftTick +
( ruler . barWidth / 2 ) +
ruler . categorySpacing +
( ruler . barWidth * barIndex ) +
( ruler . barSpacing / 2 ) +
( ruler . barSpacing * barIndex ) ;
} ,
calculateBarY : function ( index , datasetIndex ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var value = Number ( me . getDataset ( ) . data [ index ] ) ;
if ( yScale . options . stacked ) {
var sumPos = 0 ,
sumNeg = 0 ;
for ( var i = 0 ; i < datasetIndex ; i ++ ) {
var ds = me . chart . data . datasets [ i ] ;
var dsMeta = me . chart . getDatasetMeta ( i ) ;
if ( dsMeta . bar && dsMeta . yAxisID === yScale . id && me . chart . isDatasetVisible ( i ) ) {
var stackedVal = Number ( ds . data [ index ] ) ;
if ( stackedVal < 0 ) {
sumNeg += stackedVal || 0 ;
} else {
sumPos += stackedVal || 0 ;
}
}
}
if ( value < 0 ) {
return yScale . getPixelForValue ( sumNeg + value ) ;
} else {
return yScale . getPixelForValue ( sumPos + value ) ;
}
}
return yScale . getPixelForValue ( value ) ;
} ,
draw : function ( ease ) {
var me = this ;
var easingDecimal = ease || 1 ;
helpers . each ( me . getMeta ( ) . data , function ( rectangle , index ) {
var d = me . getDataset ( ) . data [ index ] ;
if ( d !== null && d !== undefined && ! isNaN ( d ) ) {
rectangle . transition ( easingDecimal ) . draw ( ) ;
}
} , me ) ;
} ,
setHoverStyle : function ( rectangle ) {
var dataset = this . chart . data . datasets [ rectangle . _datasetIndex ] ;
var index = rectangle . _index ;
var custom = rectangle . custom || { } ;
var model = rectangle . _model ;
model . backgroundColor = custom . hoverBackgroundColor ? custom . hoverBackgroundColor : helpers . getValueAtIndexOrDefault ( dataset . hoverBackgroundColor , index , helpers . getHoverColor ( model . backgroundColor ) ) ;
model . borderColor = custom . hoverBorderColor ? custom . hoverBorderColor : helpers . getValueAtIndexOrDefault ( dataset . hoverBorderColor , index , helpers . getHoverColor ( model . borderColor ) ) ;
model . borderWidth = custom . hoverBorderWidth ? custom . hoverBorderWidth : helpers . getValueAtIndexOrDefault ( dataset . hoverBorderWidth , index , model . borderWidth ) ;
} ,
removeHoverStyle : function ( rectangle ) {
var dataset = this . chart . data . datasets [ rectangle . _datasetIndex ] ;
var index = rectangle . _index ;
var custom = rectangle . custom || { } ;
var model = rectangle . _model ;
var rectangleElementOptions = this . chart . options . elements . rectangle ;
model . backgroundColor = custom . backgroundColor ? custom . backgroundColor : helpers . getValueAtIndexOrDefault ( dataset . backgroundColor , index , rectangleElementOptions . backgroundColor ) ;
model . borderColor = custom . borderColor ? custom . borderColor : helpers . getValueAtIndexOrDefault ( dataset . borderColor , index , rectangleElementOptions . borderColor ) ;
model . borderWidth = custom . borderWidth ? custom . borderWidth : helpers . getValueAtIndexOrDefault ( dataset . borderWidth , index , rectangleElementOptions . borderWidth ) ;
}
} ) ;
// including horizontalBar in the bar file, instead of a file of its own
// it extends bar (like pie extends doughnut)
Chart . defaults . horizontalBar = {
hover : {
mode : "label"
} ,
scales : {
xAxes : [ {
type : "linear" ,
position : "bottom"
} ] ,
yAxes : [ {
position : "left" ,
type : "category" ,
// Specific to Horizontal Bar Controller
categoryPercentage : 0.8 ,
barPercentage : 0.9 ,
// grid line settings
gridLines : {
offsetGridLines : true
}
} ]
} ,
elements : {
rectangle : {
borderSkipped : 'left'
}
} ,
tooltips : {
callbacks : {
title : function ( tooltipItems , data ) {
// Pick first xLabel for now
var title = '' ;
if ( tooltipItems . length > 0 ) {
if ( tooltipItems [ 0 ] . yLabel ) {
title = tooltipItems [ 0 ] . yLabel ;
} else if ( data . labels . length > 0 && tooltipItems [ 0 ] . index < data . labels . length ) {
title = data . labels [ tooltipItems [ 0 ] . index ] ;
}
}
return title ;
} ,
label : function ( tooltipItem , data ) {
var datasetLabel = data . datasets [ tooltipItem . datasetIndex ] . label || '' ;
return datasetLabel + ': ' + tooltipItem . xLabel ;
}
}
}
} ;
Chart . controllers . horizontalBar = Chart . controllers . bar . extend ( {
updateElement : function ( rectangle , index , reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var scaleBase = xScale . getBasePixel ( ) ;
var custom = rectangle . custom || { } ;
var dataset = me . getDataset ( ) ;
var rectangleElementOptions = me . chart . options . elements . rectangle ;
helpers . extend ( rectangle , {
// Utility
_xScale : xScale ,
_yScale : yScale ,
_datasetIndex : me . index ,
_index : index ,
// Desired view properties
_model : {
x : reset ? scaleBase : me . calculateBarX ( index , me . index ) ,
y : me . calculateBarY ( index , me . index ) ,
// Tooltip
label : me . chart . data . labels [ index ] ,
datasetLabel : dataset . label ,
// Appearance
base : reset ? scaleBase : me . calculateBarBase ( me . index , index ) ,
height : me . calculateBarHeight ( index ) ,
backgroundColor : custom . backgroundColor ? custom . backgroundColor : helpers . getValueAtIndexOrDefault ( dataset . backgroundColor , index , rectangleElementOptions . backgroundColor ) ,
borderSkipped : custom . borderSkipped ? custom . borderSkipped : rectangleElementOptions . borderSkipped ,
borderColor : custom . borderColor ? custom . borderColor : helpers . getValueAtIndexOrDefault ( dataset . borderColor , index , rectangleElementOptions . borderColor ) ,
borderWidth : custom . borderWidth ? custom . borderWidth : helpers . getValueAtIndexOrDefault ( dataset . borderWidth , index , rectangleElementOptions . borderWidth )
} ,
draw : function ( ) {
var ctx = this . _chart . ctx ;
var vm = this . _view ;
var halfHeight = vm . height / 2 ,
topY = vm . y - halfHeight ,
bottomY = vm . y + halfHeight ,
right = vm . base - ( vm . base - vm . x ) ,
halfStroke = vm . borderWidth / 2 ;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if ( vm . borderWidth ) {
topY += halfStroke ;
bottomY -= halfStroke ;
right += halfStroke ;
}
ctx . beginPath ( ) ;
ctx . fillStyle = vm . backgroundColor ;
ctx . strokeStyle = vm . borderColor ;
ctx . lineWidth = vm . borderWidth ;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[ vm . base , bottomY ] ,
[ vm . base , topY ] ,
[ right , topY ] ,
[ right , bottomY ]
] ;
// Find first (starting) corner with fallback to 'bottom'
var borders = [ 'bottom' , 'left' , 'top' , 'right' ] ;
var startCorner = borders . indexOf ( vm . borderSkipped , 0 ) ;
if ( startCorner === - 1 )
startCorner = 0 ;
function cornerAt ( index ) {
return corners [ ( startCorner + index ) % 4 ] ;
}
// Draw rectangle from 'startCorner'
ctx . moveTo . apply ( ctx , cornerAt ( 0 ) ) ;
for ( var i = 1 ; i < 4 ; i ++ )
ctx . lineTo . apply ( ctx , cornerAt ( i ) ) ;
ctx . fill ( ) ;
if ( vm . borderWidth ) {
ctx . stroke ( ) ;
}
} ,
inRange : function ( mouseX , mouseY ) {
var vm = this . _view ;
var inRange = false ;
if ( vm ) {
if ( vm . x < vm . base ) {
inRange = ( mouseY >= vm . y - vm . height / 2 && mouseY <= vm . y + vm . height / 2 ) && ( mouseX >= vm . x && mouseX <= vm . base ) ;
} else {
inRange = ( mouseY >= vm . y - vm . height / 2 && mouseY <= vm . y + vm . height / 2 ) && ( mouseX >= vm . base && mouseX <= vm . x ) ;
}
}
return inRange ;
}
} ) ;
rectangle . pivot ( ) ;
} ,
calculateBarBase : function ( datasetIndex , index ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var base = 0 ;
if ( xScale . options . stacked ) {
var chart = me . chart ;
var datasets = chart . data . datasets ;
var value = Number ( datasets [ datasetIndex ] . data [ index ] ) ;
for ( var i = 0 ; i < datasetIndex ; i ++ ) {
var currentDs = datasets [ i ] ;
var currentDsMeta = chart . getDatasetMeta ( i ) ;
if ( currentDsMeta . bar && currentDsMeta . xAxisID === xScale . id && chart . isDatasetVisible ( i ) ) {
var currentVal = Number ( currentDs . data [ index ] ) ;
base += value < 0 ? Math . min ( currentVal , 0 ) : Math . max ( currentVal , 0 ) ;
}
}
return xScale . getPixelForValue ( base ) ;
}
return xScale . getBasePixel ( ) ;
} ,
getRuler : function ( index ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var datasetCount = me . getBarCount ( ) ;
var tickHeight ;
if ( yScale . options . type === 'category' ) {
tickHeight = yScale . getPixelForTick ( index + 1 ) - yScale . getPixelForTick ( index ) ;
} else {
// Average width
tickHeight = yScale . width / yScale . ticks . length ;
}
var categoryHeight = tickHeight * yScale . options . categoryPercentage ;
var categorySpacing = ( tickHeight - ( tickHeight * yScale . options . categoryPercentage ) ) / 2 ;
var fullBarHeight = categoryHeight / datasetCount ;
if ( yScale . ticks . length !== me . chart . data . labels . length ) {
var perc = yScale . ticks . length / me . chart . data . labels . length ;
fullBarHeight = fullBarHeight * perc ;
}
var barHeight = fullBarHeight * yScale . options . barPercentage ;
var barSpacing = fullBarHeight - ( fullBarHeight * yScale . options . barPercentage ) ;
return {
datasetCount : datasetCount ,
tickHeight : tickHeight ,
categoryHeight : categoryHeight ,
categorySpacing : categorySpacing ,
fullBarHeight : fullBarHeight ,
barHeight : barHeight ,
barSpacing : barSpacing
} ;
} ,
calculateBarHeight : function ( index ) {
var me = this ;
var yScale = me . getScaleForId ( me . getMeta ( ) . yAxisID ) ;
if ( yScale . options . barThickness ) {
return yScale . options . barThickness ;
}
var ruler = me . getRuler ( index ) ;
return yScale . options . stacked ? ruler . categoryHeight : ruler . barHeight ;
} ,
calculateBarX : function ( index , datasetIndex ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var value = Number ( me . getDataset ( ) . data [ index ] ) ;
if ( xScale . options . stacked ) {
var sumPos = 0 ,
sumNeg = 0 ;
for ( var i = 0 ; i < datasetIndex ; i ++ ) {
var ds = me . chart . data . datasets [ i ] ;
var dsMeta = me . chart . getDatasetMeta ( i ) ;
if ( dsMeta . bar && dsMeta . xAxisID === xScale . id && me . chart . isDatasetVisible ( i ) ) {
var stackedVal = Number ( ds . data [ index ] ) ;
if ( stackedVal < 0 ) {
sumNeg += stackedVal || 0 ;
} else {
sumPos += stackedVal || 0 ;
}
}
}
if ( value < 0 ) {
return xScale . getPixelForValue ( sumNeg + value ) ;
} else {
return xScale . getPixelForValue ( sumPos + value ) ;
}
}
return xScale . getPixelForValue ( value ) ;
} ,
calculateBarY : function ( index , datasetIndex ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var barIndex = me . getBarIndex ( datasetIndex ) ;
var ruler = me . getRuler ( index ) ;
var topTick = yScale . getPixelForValue ( null , index , datasetIndex , me . chart . isCombo ) ;
topTick -= me . chart . isCombo ? ( ruler . tickHeight / 2 ) : 0 ;
if ( yScale . options . stacked ) {
return topTick + ( ruler . categoryHeight / 2 ) + ruler . categorySpacing ;
}
return topTick +
( ruler . barHeight / 2 ) +
ruler . categorySpacing +
( ruler . barHeight * barIndex ) +
( ruler . barSpacing / 2 ) +
( ruler . barSpacing * barIndex ) ;
}
} ) ;
} ;
} , { } ] , 16 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . bubble = {
hover : {
mode : "single"
} ,
scales : {
xAxes : [ {
type : "linear" , // bubble should probably use a linear scale by default
position : "bottom" ,
id : "x-axis-0" // need an ID so datasets can reference the scale
} ] ,
yAxes : [ {
type : "linear" ,
position : "left" ,
id : "y-axis-0"
} ]
} ,
tooltips : {
callbacks : {
title : function ( ) {
// Title doesn't make sense for scatter since we format the data as a point
return '' ;
} ,
label : function ( tooltipItem , data ) {
var datasetLabel = data . datasets [ tooltipItem . datasetIndex ] . label || '' ;
var dataPoint = data . datasets [ tooltipItem . datasetIndex ] . data [ tooltipItem . index ] ;
return datasetLabel + ': (' + dataPoint . x + ', ' + dataPoint . y + ', ' + dataPoint . r + ')' ;
}
}
}
} ;
Chart . controllers . bubble = Chart . DatasetController . extend ( {
dataElementType : Chart . elements . Point ,
update : function ( reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var points = meta . data ;
// Update Points
helpers . each ( points , function ( point , index ) {
me . updateElement ( point , index , reset ) ;
} ) ;
} ,
updateElement : function ( point , index , reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var custom = point . custom || { } ;
var dataset = me . getDataset ( ) ;
var data = dataset . data [ index ] ;
var pointElementOptions = me . chart . options . elements . point ;
var dsIndex = me . index ;
helpers . extend ( point , {
// Utility
_xScale : xScale ,
_yScale : yScale ,
_datasetIndex : dsIndex ,
_index : index ,
// Desired view properties
_model : {
x : reset ? xScale . getPixelForDecimal ( 0.5 ) : xScale . getPixelForValue ( typeof data === 'object' ? data : NaN , index , dsIndex , me . chart . isCombo ) ,
y : reset ? yScale . getBasePixel ( ) : yScale . getPixelForValue ( data , index , dsIndex ) ,
// Appearance
radius : reset ? 0 : custom . radius ? custom . radius : me . getRadius ( data ) ,
// Tooltip
hitRadius : custom . hitRadius ? custom . hitRadius : helpers . getValueAtIndexOrDefault ( dataset . hitRadius , index , pointElementOptions . hitRadius )
}
} ) ;
// Trick to reset the styles of the point
Chart . DatasetController . prototype . removeHoverStyle . call ( me , point , pointElementOptions ) ;
var model = point . _model ;
model . skip = custom . skip ? custom . skip : ( isNaN ( model . x ) || isNaN ( model . y ) ) ;
point . pivot ( ) ;
} ,
getRadius : function ( value ) {
return value . r || this . chart . options . elements . point . radius ;
} ,
setHoverStyle : function ( point ) {
var me = this ;
Chart . DatasetController . prototype . setHoverStyle . call ( me , point ) ;
// Radius
var dataset = me . chart . data . datasets [ point . _datasetIndex ] ;
var index = point . _index ;
var custom = point . custom || { } ;
var model = point . _model ;
model . radius = custom . hoverRadius ? custom . hoverRadius : ( helpers . getValueAtIndexOrDefault ( dataset . hoverRadius , index , me . chart . options . elements . point . hoverRadius ) ) + me . getRadius ( dataset . data [ index ] ) ;
} ,
removeHoverStyle : function ( point ) {
var me = this ;
Chart . DatasetController . prototype . removeHoverStyle . call ( me , point , me . chart . options . elements . point ) ;
var dataVal = me . chart . data . datasets [ point . _datasetIndex ] . data [ point . _index ] ;
var custom = point . custom || { } ;
var model = point . _model ;
model . radius = custom . radius ? custom . radius : me . getRadius ( dataVal ) ;
}
} ) ;
} ;
} , { } ] , 17 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ,
defaults = Chart . defaults ;
defaults . doughnut = {
animation : {
//Boolean - Whether we animate the rotation of the Doughnut
animateRotate : true ,
//Boolean - Whether we animate scaling the Doughnut from the centre
animateScale : false
} ,
aspectRatio : 1 ,
hover : {
mode : 'single'
} ,
legendCallback : function ( chart ) {
var text = [ ] ;
text . push ( '<ul class="' + chart . id + '-legend">' ) ;
var data = chart . data ;
var datasets = data . datasets ;
var labels = data . labels ;
if ( datasets . length ) {
for ( var i = 0 ; i < datasets [ 0 ] . data . length ; ++ i ) {
text . push ( '<li><span style="background-color:' + datasets [ 0 ] . backgroundColor [ i ] + '"></span>' ) ;
if ( labels [ i ] ) {
text . push ( labels [ i ] ) ;
}
text . push ( '</li>' ) ;
}
}
text . push ( '</ul>' ) ;
return text . join ( "" ) ;
} ,
legend : {
labels : {
generateLabels : function ( chart ) {
var data = chart . data ;
if ( data . labels . length && data . datasets . length ) {
return data . labels . map ( function ( label , i ) {
var meta = chart . getDatasetMeta ( 0 ) ;
var ds = data . datasets [ 0 ] ;
var arc = meta . data [ i ] ;
var custom = arc && arc . custom || { } ;
var getValueAtIndexOrDefault = helpers . getValueAtIndexOrDefault ;
var arcOpts = chart . options . elements . arc ;
var fill = custom . backgroundColor ? custom . backgroundColor : getValueAtIndexOrDefault ( ds . backgroundColor , i , arcOpts . backgroundColor ) ;
var stroke = custom . borderColor ? custom . borderColor : getValueAtIndexOrDefault ( ds . borderColor , i , arcOpts . borderColor ) ;
var bw = custom . borderWidth ? custom . borderWidth : getValueAtIndexOrDefault ( ds . borderWidth , i , arcOpts . borderWidth ) ;
return {
text : label ,
fillStyle : fill ,
strokeStyle : stroke ,
lineWidth : bw ,
hidden : isNaN ( ds . data [ i ] ) || meta . data [ i ] . hidden ,
// Extra data used for toggling the correct item
index : i
} ;
} ) ;
} else {
return [ ] ;
}
}
} ,
onClick : function ( e , legendItem ) {
var index = legendItem . index ;
var chart = this . chart ;
var i , ilen , meta ;
for ( i = 0 , ilen = ( chart . data . datasets || [ ] ) . length ; i < ilen ; ++ i ) {
meta = chart . getDatasetMeta ( i ) ;
meta . data [ index ] . hidden = ! meta . data [ index ] . hidden ;
}
chart . update ( ) ;
}
} ,
//The percentage of the chart that we cut out of the middle.
cutoutPercentage : 50 ,
//The rotation of the chart, where the first data arc begins.
rotation : Math . PI * - 0.5 ,
//The total circumference of the chart.
circumference : Math . PI * 2.0 ,
// Need to override these to give a nice default
tooltips : {
callbacks : {
title : function ( ) {
return '' ;
} ,
label : function ( tooltipItem , data ) {
return data . labels [ tooltipItem . index ] + ': ' + data . datasets [ tooltipItem . datasetIndex ] . data [ tooltipItem . index ] ;
}
}
}
} ;
defaults . pie = helpers . clone ( defaults . doughnut ) ;
helpers . extend ( defaults . pie , {
cutoutPercentage : 0
} ) ;
Chart . controllers . doughnut = Chart . controllers . pie = Chart . DatasetController . extend ( {
dataElementType : Chart . elements . Arc ,
linkScales : helpers . noop ,
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
getRingIndex : function ( datasetIndex ) {
var ringIndex = 0 ;
for ( var j = 0 ; j < datasetIndex ; ++ j ) {
if ( this . chart . isDatasetVisible ( j ) ) {
++ ringIndex ;
}
}
return ringIndex ;
} ,
update : function ( reset ) {
var me = this ;
var chart = me . chart ,
chartArea = chart . chartArea ,
opts = chart . options ,
arcOpts = opts . elements . arc ,
availableWidth = chartArea . right - chartArea . left - arcOpts . borderWidth ,
availableHeight = chartArea . bottom - chartArea . top - arcOpts . borderWidth ,
minSize = Math . min ( availableWidth , availableHeight ) ,
offset = {
x : 0 ,
y : 0
} ,
meta = me . getMeta ( ) ,
cutoutPercentage = opts . cutoutPercentage ,
circumference = opts . circumference ;
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
if ( circumference < Math . PI * 2.0 ) {
var startAngle = opts . rotation % ( Math . PI * 2.0 ) ;
startAngle += Math . PI * 2.0 * ( startAngle >= Math . PI ? - 1 : startAngle < - Math . PI ? 1 : 0 ) ;
var endAngle = startAngle + circumference ;
var start = { x : Math . cos ( startAngle ) , y : Math . sin ( startAngle ) } ;
var end = { x : Math . cos ( endAngle ) , y : Math . sin ( endAngle ) } ;
var contains0 = ( startAngle <= 0 && 0 <= endAngle ) || ( startAngle <= Math . PI * 2.0 && Math . PI * 2.0 <= endAngle ) ;
var contains90 = ( startAngle <= Math . PI * 0.5 && Math . PI * 0.5 <= endAngle ) || ( startAngle <= Math . PI * 2.5 && Math . PI * 2.5 <= endAngle ) ;
var contains180 = ( startAngle <= - Math . PI && - Math . PI <= endAngle ) || ( startAngle <= Math . PI && Math . PI <= endAngle ) ;
var contains270 = ( startAngle <= - Math . PI * 0.5 && - Math . PI * 0.5 <= endAngle ) || ( startAngle <= Math . PI * 1.5 && Math . PI * 1.5 <= endAngle ) ;
var cutout = cutoutPercentage / 100.0 ;
var min = { x : contains180 ? - 1 : Math . min ( start . x * ( start . x < 0 ? 1 : cutout ) , end . x * ( end . x < 0 ? 1 : cutout ) ) , y : contains270 ? - 1 : Math . min ( start . y * ( start . y < 0 ? 1 : cutout ) , end . y * ( end . y < 0 ? 1 : cutout ) ) } ;
var max = { x : contains0 ? 1 : Math . max ( start . x * ( start . x > 0 ? 1 : cutout ) , end . x * ( end . x > 0 ? 1 : cutout ) ) , y : contains90 ? 1 : Math . max ( start . y * ( start . y > 0 ? 1 : cutout ) , end . y * ( end . y > 0 ? 1 : cutout ) ) } ;
var size = { width : ( max . x - min . x ) * 0.5 , height : ( max . y - min . y ) * 0.5 } ;
minSize = Math . min ( availableWidth / size . width , availableHeight / size . height ) ;
offset = { x : ( max . x + min . x ) * - 0.5 , y : ( max . y + min . y ) * - 0.5 } ;
}
chart . borderWidth = me . getMaxBorderWidth ( meta . data ) ;
chart . outerRadius = Math . max ( ( minSize - chart . borderWidth ) / 2 , 0 ) ;
chart . innerRadius = Math . max ( cutoutPercentage ? ( chart . outerRadius / 100 ) * ( cutoutPercentage ) : 1 , 0 ) ;
chart . radiusLength = ( chart . outerRadius - chart . innerRadius ) / chart . getVisibleDatasetCount ( ) ;
chart . offsetX = offset . x * chart . outerRadius ;
chart . offsetY = offset . y * chart . outerRadius ;
meta . total = me . calculateTotal ( ) ;
me . outerRadius = chart . outerRadius - ( chart . radiusLength * me . getRingIndex ( me . index ) ) ;
me . innerRadius = me . outerRadius - chart . radiusLength ;
helpers . each ( meta . data , function ( arc , index ) {
me . updateElement ( arc , index , reset ) ;
} ) ;
} ,
updateElement : function ( arc , index , reset ) {
var me = this ;
var chart = me . chart ,
chartArea = chart . chartArea ,
opts = chart . options ,
animationOpts = opts . animation ,
centerX = ( chartArea . left + chartArea . right ) / 2 ,
centerY = ( chartArea . top + chartArea . bottom ) / 2 ,
startAngle = opts . rotation , // non reset case handled later
endAngle = opts . rotation , // non reset case handled later
dataset = me . getDataset ( ) ,
circumference = reset && animationOpts . animateRotate ? 0 : arc . hidden ? 0 : me . calculateCircumference ( dataset . data [ index ] ) * ( opts . circumference / ( 2.0 * Math . PI ) ) ,
innerRadius = reset && animationOpts . animateScale ? 0 : me . innerRadius ,
outerRadius = reset && animationOpts . animateScale ? 0 : me . outerRadius ,
valueAtIndexOrDefault = helpers . getValueAtIndexOrDefault ;
helpers . extend ( arc , {
// Utility
_datasetIndex : me . index ,
_index : index ,
// Desired view properties
_model : {
x : centerX + chart . offsetX ,
y : centerY + chart . offsetY ,
startAngle : startAngle ,
endAngle : endAngle ,
circumference : circumference ,
outerRadius : outerRadius ,
innerRadius : innerRadius ,
label : valueAtIndexOrDefault ( dataset . label , index , chart . data . labels [ index ] )
}
} ) ;
var model = arc . _model ;
// Resets the visual styles
this . removeHoverStyle ( arc ) ;
// Set correct angles if not resetting
if ( ! reset || ! animationOpts . animateRotate ) {
if ( index === 0 ) {
model . startAngle = opts . rotation ;
} else {
model . startAngle = me . getMeta ( ) . data [ index - 1 ] . _model . endAngle ;
}
model . endAngle = model . startAngle + model . circumference ;
}
arc . pivot ( ) ;
} ,
removeHoverStyle : function ( arc ) {
Chart . DatasetController . prototype . removeHoverStyle . call ( this , arc , this . chart . options . elements . arc ) ;
} ,
calculateTotal : function ( ) {
var dataset = this . getDataset ( ) ;
var meta = this . getMeta ( ) ;
var total = 0 ;
var value ;
helpers . each ( meta . data , function ( element , index ) {
value = dataset . data [ index ] ;
if ( ! isNaN ( value ) && ! element . hidden ) {
total += Math . abs ( value ) ;
}
} ) ;
/ * i f ( t o t a l = = = 0 ) {
total = NaN ;
} * /
return total ;
} ,
calculateCircumference : function ( value ) {
var total = this . getMeta ( ) . total ;
if ( total > 0 && ! isNaN ( value ) ) {
return ( Math . PI * 2.0 ) * ( value / total ) ;
} else {
return 0 ;
}
} ,
//gets the max border or hover width to properly scale pie charts
getMaxBorderWidth : function ( elements ) {
var max = 0 ,
index = this . index ,
length = elements . length ,
borderWidth ,
hoverWidth ;
for ( var i = 0 ; i < length ; i ++ ) {
borderWidth = elements [ i ] . _model ? elements [ i ] . _model . borderWidth : 0 ;
hoverWidth = elements [ i ] . _chart ? elements [ i ] . _chart . config . data . datasets [ index ] . hoverBorderWidth : 0 ;
max = borderWidth > max ? borderWidth : max ;
max = hoverWidth > max ? hoverWidth : max ;
}
return max ;
}
} ) ;
} ;
} , { } ] , 18 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . line = {
showLines : true ,
spanGaps : false ,
hover : {
mode : "label"
} ,
scales : {
xAxes : [ {
type : "category" ,
id : 'x-axis-0'
} ] ,
yAxes : [ {
type : "linear" ,
id : 'y-axis-0'
} ]
}
} ;
function lineEnabled ( dataset , options ) {
return helpers . getValueOrDefault ( dataset . showLine , options . showLines ) ;
}
Chart . controllers . line = Chart . DatasetController . extend ( {
datasetElementType : Chart . elements . Line ,
dataElementType : Chart . elements . Point ,
addElementAndReset : function ( index ) {
var me = this ;
var options = me . chart . options ;
var meta = me . getMeta ( ) ;
Chart . DatasetController . prototype . addElementAndReset . call ( me , index ) ;
// Make sure bezier control points are updated
if ( lineEnabled ( me . getDataset ( ) , options ) && meta . dataset . _model . tension !== 0 ) {
me . updateBezierControlPoints ( ) ;
}
} ,
update : function ( reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var line = meta . dataset ;
var points = meta . data || [ ] ;
var options = me . chart . options ;
var lineElementOptions = options . elements . line ;
var scale = me . getScaleForId ( meta . yAxisID ) ;
var i , ilen , custom ;
var dataset = me . getDataset ( ) ;
var showLine = lineEnabled ( dataset , options ) ;
// Update Line
if ( showLine ) {
custom = line . custom || { } ;
// Compatibility: If the properties are defined with only the old name, use those values
if ( ( dataset . tension !== undefined ) && ( dataset . lineTension === undefined ) ) {
dataset . lineTension = dataset . tension ;
}
// Utility
line . _scale = scale ;
line . _datasetIndex = me . index ;
// Data
line . _children = points ;
// Model
line . _model = {
// Appearance
// The default behavior of lines is to break at null values, according
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
// This option gives linse the ability to span gaps
spanGaps : dataset . spanGaps ? dataset . spanGaps : options . spanGaps ,
tension : custom . tension ? custom . tension : helpers . getValueOrDefault ( dataset . lineTension , lineElementOptions . tension ) ,
backgroundColor : custom . backgroundColor ? custom . backgroundColor : ( dataset . backgroundColor || lineElementOptions . backgroundColor ) ,
borderWidth : custom . borderWidth ? custom . borderWidth : ( dataset . borderWidth || lineElementOptions . borderWidth ) ,
borderColor : custom . borderColor ? custom . borderColor : ( dataset . borderColor || lineElementOptions . borderColor ) ,
borderCapStyle : custom . borderCapStyle ? custom . borderCapStyle : ( dataset . borderCapStyle || lineElementOptions . borderCapStyle ) ,
borderDash : custom . borderDash ? custom . borderDash : ( dataset . borderDash || lineElementOptions . borderDash ) ,
borderDashOffset : custom . borderDashOffset ? custom . borderDashOffset : ( dataset . borderDashOffset || lineElementOptions . borderDashOffset ) ,
borderJoinStyle : custom . borderJoinStyle ? custom . borderJoinStyle : ( dataset . borderJoinStyle || lineElementOptions . borderJoinStyle ) ,
fill : custom . fill ? custom . fill : ( dataset . fill !== undefined ? dataset . fill : lineElementOptions . fill ) ,
steppedLine : custom . steppedLine ? custom . steppedLine : helpers . getValueOrDefault ( dataset . steppedLine , lineElementOptions . stepped ) ,
cubicInterpolationMode : custom . cubicInterpolationMode ? custom . cubicInterpolationMode : helpers . getValueOrDefault ( dataset . cubicInterpolationMode , lineElementOptions . cubicInterpolationMode ) ,
// Scale
scaleTop : scale . top ,
scaleBottom : scale . bottom ,
scaleZero : scale . getBasePixel ( )
} ;
line . pivot ( ) ;
}
// Update Points
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
me . updateElement ( points [ i ] , i , reset ) ;
}
if ( showLine && line . _model . tension !== 0 ) {
me . updateBezierControlPoints ( ) ;
}
// Now pivot the point for animation
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
points [ i ] . pivot ( ) ;
}
} ,
getPointBackgroundColor : function ( point , index ) {
var backgroundColor = this . chart . options . elements . point . backgroundColor ;
var dataset = this . getDataset ( ) ;
var custom = point . custom || { } ;
if ( custom . backgroundColor ) {
backgroundColor = custom . backgroundColor ;
} else if ( dataset . pointBackgroundColor ) {
backgroundColor = helpers . getValueAtIndexOrDefault ( dataset . pointBackgroundColor , index , backgroundColor ) ;
} else if ( dataset . backgroundColor ) {
backgroundColor = dataset . backgroundColor ;
}
return backgroundColor ;
} ,
getPointBorderColor : function ( point , index ) {
var borderColor = this . chart . options . elements . point . borderColor ;
var dataset = this . getDataset ( ) ;
var custom = point . custom || { } ;
if ( custom . borderColor ) {
borderColor = custom . borderColor ;
} else if ( dataset . pointBorderColor ) {
borderColor = helpers . getValueAtIndexOrDefault ( dataset . pointBorderColor , index , borderColor ) ;
} else if ( dataset . borderColor ) {
borderColor = dataset . borderColor ;
}
return borderColor ;
} ,
getPointBorderWidth : function ( point , index ) {
var borderWidth = this . chart . options . elements . point . borderWidth ;
var dataset = this . getDataset ( ) ;
var custom = point . custom || { } ;
if ( custom . borderWidth ) {
borderWidth = custom . borderWidth ;
} else if ( dataset . pointBorderWidth ) {
borderWidth = helpers . getValueAtIndexOrDefault ( dataset . pointBorderWidth , index , borderWidth ) ;
} else if ( dataset . borderWidth ) {
borderWidth = dataset . borderWidth ;
}
return borderWidth ;
} ,
updateElement : function ( point , index , reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var custom = point . custom || { } ;
var dataset = me . getDataset ( ) ;
var datasetIndex = me . index ;
var value = dataset . data [ index ] ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var pointOptions = me . chart . options . elements . point ;
var x , y ;
var labels = me . chart . data . labels || [ ] ;
var includeOffset = ( labels . length === 1 || dataset . data . length === 1 ) || me . chart . isCombo ;
// Compatibility: If the properties are defined with only the old name, use those values
if ( ( dataset . radius !== undefined ) && ( dataset . pointRadius === undefined ) ) {
dataset . pointRadius = dataset . radius ;
}
if ( ( dataset . hitRadius !== undefined ) && ( dataset . pointHitRadius === undefined ) ) {
dataset . pointHitRadius = dataset . hitRadius ;
}
x = xScale . getPixelForValue ( typeof value === 'object' ? value : NaN , index , datasetIndex , includeOffset ) ;
y = reset ? yScale . getBasePixel ( ) : me . calculatePointY ( value , index , datasetIndex ) ;
// Utility
point . _xScale = xScale ;
point . _yScale = yScale ;
point . _datasetIndex = datasetIndex ;
point . _index = index ;
// Desired view properties
point . _model = {
x : x ,
y : y ,
skip : custom . skip || isNaN ( x ) || isNaN ( y ) ,
// Appearance
radius : custom . radius || helpers . getValueAtIndexOrDefault ( dataset . pointRadius , index , pointOptions . radius ) ,
pointStyle : custom . pointStyle || helpers . getValueAtIndexOrDefault ( dataset . pointStyle , index , pointOptions . pointStyle ) ,
backgroundColor : me . getPointBackgroundColor ( point , index ) ,
borderColor : me . getPointBorderColor ( point , index ) ,
borderWidth : me . getPointBorderWidth ( point , index ) ,
tension : meta . dataset . _model ? meta . dataset . _model . tension : 0 ,
steppedLine : meta . dataset . _model ? meta . dataset . _model . steppedLine : false ,
// Tooltip
hitRadius : custom . hitRadius || helpers . getValueAtIndexOrDefault ( dataset . pointHitRadius , index , pointOptions . hitRadius )
} ;
} ,
calculatePointY : function ( value , index , datasetIndex ) {
var me = this ;
var chart = me . chart ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var sumPos = 0 ;
var sumNeg = 0 ;
var i , ds , dsMeta ;
if ( yScale . options . stacked ) {
for ( i = 0 ; i < datasetIndex ; i ++ ) {
ds = chart . data . datasets [ i ] ;
dsMeta = chart . getDatasetMeta ( i ) ;
if ( dsMeta . type === 'line' && dsMeta . yAxisID === yScale . id && chart . isDatasetVisible ( i ) ) {
var stackedRightValue = Number ( yScale . getRightValue ( ds . data [ index ] ) ) ;
if ( stackedRightValue < 0 ) {
sumNeg += stackedRightValue || 0 ;
} else {
sumPos += stackedRightValue || 0 ;
}
}
}
var rightValue = Number ( yScale . getRightValue ( value ) ) ;
if ( rightValue < 0 ) {
return yScale . getPixelForValue ( sumNeg + rightValue ) ;
} else {
return yScale . getPixelForValue ( sumPos + rightValue ) ;
}
}
return yScale . getPixelForValue ( value ) ;
} ,
updateBezierControlPoints : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var area = me . chart . chartArea ;
// Only consider points that are drawn in case the spanGaps option is used
var points = ( meta . data || [ ] ) ;
if ( meta . dataset . _model . spanGaps ) points = points . filter ( function ( pt ) { return ! pt . _model . skip ; } ) ;
var i , ilen , point , model , controlPoints ;
function capControlPoint ( pt , min , max ) {
return Math . max ( Math . min ( pt , max ) , min ) ;
}
if ( meta . dataset . _model . cubicInterpolationMode == 'monotone' ) {
helpers . splineCurveMonotone ( points ) ;
}
else {
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
point = points [ i ] ;
model = point . _model ;
controlPoints = helpers . splineCurve (
helpers . previousItem ( points , i ) . _model ,
model ,
helpers . nextItem ( points , i ) . _model ,
meta . dataset . _model . tension
) ;
model . controlPointPreviousX = controlPoints . previous . x ;
model . controlPointPreviousY = controlPoints . previous . y ;
model . controlPointNextX = controlPoints . next . x ;
model . controlPointNextY = controlPoints . next . y ;
}
}
if ( me . chart . options . elements . line . capBezierPoints ) {
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
model = points [ i ] . _model ;
model . controlPointPreviousX = capControlPoint ( model . controlPointPreviousX , area . left , area . right ) ;
model . controlPointPreviousY = capControlPoint ( model . controlPointPreviousY , area . top , area . bottom ) ;
model . controlPointNextX = capControlPoint ( model . controlPointNextX , area . left , area . right ) ;
model . controlPointNextY = capControlPoint ( model . controlPointNextY , area . top , area . bottom ) ;
}
}
} ,
draw : function ( ease ) {
var me = this ;
var meta = me . getMeta ( ) ;
var points = meta . data || [ ] ;
var easingDecimal = ease || 1 ;
var i , ilen ;
// Transition Point Locations
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
points [ i ] . transition ( easingDecimal ) ;
}
// Transition and Draw the line
if ( lineEnabled ( me . getDataset ( ) , me . chart . options ) ) {
meta . dataset . transition ( easingDecimal ) . draw ( ) ;
}
// Draw the points
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
points [ i ] . draw ( ) ;
}
} ,
setHoverStyle : function ( point ) {
// Point
var dataset = this . chart . data . datasets [ point . _datasetIndex ] ;
var index = point . _index ;
var custom = point . custom || { } ;
var model = point . _model ;
model . radius = custom . hoverRadius || helpers . getValueAtIndexOrDefault ( dataset . pointHoverRadius , index , this . chart . options . elements . point . hoverRadius ) ;
model . backgroundColor = custom . hoverBackgroundColor || helpers . getValueAtIndexOrDefault ( dataset . pointHoverBackgroundColor , index , helpers . getHoverColor ( model . backgroundColor ) ) ;
model . borderColor = custom . hoverBorderColor || helpers . getValueAtIndexOrDefault ( dataset . pointHoverBorderColor , index , helpers . getHoverColor ( model . borderColor ) ) ;
model . borderWidth = custom . hoverBorderWidth || helpers . getValueAtIndexOrDefault ( dataset . pointHoverBorderWidth , index , model . borderWidth ) ;
} ,
removeHoverStyle : function ( point ) {
var me = this ;
var dataset = me . chart . data . datasets [ point . _datasetIndex ] ;
var index = point . _index ;
var custom = point . custom || { } ;
var model = point . _model ;
// Compatibility: If the properties are defined with only the old name, use those values
if ( ( dataset . radius !== undefined ) && ( dataset . pointRadius === undefined ) ) {
dataset . pointRadius = dataset . radius ;
}
model . radius = custom . radius || helpers . getValueAtIndexOrDefault ( dataset . pointRadius , index , me . chart . options . elements . point . radius ) ;
model . backgroundColor = me . getPointBackgroundColor ( point , index ) ;
model . borderColor = me . getPointBorderColor ( point , index ) ;
model . borderWidth = me . getPointBorderWidth ( point , index ) ;
}
} ) ;
} ;
} , { } ] , 19 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . polarArea = {
scale : {
type : "radialLinear" ,
lineArc : true , // so that lines are circular
ticks : {
beginAtZero : true
}
} ,
//Boolean - Whether to animate the rotation of the chart
animation : {
animateRotate : true ,
animateScale : true
} ,
startAngle : - 0.5 * Math . PI ,
aspectRatio : 1 ,
legendCallback : function ( chart ) {
var text = [ ] ;
text . push ( '<ul class="' + chart . id + '-legend">' ) ;
var data = chart . data ;
var datasets = data . datasets ;
var labels = data . labels ;
if ( datasets . length ) {
for ( var i = 0 ; i < datasets [ 0 ] . data . length ; ++ i ) {
text . push ( '<li><span style="background-color:' + datasets [ 0 ] . backgroundColor [ i ] + '">' ) ;
if ( labels [ i ] ) {
text . push ( labels [ i ] ) ;
}
text . push ( '</span></li>' ) ;
}
}
text . push ( '</ul>' ) ;
return text . join ( "" ) ;
} ,
legend : {
labels : {
generateLabels : function ( chart ) {
var data = chart . data ;
if ( data . labels . length && data . datasets . length ) {
return data . labels . map ( function ( label , i ) {
var meta = chart . getDatasetMeta ( 0 ) ;
var ds = data . datasets [ 0 ] ;
var arc = meta . data [ i ] ;
var custom = arc . custom || { } ;
var getValueAtIndexOrDefault = helpers . getValueAtIndexOrDefault ;
var arcOpts = chart . options . elements . arc ;
var fill = custom . backgroundColor ? custom . backgroundColor : getValueAtIndexOrDefault ( ds . backgroundColor , i , arcOpts . backgroundColor ) ;
var stroke = custom . borderColor ? custom . borderColor : getValueAtIndexOrDefault ( ds . borderColor , i , arcOpts . borderColor ) ;
var bw = custom . borderWidth ? custom . borderWidth : getValueAtIndexOrDefault ( ds . borderWidth , i , arcOpts . borderWidth ) ;
return {
text : label ,
fillStyle : fill ,
strokeStyle : stroke ,
lineWidth : bw ,
hidden : isNaN ( ds . data [ i ] ) || meta . data [ i ] . hidden ,
// Extra data used for toggling the correct item
index : i
} ;
} ) ;
} else {
return [ ] ;
}
}
} ,
onClick : function ( e , legendItem ) {
var index = legendItem . index ;
var chart = this . chart ;
var i , ilen , meta ;
for ( i = 0 , ilen = ( chart . data . datasets || [ ] ) . length ; i < ilen ; ++ i ) {
meta = chart . getDatasetMeta ( i ) ;
meta . data [ index ] . hidden = ! meta . data [ index ] . hidden ;
}
chart . update ( ) ;
}
} ,
// Need to override these to give a nice default
tooltips : {
callbacks : {
title : function ( ) {
return '' ;
} ,
label : function ( tooltipItem , data ) {
return data . labels [ tooltipItem . index ] + ': ' + tooltipItem . yLabel ;
}
}
}
} ;
Chart . controllers . polarArea = Chart . DatasetController . extend ( {
dataElementType : Chart . elements . Arc ,
linkScales : helpers . noop ,
update : function ( reset ) {
var me = this ;
var chart = me . chart ;
var chartArea = chart . chartArea ;
var meta = me . getMeta ( ) ;
var opts = chart . options ;
var arcOpts = opts . elements . arc ;
var minSize = Math . min ( chartArea . right - chartArea . left , chartArea . bottom - chartArea . top ) ;
chart . outerRadius = Math . max ( ( minSize - arcOpts . borderWidth / 2 ) / 2 , 0 ) ;
chart . innerRadius = Math . max ( opts . cutoutPercentage ? ( chart . outerRadius / 100 ) * ( opts . cutoutPercentage ) : 1 , 0 ) ;
chart . radiusLength = ( chart . outerRadius - chart . innerRadius ) / chart . getVisibleDatasetCount ( ) ;
me . outerRadius = chart . outerRadius - ( chart . radiusLength * me . index ) ;
me . innerRadius = me . outerRadius - chart . radiusLength ;
meta . count = me . countVisibleElements ( ) ;
helpers . each ( meta . data , function ( arc , index ) {
me . updateElement ( arc , index , reset ) ;
} ) ;
} ,
updateElement : function ( arc , index , reset ) {
var me = this ;
var chart = me . chart ;
var dataset = me . getDataset ( ) ;
var opts = chart . options ;
var animationOpts = opts . animation ;
var scale = chart . scale ;
var getValueAtIndexOrDefault = helpers . getValueAtIndexOrDefault ;
var labels = chart . data . labels ;
var circumference = me . calculateCircumference ( dataset . data [ index ] ) ;
var centerX = scale . xCenter ;
var centerY = scale . yCenter ;
// If there is NaN data before us, we need to calculate the starting angle correctly.
// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
var visibleCount = 0 ;
var meta = me . getMeta ( ) ;
for ( var i = 0 ; i < index ; ++ i ) {
if ( ! isNaN ( dataset . data [ i ] ) && ! meta . data [ i ] . hidden ) {
++ visibleCount ;
}
}
//var negHalfPI = -0.5 * Math.PI;
var datasetStartAngle = opts . startAngle ;
var distance = arc . hidden ? 0 : scale . getDistanceFromCenterForValue ( dataset . data [ index ] ) ;
var startAngle = datasetStartAngle + ( circumference * visibleCount ) ;
var endAngle = startAngle + ( arc . hidden ? 0 : circumference ) ;
var resetRadius = animationOpts . animateScale ? 0 : scale . getDistanceFromCenterForValue ( dataset . data [ index ] ) ;
helpers . extend ( arc , {
// Utility
_datasetIndex : me . index ,
_index : index ,
_scale : scale ,
// Desired view properties
_model : {
x : centerX ,
y : centerY ,
innerRadius : 0 ,
outerRadius : reset ? resetRadius : distance ,
startAngle : reset && animationOpts . animateRotate ? datasetStartAngle : startAngle ,
endAngle : reset && animationOpts . animateRotate ? datasetStartAngle : endAngle ,
label : getValueAtIndexOrDefault ( labels , index , labels [ index ] )
}
} ) ;
// Apply border and fill style
me . removeHoverStyle ( arc ) ;
arc . pivot ( ) ;
} ,
removeHoverStyle : function ( arc ) {
Chart . DatasetController . prototype . removeHoverStyle . call ( this , arc , this . chart . options . elements . arc ) ;
} ,
countVisibleElements : function ( ) {
var dataset = this . getDataset ( ) ;
var meta = this . getMeta ( ) ;
var count = 0 ;
helpers . each ( meta . data , function ( element , index ) {
if ( ! isNaN ( dataset . data [ index ] ) && ! element . hidden ) {
count ++ ;
}
} ) ;
return count ;
} ,
calculateCircumference : function ( value ) {
var count = this . getMeta ( ) . count ;
if ( count > 0 && ! isNaN ( value ) ) {
return ( 2 * Math . PI ) / count ;
} else {
return 0 ;
}
}
} ) ;
} ;
} , { } ] , 20 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . radar = {
scale : {
type : "radialLinear"
} ,
elements : {
line : {
tension : 0 // no bezier in radar
}
}
} ;
Chart . controllers . radar = Chart . DatasetController . extend ( {
datasetElementType : Chart . elements . Line ,
dataElementType : Chart . elements . Point ,
linkScales : helpers . noop ,
addElementAndReset : function ( index ) {
Chart . DatasetController . prototype . addElementAndReset . call ( this , index ) ;
// Make sure bezier control points are updated
this . updateBezierControlPoints ( ) ;
} ,
update : function ( reset ) {
var me = this ;
var meta = me . getMeta ( ) ;
var line = meta . dataset ;
var points = meta . data ;
var custom = line . custom || { } ;
var dataset = me . getDataset ( ) ;
var lineElementOptions = me . chart . options . elements . line ;
var scale = me . chart . scale ;
// Compatibility: If the properties are defined with only the old name, use those values
if ( ( dataset . tension !== undefined ) && ( dataset . lineTension === undefined ) ) {
dataset . lineTension = dataset . tension ;
}
helpers . extend ( meta . dataset , {
// Utility
_datasetIndex : me . index ,
// Data
_children : points ,
_loop : true ,
// Model
_model : {
// Appearance
tension : custom . tension ? custom . tension : helpers . getValueOrDefault ( dataset . lineTension , lineElementOptions . tension ) ,
backgroundColor : custom . backgroundColor ? custom . backgroundColor : ( dataset . backgroundColor || lineElementOptions . backgroundColor ) ,
borderWidth : custom . borderWidth ? custom . borderWidth : ( dataset . borderWidth || lineElementOptions . borderWidth ) ,
borderColor : custom . borderColor ? custom . borderColor : ( dataset . borderColor || lineElementOptions . borderColor ) ,
fill : custom . fill ? custom . fill : ( dataset . fill !== undefined ? dataset . fill : lineElementOptions . fill ) ,
borderCapStyle : custom . borderCapStyle ? custom . borderCapStyle : ( dataset . borderCapStyle || lineElementOptions . borderCapStyle ) ,
borderDash : custom . borderDash ? custom . borderDash : ( dataset . borderDash || lineElementOptions . borderDash ) ,
borderDashOffset : custom . borderDashOffset ? custom . borderDashOffset : ( dataset . borderDashOffset || lineElementOptions . borderDashOffset ) ,
borderJoinStyle : custom . borderJoinStyle ? custom . borderJoinStyle : ( dataset . borderJoinStyle || lineElementOptions . borderJoinStyle ) ,
// Scale
scaleTop : scale . top ,
scaleBottom : scale . bottom ,
scaleZero : scale . getBasePosition ( )
}
} ) ;
meta . dataset . pivot ( ) ;
// Update Points
helpers . each ( points , function ( point , index ) {
me . updateElement ( point , index , reset ) ;
} , me ) ;
// Update bezier control points
me . updateBezierControlPoints ( ) ;
} ,
updateElement : function ( point , index , reset ) {
var me = this ;
var custom = point . custom || { } ;
var dataset = me . getDataset ( ) ;
var scale = me . chart . scale ;
var pointElementOptions = me . chart . options . elements . point ;
var pointPosition = scale . getPointPositionForValue ( index , dataset . data [ index ] ) ;
helpers . extend ( point , {
// Utility
_datasetIndex : me . index ,
_index : index ,
_scale : scale ,
// Desired view properties
_model : {
x : reset ? scale . xCenter : pointPosition . x , // value not used in dataset scale, but we want a consistent API between scales
y : reset ? scale . yCenter : pointPosition . y ,
// Appearance
tension : custom . tension ? custom . tension : helpers . getValueOrDefault ( dataset . tension , me . chart . options . elements . line . tension ) ,
radius : custom . radius ? custom . radius : helpers . getValueAtIndexOrDefault ( dataset . pointRadius , index , pointElementOptions . radius ) ,
backgroundColor : custom . backgroundColor ? custom . backgroundColor : helpers . getValueAtIndexOrDefault ( dataset . pointBackgroundColor , index , pointElementOptions . backgroundColor ) ,
borderColor : custom . borderColor ? custom . borderColor : helpers . getValueAtIndexOrDefault ( dataset . pointBorderColor , index , pointElementOptions . borderColor ) ,
borderWidth : custom . borderWidth ? custom . borderWidth : helpers . getValueAtIndexOrDefault ( dataset . pointBorderWidth , index , pointElementOptions . borderWidth ) ,
pointStyle : custom . pointStyle ? custom . pointStyle : helpers . getValueAtIndexOrDefault ( dataset . pointStyle , index , pointElementOptions . pointStyle ) ,
// Tooltip
hitRadius : custom . hitRadius ? custom . hitRadius : helpers . getValueAtIndexOrDefault ( dataset . hitRadius , index , pointElementOptions . hitRadius )
}
} ) ;
point . _model . skip = custom . skip ? custom . skip : ( isNaN ( point . _model . x ) || isNaN ( point . _model . y ) ) ;
} ,
updateBezierControlPoints : function ( ) {
var chartArea = this . chart . chartArea ;
var meta = this . getMeta ( ) ;
helpers . each ( meta . data , function ( point , index ) {
var model = point . _model ;
var controlPoints = helpers . splineCurve (
helpers . previousItem ( meta . data , index , true ) . _model ,
model ,
helpers . nextItem ( meta . data , index , true ) . _model ,
model . tension
) ;
// Prevent the bezier going outside of the bounds of the graph
model . controlPointPreviousX = Math . max ( Math . min ( controlPoints . previous . x , chartArea . right ) , chartArea . left ) ;
model . controlPointPreviousY = Math . max ( Math . min ( controlPoints . previous . y , chartArea . bottom ) , chartArea . top ) ;
model . controlPointNextX = Math . max ( Math . min ( controlPoints . next . x , chartArea . right ) , chartArea . left ) ;
model . controlPointNextY = Math . max ( Math . min ( controlPoints . next . y , chartArea . bottom ) , chartArea . top ) ;
// Now pivot the point for animation
point . pivot ( ) ;
} ) ;
} ,
draw : function ( ease ) {
var meta = this . getMeta ( ) ;
var easingDecimal = ease || 1 ;
// Transition Point Locations
helpers . each ( meta . data , function ( point ) {
point . transition ( easingDecimal ) ;
} ) ;
// Transition and Draw the line
meta . dataset . transition ( easingDecimal ) . draw ( ) ;
// Draw the points
helpers . each ( meta . data , function ( point ) {
point . draw ( ) ;
} ) ;
} ,
setHoverStyle : function ( point ) {
// Point
var dataset = this . chart . data . datasets [ point . _datasetIndex ] ;
var custom = point . custom || { } ;
var index = point . _index ;
var model = point . _model ;
model . radius = custom . hoverRadius ? custom . hoverRadius : helpers . getValueAtIndexOrDefault ( dataset . pointHoverRadius , index , this . chart . options . elements . point . hoverRadius ) ;
model . backgroundColor = custom . hoverBackgroundColor ? custom . hoverBackgroundColor : helpers . getValueAtIndexOrDefault ( dataset . pointHoverBackgroundColor , index , helpers . getHoverColor ( model . backgroundColor ) ) ;
model . borderColor = custom . hoverBorderColor ? custom . hoverBorderColor : helpers . getValueAtIndexOrDefault ( dataset . pointHoverBorderColor , index , helpers . getHoverColor ( model . borderColor ) ) ;
model . borderWidth = custom . hoverBorderWidth ? custom . hoverBorderWidth : helpers . getValueAtIndexOrDefault ( dataset . pointHoverBorderWidth , index , model . borderWidth ) ;
} ,
removeHoverStyle : function ( point ) {
var dataset = this . chart . data . datasets [ point . _datasetIndex ] ;
var custom = point . custom || { } ;
var index = point . _index ;
var model = point . _model ;
var pointElementOptions = this . chart . options . elements . point ;
model . radius = custom . radius ? custom . radius : helpers . getValueAtIndexOrDefault ( dataset . radius , index , pointElementOptions . radius ) ;
model . backgroundColor = custom . backgroundColor ? custom . backgroundColor : helpers . getValueAtIndexOrDefault ( dataset . pointBackgroundColor , index , pointElementOptions . backgroundColor ) ;
model . borderColor = custom . borderColor ? custom . borderColor : helpers . getValueAtIndexOrDefault ( dataset . pointBorderColor , index , pointElementOptions . borderColor ) ;
model . borderWidth = custom . borderWidth ? custom . borderWidth : helpers . getValueAtIndexOrDefault ( dataset . pointBorderWidth , index , pointElementOptions . borderWidth ) ;
}
} ) ;
} ;
} , { } ] , 21 : [ function ( require , module , exports ) {
/*global window: false */
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . global . animation = {
duration : 1000 ,
easing : "easeOutQuart" ,
onProgress : helpers . noop ,
onComplete : helpers . noop
} ;
Chart . Animation = Chart . Element . extend ( {
currentStep : null , // the current animation step
numSteps : 60 , // default number of steps
easing : "" , // the easing to use for this animation
render : null , // render function used by the animation service
onAnimationProgress : null , // user specified callback to fire on each step of the animation
onAnimationComplete : null // user specified callback to fire when the animation finishes
} ) ;
Chart . animationService = {
frameDuration : 17 ,
animations : [ ] ,
dropFrames : 0 ,
request : null ,
addAnimation : function ( chartInstance , animationObject , duration , lazy ) {
var me = this ;
if ( ! lazy ) {
chartInstance . animating = true ;
}
for ( var index = 0 ; index < me . animations . length ; ++ index ) {
if ( me . animations [ index ] . chartInstance === chartInstance ) {
// replacing an in progress animation
me . animations [ index ] . animationObject = animationObject ;
return ;
}
}
me . animations . push ( {
chartInstance : chartInstance ,
animationObject : animationObject
} ) ;
// If there are no animations queued, manually kickstart a digest, for lack of a better word
if ( me . animations . length === 1 ) {
me . requestAnimationFrame ( ) ;
}
} ,
// Cancel the animation for a given chart instance
cancelAnimation : function ( chartInstance ) {
var index = helpers . findIndex ( this . animations , function ( animationWrapper ) {
return animationWrapper . chartInstance === chartInstance ;
} ) ;
if ( index !== - 1 ) {
this . animations . splice ( index , 1 ) ;
chartInstance . animating = false ;
}
} ,
requestAnimationFrame : function ( ) {
var me = this ;
if ( me . request === null ) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me . request = helpers . requestAnimFrame . call ( window , function ( ) {
me . request = null ;
me . startDigest ( ) ;
} ) ;
}
} ,
startDigest : function ( ) {
var me = this ;
var startTime = Date . now ( ) ;
var framesToDrop = 0 ;
if ( me . dropFrames > 1 ) {
framesToDrop = Math . floor ( me . dropFrames ) ;
me . dropFrames = me . dropFrames % 1 ;
}
var i = 0 ;
while ( i < me . animations . length ) {
if ( me . animations [ i ] . animationObject . currentStep === null ) {
me . animations [ i ] . animationObject . currentStep = 0 ;
}
me . animations [ i ] . animationObject . currentStep += 1 + framesToDrop ;
if ( me . animations [ i ] . animationObject . currentStep > me . animations [ i ] . animationObject . numSteps ) {
me . animations [ i ] . animationObject . currentStep = me . animations [ i ] . animationObject . numSteps ;
}
me . animations [ i ] . animationObject . render ( me . animations [ i ] . chartInstance , me . animations [ i ] . animationObject ) ;
if ( me . animations [ i ] . animationObject . onAnimationProgress && me . animations [ i ] . animationObject . onAnimationProgress . call ) {
me . animations [ i ] . animationObject . onAnimationProgress . call ( me . animations [ i ] . chartInstance , me . animations [ i ] ) ;
}
if ( me . animations [ i ] . animationObject . currentStep === me . animations [ i ] . animationObject . numSteps ) {
if ( me . animations [ i ] . animationObject . onAnimationComplete && me . animations [ i ] . animationObject . onAnimationComplete . call ) {
me . animations [ i ] . animationObject . onAnimationComplete . call ( me . animations [ i ] . chartInstance , me . animations [ i ] ) ;
}
// executed the last frame. Remove the animation.
me . animations [ i ] . chartInstance . animating = false ;
me . animations . splice ( i , 1 ) ;
} else {
++ i ;
}
}
var endTime = Date . now ( ) ;
var dropFrames = ( endTime - startTime ) / me . frameDuration ;
me . dropFrames += dropFrames ;
// Do we have more stuff to animate?
if ( me . animations . length > 0 ) {
me . requestAnimationFrame ( ) ;
}
}
} ;
} ;
} , { } ] , 22 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
// Global Chart canvas helpers object for drawing items to canvas
var helpers = Chart . canvasHelpers = { } ;
helpers . drawPoint = function ( ctx , pointStyle , radius , x , y ) {
var type , edgeLength , xOffset , yOffset , height , size ;
if ( typeof pointStyle === 'object' ) {
type = pointStyle . toString ( ) ;
if ( type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]' ) {
ctx . drawImage ( pointStyle , x - pointStyle . width / 2 , y - pointStyle . height / 2 ) ;
return ;
}
}
if ( isNaN ( radius ) || radius <= 0 ) {
return ;
}
switch ( pointStyle ) {
// Default includes circle
default :
ctx . beginPath ( ) ;
ctx . arc ( x , y , radius , 0 , Math . PI * 2 ) ;
ctx . closePath ( ) ;
ctx . fill ( ) ;
break ;
case 'triangle' :
ctx . beginPath ( ) ;
edgeLength = 3 * radius / Math . sqrt ( 3 ) ;
height = edgeLength * Math . sqrt ( 3 ) / 2 ;
ctx . moveTo ( x - edgeLength / 2 , y + height / 3 ) ;
ctx . lineTo ( x + edgeLength / 2 , y + height / 3 ) ;
ctx . lineTo ( x , y - 2 * height / 3 ) ;
ctx . closePath ( ) ;
ctx . fill ( ) ;
break ;
case 'rect' :
size = 1 / Math . SQRT2 * radius ;
ctx . beginPath ( ) ;
ctx . fillRect ( x - size , y - size , 2 * size , 2 * size ) ;
ctx . strokeRect ( x - size , y - size , 2 * size , 2 * size ) ;
break ;
case 'rectRot' :
size = 1 / Math . SQRT2 * radius ;
ctx . beginPath ( ) ;
ctx . moveTo ( x - size , y ) ;
ctx . lineTo ( x , y + size ) ;
ctx . lineTo ( x + size , y ) ;
ctx . lineTo ( x , y - size ) ;
ctx . closePath ( ) ;
ctx . fill ( ) ;
break ;
case 'cross' :
ctx . beginPath ( ) ;
ctx . moveTo ( x , y + radius ) ;
ctx . lineTo ( x , y - radius ) ;
ctx . moveTo ( x - radius , y ) ;
ctx . lineTo ( x + radius , y ) ;
ctx . closePath ( ) ;
break ;
case 'crossRot' :
ctx . beginPath ( ) ;
xOffset = Math . cos ( Math . PI / 4 ) * radius ;
yOffset = Math . sin ( Math . PI / 4 ) * radius ;
ctx . moveTo ( x - xOffset , y - yOffset ) ;
ctx . lineTo ( x + xOffset , y + yOffset ) ;
ctx . moveTo ( x - xOffset , y + yOffset ) ;
ctx . lineTo ( x + xOffset , y - yOffset ) ;
ctx . closePath ( ) ;
break ;
case 'star' :
ctx . beginPath ( ) ;
ctx . moveTo ( x , y + radius ) ;
ctx . lineTo ( x , y - radius ) ;
ctx . moveTo ( x - radius , y ) ;
ctx . lineTo ( x + radius , y ) ;
xOffset = Math . cos ( Math . PI / 4 ) * radius ;
yOffset = Math . sin ( Math . PI / 4 ) * radius ;
ctx . moveTo ( x - xOffset , y - yOffset ) ;
ctx . lineTo ( x + xOffset , y + yOffset ) ;
ctx . moveTo ( x - xOffset , y + yOffset ) ;
ctx . lineTo ( x + xOffset , y - yOffset ) ;
ctx . closePath ( ) ;
break ;
case 'line' :
ctx . beginPath ( ) ;
ctx . moveTo ( x - radius , y ) ;
ctx . lineTo ( x + radius , y ) ;
ctx . closePath ( ) ;
break ;
case 'dash' :
ctx . beginPath ( ) ;
ctx . moveTo ( x , y ) ;
ctx . lineTo ( x + radius , y ) ;
ctx . closePath ( ) ;
break ;
}
ctx . stroke ( ) ;
} ;
} ;
} , { } ] , 23 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
//Create a dictionary of chart types, to allow for extension of existing types
Chart . types = { } ;
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
//Destroy method on the chart will remove the instance of the chart from this reference.
Chart . instances = { } ;
// Controllers available for dataset visualization eg. bar, line, slice, etc.
Chart . controllers = { } ;
/ * *
* @ class Chart . Controller
* The main controller of a chart .
* /
Chart . Controller = function ( instance ) {
this . chart = instance ;
this . config = instance . config ;
this . options = this . config . options = helpers . configMerge ( Chart . defaults . global , Chart . defaults [ this . config . type ] , this . config . options || { } ) ;
this . id = helpers . uid ( ) ;
Object . defineProperty ( this , 'data' , {
get : function ( ) {
return this . config . data ;
}
} ) ;
//Add the chart instance to the global namespace
Chart . instances [ this . id ] = this ;
if ( this . options . responsive ) {
// Silent resize before chart draws
this . resize ( true ) ;
}
this . initialize ( ) ;
return this ;
} ;
helpers . extend ( Chart . Controller . prototype , /** @lends Chart.Controller */ {
initialize : function ( ) {
var me = this ;
// Before init plugin notification
Chart . plugins . notify ( 'beforeInit' , [ me ] ) ;
me . bindEvents ( ) ;
// Make sure controllers are built first so that each dataset is bound to an axis before the scales
// are built
me . ensureScalesHaveIDs ( ) ;
me . buildOrUpdateControllers ( ) ;
me . buildScales ( ) ;
me . updateLayout ( ) ;
me . resetElements ( ) ;
me . initToolTip ( ) ;
me . update ( ) ;
// After init plugin notification
Chart . plugins . notify ( 'afterInit' , [ me ] ) ;
return me ;
} ,
clear : function ( ) {
helpers . clear ( this . chart ) ;
return this ;
} ,
stop : function ( ) {
// Stops any current animation loop occuring
Chart . animationService . cancelAnimation ( this ) ;
return this ;
} ,
resize : function resize ( silent ) {
var me = this ;
var chart = me . chart ;
var canvas = chart . canvas ;
var newWidth = helpers . getMaximumWidth ( canvas ) ;
var aspectRatio = chart . aspectRatio ;
var newHeight = ( me . options . maintainAspectRatio && isNaN ( aspectRatio ) === false && isFinite ( aspectRatio ) && aspectRatio !== 0 ) ? newWidth / aspectRatio : helpers . getMaximumHeight ( canvas ) ;
var sizeChanged = chart . width !== newWidth || chart . height !== newHeight ;
if ( ! sizeChanged ) {
return me ;
}
canvas . width = chart . width = newWidth ;
canvas . height = chart . height = newHeight ;
helpers . retinaScale ( chart ) ;
// Notify any plugins about the resize
var newSize = { width : newWidth , height : newHeight } ;
Chart . plugins . notify ( 'resize' , [ me , newSize ] ) ;
// Notify of resize
if ( me . options . onResize ) {
me . options . onResize ( me , newSize ) ;
}
if ( ! silent ) {
me . stop ( ) ;
me . update ( me . options . responsiveAnimationDuration ) ;
}
return me ;
} ,
ensureScalesHaveIDs : function ( ) {
var options = this . options ;
var scalesOptions = options . scales || { } ;
var scaleOptions = options . scale ;
helpers . each ( scalesOptions . xAxes , function ( xAxisOptions , index ) {
xAxisOptions . id = xAxisOptions . id || ( 'x-axis-' + index ) ;
} ) ;
helpers . each ( scalesOptions . yAxes , function ( yAxisOptions , index ) {
yAxisOptions . id = yAxisOptions . id || ( 'y-axis-' + index ) ;
} ) ;
if ( scaleOptions ) {
scaleOptions . id = scaleOptions . id || 'scale' ;
}
} ,
/ * *
* Builds a map of scale ID to scale object for future lookup .
* /
buildScales : function ( ) {
var me = this ;
var options = me . options ;
var scales = me . scales = { } ;
var items = [ ] ;
if ( options . scales ) {
items = items . concat (
( options . scales . xAxes || [ ] ) . map ( function ( xAxisOptions ) {
return { options : xAxisOptions , dtype : 'category' } ; } ) ,
( options . scales . yAxes || [ ] ) . map ( function ( yAxisOptions ) {
return { options : yAxisOptions , dtype : 'linear' } ; } ) ) ;
}
if ( options . scale ) {
items . push ( { options : options . scale , dtype : 'radialLinear' , isDefault : true } ) ;
}
helpers . each ( items , function ( item ) {
var scaleOptions = item . options ;
var scaleType = helpers . getValueOrDefault ( scaleOptions . type , item . dtype ) ;
var scaleClass = Chart . scaleService . getScaleConstructor ( scaleType ) ;
if ( ! scaleClass ) {
return ;
}
var scale = new scaleClass ( {
id : scaleOptions . id ,
options : scaleOptions ,
ctx : me . chart . ctx ,
chart : me
} ) ;
scales [ scale . id ] = scale ;
// TODO(SB): I think we should be able to remove this custom case (options.scale)
// and consider it as a regular scale part of the "scales"" map only! This would
// make the logic easier and remove some useless? custom code.
if ( item . isDefault ) {
me . scale = scale ;
}
} ) ;
Chart . scaleService . addScalesToLayout ( this ) ;
} ,
updateLayout : function ( ) {
Chart . layoutService . update ( this , this . chart . width , this . chart . height ) ;
} ,
buildOrUpdateControllers : function ( ) {
var me = this ;
var types = [ ] ;
var newControllers = [ ] ;
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
var meta = me . getDatasetMeta ( datasetIndex ) ;
if ( ! meta . type ) {
meta . type = dataset . type || me . config . type ;
}
types . push ( meta . type ) ;
if ( meta . controller ) {
meta . controller . updateIndex ( datasetIndex ) ;
} else {
meta . controller = new Chart . controllers [ meta . type ] ( me , datasetIndex ) ;
newControllers . push ( meta . controller ) ;
}
} , me ) ;
if ( types . length > 1 ) {
for ( var i = 1 ; i < types . length ; i ++ ) {
if ( types [ i ] !== types [ i - 1 ] ) {
me . isCombo = true ;
break ;
}
}
}
return newControllers ;
} ,
resetElements : function ( ) {
var me = this ;
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
me . getDatasetMeta ( datasetIndex ) . controller . reset ( ) ;
} , me ) ;
} ,
update : function update ( animationDuration , lazy ) {
var me = this ;
Chart . plugins . notify ( 'beforeUpdate' , [ me ] ) ;
// In case the entire data object changed
me . tooltip . _data = me . data ;
// Make sure dataset controllers are updated and new controllers are reset
var newControllers = me . buildOrUpdateControllers ( ) ;
// Make sure all dataset controllers have correct meta data counts
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
me . getDatasetMeta ( datasetIndex ) . controller . buildOrUpdateElements ( ) ;
} , me ) ;
Chart . layoutService . update ( me , me . chart . width , me . chart . height ) ;
// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
Chart . plugins . notify ( 'afterScaleUpdate' , [ me ] ) ;
// Can only reset the new controllers after the scales have been updated
helpers . each ( newControllers , function ( controller ) {
controller . reset ( ) ;
} ) ;
me . updateDatasets ( ) ;
// Do this before render so that any plugins that need final scale updates can use it
Chart . plugins . notify ( 'afterUpdate' , [ me ] ) ;
me . render ( animationDuration , lazy ) ;
} ,
/ * *
* @ method beforeDatasetsUpdate
* @ description Called before all datasets are updated . If a plugin returns false ,
* the datasets update will be cancelled until another chart update is triggered .
* @ param { Object } instance the chart instance being updated .
* @ returns { Boolean } false to cancel the datasets update .
* @ memberof Chart . PluginBase
* @ since version 2.1 . 5
* @ instance
* /
/ * *
* @ method afterDatasetsUpdate
* @ description Called after all datasets have been updated . Note that this
* extension will not be called if the datasets update has been cancelled .
* @ param { Object } instance the chart instance being updated .
* @ memberof Chart . PluginBase
* @ since version 2.1 . 5
* @ instance
* /
/ * *
* Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
* extension , in which case no datasets will be updated and the afterDatasetsUpdate
* notification will be skipped .
* @ protected
* @ instance
* /
updateDatasets : function ( ) {
var me = this ;
var i , ilen ;
if ( Chart . plugins . notify ( 'beforeDatasetsUpdate' , [ me ] ) ) {
for ( i = 0 , ilen = me . data . datasets . length ; i < ilen ; ++ i ) {
me . getDatasetMeta ( i ) . controller . update ( ) ;
}
Chart . plugins . notify ( 'afterDatasetsUpdate' , [ me ] ) ;
}
} ,
render : function render ( duration , lazy ) {
var me = this ;
Chart . plugins . notify ( 'beforeRender' , [ me ] ) ;
var animationOptions = me . options . animation ;
if ( animationOptions && ( ( typeof duration !== 'undefined' && duration !== 0 ) || ( typeof duration === 'undefined' && animationOptions . duration !== 0 ) ) ) {
var animation = new Chart . Animation ( ) ;
animation . numSteps = ( duration || animationOptions . duration ) / 16.66 ; //60 fps
animation . easing = animationOptions . easing ;
// render function
animation . render = function ( chartInstance , animationObject ) {
var easingFunction = helpers . easingEffects [ animationObject . easing ] ;
var stepDecimal = animationObject . currentStep / animationObject . numSteps ;
var easeDecimal = easingFunction ( stepDecimal ) ;
chartInstance . draw ( easeDecimal , stepDecimal , animationObject . currentStep ) ;
} ;
// user events
animation . onAnimationProgress = animationOptions . onProgress ;
animation . onAnimationComplete = animationOptions . onComplete ;
Chart . animationService . addAnimation ( me , animation , duration , lazy ) ;
} else {
me . draw ( ) ;
if ( animationOptions && animationOptions . onComplete && animationOptions . onComplete . call ) {
animationOptions . onComplete . call ( me ) ;
}
}
return me ;
} ,
draw : function ( ease ) {
var me = this ;
var easingDecimal = ease || 1 ;
me . clear ( ) ;
Chart . plugins . notify ( 'beforeDraw' , [ me , easingDecimal ] ) ;
// Draw all the scales
helpers . each ( me . boxes , function ( box ) {
box . draw ( me . chartArea ) ;
} , me ) ;
if ( me . scale ) {
me . scale . draw ( ) ;
}
Chart . plugins . notify ( 'beforeDatasetsDraw' , [ me , easingDecimal ] ) ;
// Draw each dataset via its respective controller (reversed to support proper line stacking)
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
if ( me . isDatasetVisible ( datasetIndex ) ) {
me . getDatasetMeta ( datasetIndex ) . controller . draw ( ease ) ;
}
} , me , true ) ;
Chart . plugins . notify ( 'afterDatasetsDraw' , [ me , easingDecimal ] ) ;
// Finally draw the tooltip
me . tooltip . transition ( easingDecimal ) . draw ( ) ;
Chart . plugins . notify ( 'afterDraw' , [ me , easingDecimal ] ) ;
} ,
// Get the single element that was clicked on
// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
getElementAtEvent : function ( e ) {
var me = this ;
var eventPosition = helpers . getRelativePosition ( e , me . chart ) ;
var elementsArray = [ ] ;
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
if ( me . isDatasetVisible ( datasetIndex ) ) {
var meta = me . getDatasetMeta ( datasetIndex ) ;
helpers . each ( meta . data , function ( element ) {
if ( element . inRange ( eventPosition . x , eventPosition . y ) ) {
elementsArray . push ( element ) ;
return elementsArray ;
}
} ) ;
}
} ) ;
return elementsArray . slice ( 0 , 1 ) ;
} ,
getElementsAtEvent : function ( e ) {
var me = this ;
var eventPosition = helpers . getRelativePosition ( e , me . chart ) ;
var elementsArray = [ ] ;
var found = ( function ( ) {
if ( me . data . datasets ) {
for ( var i = 0 ; i < me . data . datasets . length ; i ++ ) {
var meta = me . getDatasetMeta ( i ) ;
if ( me . isDatasetVisible ( i ) ) {
for ( var j = 0 ; j < meta . data . length ; j ++ ) {
if ( meta . data [ j ] . inRange ( eventPosition . x , eventPosition . y ) ) {
return meta . data [ j ] ;
}
}
}
}
}
} ) . call ( me ) ;
if ( ! found ) {
return elementsArray ;
}
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
if ( me . isDatasetVisible ( datasetIndex ) ) {
var meta = me . getDatasetMeta ( datasetIndex ) ,
element = meta . data [ found . _index ] ;
if ( element && ! element . _view . skip ) {
elementsArray . push ( element ) ;
}
}
} , me ) ;
return elementsArray ;
} ,
getElementsAtXAxis : function ( e ) {
var me = this ;
var eventPosition = helpers . getRelativePosition ( e , me . chart ) ;
var elementsArray = [ ] ;
var found = ( function ( ) {
if ( me . data . datasets ) {
for ( var i = 0 ; i < me . data . datasets . length ; i ++ ) {
var meta = me . getDatasetMeta ( i ) ;
if ( me . isDatasetVisible ( i ) ) {
for ( var j = 0 ; j < meta . data . length ; j ++ ) {
if ( meta . data [ j ] . inLabelRange ( eventPosition . x , eventPosition . y ) ) {
return meta . data [ j ] ;
}
}
}
}
}
} ) . call ( me ) ;
if ( ! found ) {
return elementsArray ;
}
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
if ( me . isDatasetVisible ( datasetIndex ) ) {
var meta = me . getDatasetMeta ( datasetIndex ) ;
var index = helpers . findIndex ( meta . data , function ( it ) {
return found . _model . x === it . _model . x ;
} ) ;
if ( index !== - 1 && ! meta . data [ index ] . _view . skip ) {
elementsArray . push ( meta . data [ index ] ) ;
}
}
} , me ) ;
return elementsArray ;
} ,
getElementsAtEventForMode : function ( e , mode ) {
var me = this ;
switch ( mode ) {
case 'single' :
return me . getElementAtEvent ( e ) ;
case 'label' :
return me . getElementsAtEvent ( e ) ;
case 'dataset' :
return me . getDatasetAtEvent ( e ) ;
case 'x-axis' :
return me . getElementsAtXAxis ( e ) ;
default :
return e ;
}
} ,
getDatasetAtEvent : function ( e ) {
var elementsArray = this . getElementAtEvent ( e ) ;
if ( elementsArray . length > 0 ) {
elementsArray = this . getDatasetMeta ( elementsArray [ 0 ] . _datasetIndex ) . data ;
}
return elementsArray ;
} ,
getDatasetMeta : function ( datasetIndex ) {
var me = this ;
var dataset = me . data . datasets [ datasetIndex ] ;
if ( ! dataset . _meta ) {
dataset . _meta = { } ;
}
var meta = dataset . _meta [ me . id ] ;
if ( ! meta ) {
meta = dataset . _meta [ me . id ] = {
type : null ,
data : [ ] ,
dataset : null ,
controller : null ,
hidden : null , // See isDatasetVisible() comment
xAxisID : null ,
yAxisID : null
} ;
}
return meta ;
} ,
getVisibleDatasetCount : function ( ) {
var count = 0 ;
for ( var i = 0 , ilen = this . data . datasets . length ; i < ilen ; ++ i ) {
if ( this . isDatasetVisible ( i ) ) {
count ++ ;
}
}
return count ;
} ,
isDatasetVisible : function ( datasetIndex ) {
var meta = this . getDatasetMeta ( datasetIndex ) ;
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
return typeof meta . hidden === 'boolean' ? ! meta . hidden : ! this . data . datasets [ datasetIndex ] . hidden ;
} ,
generateLegend : function ( ) {
return this . options . legendCallback ( this ) ;
} ,
destroy : function ( ) {
var me = this ;
me . stop ( ) ;
me . clear ( ) ;
helpers . unbindEvents ( me , me . events ) ;
helpers . removeResizeListener ( me . chart . canvas . parentNode ) ;
// Reset canvas height/width attributes
var canvas = me . chart . canvas ;
canvas . width = me . chart . width ;
canvas . height = me . chart . height ;
// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
if ( me . chart . originalDevicePixelRatio !== undefined ) {
me . chart . ctx . scale ( 1 / me . chart . originalDevicePixelRatio , 1 / me . chart . originalDevicePixelRatio ) ;
}
// Reset to the old style since it may have been changed by the device pixel ratio changes
canvas . style . width = me . chart . originalCanvasStyleWidth ;
canvas . style . height = me . chart . originalCanvasStyleHeight ;
Chart . plugins . notify ( 'destroy' , [ me ] ) ;
delete Chart . instances [ me . id ] ;
} ,
toBase64Image : function ( ) {
return this . chart . canvas . toDataURL . apply ( this . chart . canvas , arguments ) ;
} ,
initToolTip : function ( ) {
var me = this ;
me . tooltip = new Chart . Tooltip ( {
_chart : me . chart ,
_chartInstance : me ,
_data : me . data ,
_options : me . options . tooltips
} , me ) ;
} ,
bindEvents : function ( ) {
var me = this ;
helpers . bindEvents ( me , me . options . events , function ( evt ) {
me . eventHandler ( evt ) ;
} ) ;
} ,
updateHoverStyle : function ( elements , mode , enabled ) {
var method = enabled ? 'setHoverStyle' : 'removeHoverStyle' ;
var element , i , ilen ;
switch ( mode ) {
case 'single' :
elements = [ elements [ 0 ] ] ;
break ;
case 'label' :
case 'dataset' :
case 'x-axis' :
// elements = elements;
break ;
default :
// unsupported mode
return ;
}
for ( i = 0 , ilen = elements . length ; i < ilen ; ++ i ) {
element = elements [ i ] ;
if ( element ) {
this . getDatasetMeta ( element . _datasetIndex ) . controller [ method ] ( element ) ;
}
}
} ,
eventHandler : function eventHandler ( e ) {
var me = this ;
var tooltip = me . tooltip ;
var options = me . options || { } ;
var hoverOptions = options . hover ;
var tooltipsOptions = options . tooltips ;
me . lastActive = me . lastActive || [ ] ;
me . lastTooltipActive = me . lastTooltipActive || [ ] ;
// Find Active Elements for hover and tooltips
if ( e . type === 'mouseout' ) {
me . active = [ ] ;
me . tooltipActive = [ ] ;
} else {
me . active = me . getElementsAtEventForMode ( e , hoverOptions . mode ) ;
me . tooltipActive = me . getElementsAtEventForMode ( e , tooltipsOptions . mode ) ;
}
// On Hover hook
if ( hoverOptions . onHover ) {
hoverOptions . onHover . call ( me , me . active ) ;
}
if ( e . type === 'mouseup' || e . type === 'click' ) {
if ( options . onClick ) {
options . onClick . call ( me , e , me . active ) ;
}
if ( me . legend && me . legend . handleEvent ) {
me . legend . handleEvent ( e ) ;
}
}
// Remove styling for last active (even if it may still be active)
if ( me . lastActive . length ) {
me . updateHoverStyle ( me . lastActive , hoverOptions . mode , false ) ;
}
// Built in hover styling
if ( me . active . length && hoverOptions . mode ) {
me . updateHoverStyle ( me . active , hoverOptions . mode , true ) ;
}
// Built in Tooltips
if ( tooltipsOptions . enabled || tooltipsOptions . custom ) {
tooltip . initialize ( ) ;
tooltip . _active = me . tooltipActive ;
tooltip . update ( true ) ;
}
// Hover animations
tooltip . pivot ( ) ;
if ( ! me . animating ) {
// If entering, leaving, or changing elements, animate the change via pivot
if ( ! helpers . arrayEquals ( me . active , me . lastActive ) ||
! helpers . arrayEquals ( me . tooltipActive , me . lastTooltipActive ) ) {
me . stop ( ) ;
if ( tooltipsOptions . enabled || tooltipsOptions . custom ) {
tooltip . update ( true ) ;
}
// We only need to render at this point. Updating will cause scales to be
// recomputed generating flicker & using more memory than necessary.
me . render ( hoverOptions . animationDuration , true ) ;
}
}
// Remember Last Actives
me . lastActive = me . active ;
me . lastTooltipActive = me . tooltipActive ;
return me ;
}
} ) ;
} ;
} , { } ] , 24 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var noop = helpers . noop ;
// Base class for all dataset controllers (line, bar, etc)
Chart . DatasetController = function ( chart , datasetIndex ) {
this . initialize . call ( this , chart , datasetIndex ) ;
} ;
helpers . extend ( Chart . DatasetController . prototype , {
/ * *
* Element type used to generate a meta dataset ( e . g . Chart . element . Line ) .
* @ type { Chart . core . element }
* /
datasetElementType : null ,
/ * *
* Element type used to generate a meta data ( e . g . Chart . element . Point ) .
* @ type { Chart . core . element }
* /
dataElementType : null ,
initialize : function ( chart , datasetIndex ) {
var me = this ;
me . chart = chart ;
me . index = datasetIndex ;
me . linkScales ( ) ;
me . addElements ( ) ;
} ,
updateIndex : function ( datasetIndex ) {
this . index = datasetIndex ;
} ,
linkScales : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var dataset = me . getDataset ( ) ;
if ( meta . xAxisID === null ) {
meta . xAxisID = dataset . xAxisID || me . chart . options . scales . xAxes [ 0 ] . id ;
}
if ( meta . yAxisID === null ) {
meta . yAxisID = dataset . yAxisID || me . chart . options . scales . yAxes [ 0 ] . id ;
}
} ,
getDataset : function ( ) {
return this . chart . data . datasets [ this . index ] ;
} ,
getMeta : function ( ) {
return this . chart . getDatasetMeta ( this . index ) ;
} ,
getScaleForId : function ( scaleID ) {
return this . chart . scales [ scaleID ] ;
} ,
reset : function ( ) {
this . update ( true ) ;
} ,
createMetaDataset : function ( ) {
var me = this ;
var type = me . datasetElementType ;
return type && new type ( {
_chart : me . chart . chart ,
_datasetIndex : me . index
} ) ;
} ,
createMetaData : function ( index ) {
var me = this ;
var type = me . dataElementType ;
return type && new type ( {
_chart : me . chart . chart ,
_datasetIndex : me . index ,
_index : index
} ) ;
} ,
addElements : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var data = me . getDataset ( ) . data || [ ] ;
var metaData = meta . data ;
var i , ilen ;
for ( i = 0 , ilen = data . length ; i < ilen ; ++ i ) {
metaData [ i ] = metaData [ i ] || me . createMetaData ( meta , i ) ;
}
meta . dataset = meta . dataset || me . createMetaDataset ( ) ;
} ,
addElementAndReset : function ( index ) {
var me = this ;
var element = me . createMetaData ( index ) ;
me . getMeta ( ) . data . splice ( index , 0 , element ) ;
me . updateElement ( element , index , true ) ;
} ,
buildOrUpdateElements : function ( ) {
// Handle the number of data points changing
var meta = this . getMeta ( ) ,
md = meta . data ,
numData = this . getDataset ( ) . data . length ,
numMetaData = md . length ;
// Make sure that we handle number of datapoints changing
if ( numData < numMetaData ) {
// Remove excess bars for data points that have been removed
md . splice ( numData , numMetaData - numData ) ;
} else if ( numData > numMetaData ) {
// Add new elements
for ( var index = numMetaData ; index < numData ; ++ index ) {
this . addElementAndReset ( index ) ;
}
}
} ,
update : noop ,
draw : function ( ease ) {
var easingDecimal = ease || 1 ;
helpers . each ( this . getMeta ( ) . data , function ( element ) {
element . transition ( easingDecimal ) . draw ( ) ;
} ) ;
} ,
removeHoverStyle : function ( element , elementOpts ) {
var dataset = this . chart . data . datasets [ element . _datasetIndex ] ,
index = element . _index ,
custom = element . custom || { } ,
valueOrDefault = helpers . getValueAtIndexOrDefault ,
model = element . _model ;
model . backgroundColor = custom . backgroundColor ? custom . backgroundColor : valueOrDefault ( dataset . backgroundColor , index , elementOpts . backgroundColor ) ;
model . borderColor = custom . borderColor ? custom . borderColor : valueOrDefault ( dataset . borderColor , index , elementOpts . borderColor ) ;
model . borderWidth = custom . borderWidth ? custom . borderWidth : valueOrDefault ( dataset . borderWidth , index , elementOpts . borderWidth ) ;
} ,
setHoverStyle : function ( element ) {
var dataset = this . chart . data . datasets [ element . _datasetIndex ] ,
index = element . _index ,
custom = element . custom || { } ,
valueOrDefault = helpers . getValueAtIndexOrDefault ,
getHoverColor = helpers . getHoverColor ,
model = element . _model ;
model . backgroundColor = custom . hoverBackgroundColor ? custom . hoverBackgroundColor : valueOrDefault ( dataset . hoverBackgroundColor , index , getHoverColor ( model . backgroundColor ) ) ;
model . borderColor = custom . hoverBorderColor ? custom . hoverBorderColor : valueOrDefault ( dataset . hoverBorderColor , index , getHoverColor ( model . borderColor ) ) ;
model . borderWidth = custom . hoverBorderWidth ? custom . hoverBorderWidth : valueOrDefault ( dataset . hoverBorderWidth , index , model . borderWidth ) ;
}
} ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
Chart . DatasetController . extend = helpers . inherits ;
} ;
} , { } ] , 25 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . elements = { } ;
Chart . Element = function ( configuration ) {
helpers . extend ( this , configuration ) ;
this . initialize . apply ( this , arguments ) ;
} ;
helpers . extend ( Chart . Element . prototype , {
initialize : function ( ) {
this . hidden = false ;
} ,
pivot : function ( ) {
var me = this ;
if ( ! me . _view ) {
me . _view = helpers . clone ( me . _model ) ;
}
me . _start = helpers . clone ( me . _view ) ;
return me ;
} ,
transition : function ( ease ) {
var me = this ;
if ( ! me . _view ) {
me . _view = helpers . clone ( me . _model ) ;
}
// No animation -> No Transition
if ( ease === 1 ) {
me . _view = me . _model ;
me . _start = null ;
return me ;
}
if ( ! me . _start ) {
me . pivot ( ) ;
}
helpers . each ( me . _model , function ( value , key ) {
if ( key [ 0 ] === '_' ) {
// Only non-underscored properties
}
// Init if doesn't exist
else if ( ! me . _view . hasOwnProperty ( key ) ) {
if ( typeof value === 'number' && ! isNaN ( me . _view [ key ] ) ) {
me . _view [ key ] = value * ease ;
} else {
me . _view [ key ] = value ;
}
}
// No unnecessary computations
else if ( value === me . _view [ key ] ) {
// It's the same! Woohoo!
}
// Color transitions if possible
else if ( typeof value === 'string' ) {
try {
var color = helpers . color ( me . _model [ key ] ) . mix ( helpers . color ( me . _start [ key ] ) , ease ) ;
me . _view [ key ] = color . rgbString ( ) ;
} catch ( err ) {
me . _view [ key ] = value ;
}
}
// Number transitions
else if ( typeof value === 'number' ) {
var startVal = me . _start [ key ] !== undefined && isNaN ( me . _start [ key ] ) === false ? me . _start [ key ] : 0 ;
me . _view [ key ] = ( ( me . _model [ key ] - startVal ) * ease ) + startVal ;
}
// Everything else
else {
me . _view [ key ] = value ;
}
} , me ) ;
return me ;
} ,
tooltipPosition : function ( ) {
return {
x : this . _model . x ,
y : this . _model . y
} ;
} ,
hasValue : function ( ) {
return helpers . isNumber ( this . _model . x ) && helpers . isNumber ( this . _model . y ) ;
}
} ) ;
Chart . Element . extend = helpers . inherits ;
} ;
} , { } ] , 26 : [ function ( require , module , exports ) {
/*global window: false */
/*global document: false */
"use strict" ;
var color = require ( 3 ) ;
module . exports = function ( Chart ) {
//Global Chart helpers object for utility methods and classes
var helpers = Chart . helpers = { } ;
//-- Basic js utility methods
helpers . each = function ( loopable , callback , self , reverse ) {
// Check to see if null or undefined firstly.
var i , len ;
if ( helpers . isArray ( loopable ) ) {
len = loopable . length ;
if ( reverse ) {
for ( i = len - 1 ; i >= 0 ; i -- ) {
callback . call ( self , loopable [ i ] , i ) ;
}
} else {
for ( i = 0 ; i < len ; i ++ ) {
callback . call ( self , loopable [ i ] , i ) ;
}
}
} else if ( typeof loopable === 'object' ) {
var keys = Object . keys ( loopable ) ;
len = keys . length ;
for ( i = 0 ; i < len ; i ++ ) {
callback . call ( self , loopable [ keys [ i ] ] , keys [ i ] ) ;
}
}
} ;
helpers . clone = function ( obj ) {
var objClone = { } ;
helpers . each ( obj , function ( value , key ) {
if ( helpers . isArray ( value ) ) {
objClone [ key ] = value . slice ( 0 ) ;
} else if ( typeof value === 'object' && value !== null ) {
objClone [ key ] = helpers . clone ( value ) ;
} else {
objClone [ key ] = value ;
}
} ) ;
return objClone ;
} ;
helpers . extend = function ( base ) {
var setFn = function ( value , key ) { base [ key ] = value ; } ;
for ( var i = 1 , ilen = arguments . length ; i < ilen ; i ++ ) {
helpers . each ( arguments [ i ] , setFn ) ;
}
return base ;
} ;
// Need a special merge function to chart configs since they are now grouped
helpers . configMerge = function ( _base ) {
var base = helpers . clone ( _base ) ;
helpers . each ( Array . prototype . slice . call ( arguments , 1 ) , function ( extension ) {
helpers . each ( extension , function ( value , key ) {
if ( key === 'scales' ) {
// Scale config merging is complex. Add out own function here for that
base [ key ] = helpers . scaleMerge ( base . hasOwnProperty ( key ) ? base [ key ] : { } , value ) ;
} else if ( key === 'scale' ) {
// Used in polar area & radar charts since there is only one scale
base [ key ] = helpers . configMerge ( base . hasOwnProperty ( key ) ? base [ key ] : { } , Chart . scaleService . getScaleDefaults ( value . type ) , value ) ;
} else if ( base . hasOwnProperty ( key ) && helpers . isArray ( base [ key ] ) && helpers . isArray ( value ) ) {
// In this case we have an array of objects replacing another array. Rather than doing a strict replace,
// merge. This allows easy scale option merging
var baseArray = base [ key ] ;
helpers . each ( value , function ( valueObj , index ) {
if ( index < baseArray . length ) {
if ( typeof baseArray [ index ] === 'object' && baseArray [ index ] !== null && typeof valueObj === 'object' && valueObj !== null ) {
// Two objects are coming together. Do a merge of them.
baseArray [ index ] = helpers . configMerge ( baseArray [ index ] , valueObj ) ;
} else {
// Just overwrite in this case since there is nothing to merge
baseArray [ index ] = valueObj ;
}
} else {
baseArray . push ( valueObj ) ; // nothing to merge
}
} ) ;
} else if ( base . hasOwnProperty ( key ) && typeof base [ key ] === "object" && base [ key ] !== null && typeof value === "object" ) {
// If we are overwriting an object with an object, do a merge of the properties.
base [ key ] = helpers . configMerge ( base [ key ] , value ) ;
} else {
// can just overwrite the value in this case
base [ key ] = value ;
}
} ) ;
} ) ;
return base ;
} ;
helpers . scaleMerge = function ( _base , extension ) {
var base = helpers . clone ( _base ) ;
helpers . each ( extension , function ( value , key ) {
if ( key === 'xAxes' || key === 'yAxes' ) {
// These properties are arrays of items
if ( base . hasOwnProperty ( key ) ) {
helpers . each ( value , function ( valueObj , index ) {
var axisType = helpers . getValueOrDefault ( valueObj . type , key === 'xAxes' ? 'category' : 'linear' ) ;
var axisDefaults = Chart . scaleService . getScaleDefaults ( axisType ) ;
if ( index >= base [ key ] . length || ! base [ key ] [ index ] . type ) {
base [ key ] . push ( helpers . configMerge ( axisDefaults , valueObj ) ) ;
} else if ( valueObj . type && valueObj . type !== base [ key ] [ index ] . type ) {
// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
base [ key ] [ index ] = helpers . configMerge ( base [ key ] [ index ] , axisDefaults , valueObj ) ;
} else {
// Type is the same
base [ key ] [ index ] = helpers . configMerge ( base [ key ] [ index ] , valueObj ) ;
}
} ) ;
} else {
base [ key ] = [ ] ;
helpers . each ( value , function ( valueObj ) {
var axisType = helpers . getValueOrDefault ( valueObj . type , key === 'xAxes' ? 'category' : 'linear' ) ;
base [ key ] . push ( helpers . configMerge ( Chart . scaleService . getScaleDefaults ( axisType ) , valueObj ) ) ;
} ) ;
}
} else if ( base . hasOwnProperty ( key ) && typeof base [ key ] === "object" && base [ key ] !== null && typeof value === "object" ) {
// If we are overwriting an object with an object, do a merge of the properties.
base [ key ] = helpers . configMerge ( base [ key ] , value ) ;
} else {
// can just overwrite the value in this case
base [ key ] = value ;
}
} ) ;
return base ;
} ;
helpers . getValueAtIndexOrDefault = function ( value , index , defaultValue ) {
if ( value === undefined || value === null ) {
return defaultValue ;
}
if ( helpers . isArray ( value ) ) {
return index < value . length ? value [ index ] : defaultValue ;
}
return value ;
} ;
helpers . getValueOrDefault = function ( value , defaultValue ) {
return value === undefined ? defaultValue : value ;
} ;
helpers . indexOf = Array . prototype . indexOf ?
function ( array , item ) { return array . indexOf ( item ) ; } :
function ( array , item ) {
for ( var i = 0 , ilen = array . length ; i < ilen ; ++ i ) {
if ( array [ i ] === item ) {
return i ;
}
}
return - 1 ;
} ;
helpers . where = function ( collection , filterCallback ) {
if ( helpers . isArray ( collection ) && Array . prototype . filter ) {
return collection . filter ( filterCallback ) ;
} else {
var filtered = [ ] ;
helpers . each ( collection , function ( item ) {
if ( filterCallback ( item ) ) {
filtered . push ( item ) ;
}
} ) ;
return filtered ;
}
} ;
helpers . findIndex = Array . prototype . findIndex ?
function ( array , callback , scope ) { return array . findIndex ( callback , scope ) ; } :
function ( array , callback , scope ) {
scope = scope === undefined ? array : scope ;
for ( var i = 0 , ilen = array . length ; i < ilen ; ++ i ) {
if ( callback . call ( scope , array [ i ] , i , array ) ) {
return i ;
}
}
return - 1 ;
} ;
helpers . findNextWhere = function ( arrayToSearch , filterCallback , startIndex ) {
// Default to start of the array
if ( startIndex === undefined || startIndex === null ) {
startIndex = - 1 ;
}
for ( var i = startIndex + 1 ; i < arrayToSearch . length ; i ++ ) {
var currentItem = arrayToSearch [ i ] ;
if ( filterCallback ( currentItem ) ) {
return currentItem ;
}
}
} ;
helpers . findPreviousWhere = function ( arrayToSearch , filterCallback , startIndex ) {
// Default to end of the array
if ( startIndex === undefined || startIndex === null ) {
startIndex = arrayToSearch . length ;
}
for ( var i = startIndex - 1 ; i >= 0 ; i -- ) {
var currentItem = arrayToSearch [ i ] ;
if ( filterCallback ( currentItem ) ) {
return currentItem ;
}
}
} ;
helpers . inherits = function ( extensions ) {
//Basic javascript inheritance based on the model created in Backbone.js
var parent = this ;
var ChartElement = ( extensions && extensions . hasOwnProperty ( "constructor" ) ) ? extensions . constructor : function ( ) {
return parent . apply ( this , arguments ) ;
} ;
var Surrogate = function ( ) {
this . constructor = ChartElement ;
} ;
Surrogate . prototype = parent . prototype ;
ChartElement . prototype = new Surrogate ( ) ;
ChartElement . extend = helpers . inherits ;
if ( extensions ) {
helpers . extend ( ChartElement . prototype , extensions ) ;
}
ChartElement . _ _super _ _ = parent . prototype ;
return ChartElement ;
} ;
helpers . noop = function ( ) { } ;
helpers . uid = ( function ( ) {
var id = 0 ;
return function ( ) {
return id ++ ;
} ;
} ) ( ) ;
//-- Math methods
helpers . isNumber = function ( n ) {
return ! isNaN ( parseFloat ( n ) ) && isFinite ( n ) ;
} ;
helpers . almostEquals = function ( x , y , epsilon ) {
return Math . abs ( x - y ) < epsilon ;
} ;
helpers . max = function ( array ) {
return array . reduce ( function ( max , value ) {
if ( ! isNaN ( value ) ) {
return Math . max ( max , value ) ;
} else {
return max ;
}
} , Number . NEGATIVE _INFINITY ) ;
} ;
helpers . min = function ( array ) {
return array . reduce ( function ( min , value ) {
if ( ! isNaN ( value ) ) {
return Math . min ( min , value ) ;
} else {
return min ;
}
} , Number . POSITIVE _INFINITY ) ;
} ;
helpers . sign = Math . sign ?
function ( x ) { return Math . sign ( x ) ; } :
function ( x ) {
x = + x ; // convert to a number
if ( x === 0 || isNaN ( x ) ) {
return x ;
}
return x > 0 ? 1 : - 1 ;
} ;
helpers . log10 = Math . log10 ?
function ( x ) { return Math . log10 ( x ) ; } :
function ( x ) {
return Math . log ( x ) / Math . LN10 ;
} ;
helpers . toRadians = function ( degrees ) {
return degrees * ( Math . PI / 180 ) ;
} ;
helpers . toDegrees = function ( radians ) {
return radians * ( 180 / Math . PI ) ;
} ;
// Gets the angle from vertical upright to the point about a centre.
helpers . getAngleFromPoint = function ( centrePoint , anglePoint ) {
var distanceFromXCenter = anglePoint . x - centrePoint . x ,
distanceFromYCenter = anglePoint . y - centrePoint . y ,
radialDistanceFromCenter = Math . sqrt ( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter ) ;
var angle = Math . atan2 ( distanceFromYCenter , distanceFromXCenter ) ;
if ( angle < ( - 0.5 * Math . PI ) ) {
angle += 2.0 * Math . PI ; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
}
return {
angle : angle ,
distance : radialDistanceFromCenter
} ;
} ;
helpers . aliasPixel = function ( pixelWidth ) {
return ( pixelWidth % 2 === 0 ) ? 0 : 0.5 ;
} ;
helpers . splineCurve = function ( firstPoint , middlePoint , afterPoint , t ) {
//Props to Rob Spencer at scaled innovation for his post on splining between points
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
// This function must also respect "skipped" points
var previous = firstPoint . skip ? middlePoint : firstPoint ,
current = middlePoint ,
next = afterPoint . skip ? middlePoint : afterPoint ;
var d01 = Math . sqrt ( Math . pow ( current . x - previous . x , 2 ) + Math . pow ( current . y - previous . y , 2 ) ) ;
var d12 = Math . sqrt ( Math . pow ( next . x - current . x , 2 ) + Math . pow ( next . y - current . y , 2 ) ) ;
var s01 = d01 / ( d01 + d12 ) ;
var s12 = d12 / ( d01 + d12 ) ;
// If all points are the same, s01 & s02 will be inf
s01 = isNaN ( s01 ) ? 0 : s01 ;
s12 = isNaN ( s12 ) ? 0 : s12 ;
var fa = t * s01 ; // scaling factor for triangle Ta
var fb = t * s12 ;
return {
previous : {
x : current . x - fa * ( next . x - previous . x ) ,
y : current . y - fa * ( next . y - previous . y )
} ,
next : {
x : current . x + fb * ( next . x - previous . x ) ,
y : current . y + fb * ( next . y - previous . y )
}
} ;
} ;
helpers . EPSILON = Number . EPSILON || 1e-14 ;
helpers . splineCurveMonotone = function ( points ) {
// This function calculates Bézier control points in a similar way than |splineCurve|,
// but preserves monotonicity of the provided data and ensures no local extremums are added
// between the dataset discrete points due to the interpolation.
// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
var pointsWithTangents = ( points || [ ] ) . map ( function ( point ) {
return {
model : point . _model ,
deltaK : 0 ,
mK : 0
} ;
} ) ;
// Calculate slopes (deltaK) and initialize tangents (mK)
var pointsLen = pointsWithTangents . length ;
var i , pointBefore , pointCurrent , pointAfter ;
for ( i = 0 ; i < pointsLen ; ++ i ) {
pointCurrent = pointsWithTangents [ i ] ;
if ( pointCurrent . model . skip ) continue ;
pointBefore = i > 0 ? pointsWithTangents [ i - 1 ] : null ;
pointAfter = i < pointsLen - 1 ? pointsWithTangents [ i + 1 ] : null ;
if ( pointAfter && ! pointAfter . model . skip ) {
pointCurrent . deltaK = ( pointAfter . model . y - pointCurrent . model . y ) / ( pointAfter . model . x - pointCurrent . model . x ) ;
}
if ( ! pointBefore || pointBefore . model . skip ) pointCurrent . mK = pointCurrent . deltaK ;
else if ( ! pointAfter || pointAfter . model . skip ) pointCurrent . mK = pointBefore . deltaK ;
else if ( this . sign ( pointBefore . deltaK ) != this . sign ( pointCurrent . deltaK ) ) pointCurrent . mK = 0 ;
else pointCurrent . mK = ( pointBefore . deltaK + pointCurrent . deltaK ) / 2 ;
}
// Adjust tangents to ensure monotonic properties
var alphaK , betaK , tauK , squaredMagnitude ;
for ( i = 0 ; i < pointsLen - 1 ; ++ i ) {
pointCurrent = pointsWithTangents [ i ] ;
pointAfter = pointsWithTangents [ i + 1 ] ;
if ( pointCurrent . model . skip || pointAfter . model . skip ) continue ;
if ( helpers . almostEquals ( pointCurrent . deltaK , 0 , this . EPSILON ) )
{
pointCurrent . mK = pointAfter . mK = 0 ;
continue ;
}
alphaK = pointCurrent . mK / pointCurrent . deltaK ;
betaK = pointAfter . mK / pointCurrent . deltaK ;
squaredMagnitude = Math . pow ( alphaK , 2 ) + Math . pow ( betaK , 2 ) ;
if ( squaredMagnitude <= 9 ) continue ;
tauK = 3 / Math . sqrt ( squaredMagnitude ) ;
pointCurrent . mK = alphaK * tauK * pointCurrent . deltaK ;
pointAfter . mK = betaK * tauK * pointCurrent . deltaK ;
}
// Compute control points
var deltaX ;
for ( i = 0 ; i < pointsLen ; ++ i ) {
pointCurrent = pointsWithTangents [ i ] ;
if ( pointCurrent . model . skip ) continue ;
pointBefore = i > 0 ? pointsWithTangents [ i - 1 ] : null ;
pointAfter = i < pointsLen - 1 ? pointsWithTangents [ i + 1 ] : null ;
if ( pointBefore && ! pointBefore . model . skip ) {
deltaX = ( pointCurrent . model . x - pointBefore . model . x ) / 3 ;
pointCurrent . model . controlPointPreviousX = pointCurrent . model . x - deltaX ;
pointCurrent . model . controlPointPreviousY = pointCurrent . model . y - deltaX * pointCurrent . mK ;
}
if ( pointAfter && ! pointAfter . model . skip ) {
deltaX = ( pointAfter . model . x - pointCurrent . model . x ) / 3 ;
pointCurrent . model . controlPointNextX = pointCurrent . model . x + deltaX ;
pointCurrent . model . controlPointNextY = pointCurrent . model . y + deltaX * pointCurrent . mK ;
}
}
} ;
helpers . nextItem = function ( collection , index , loop ) {
if ( loop ) {
return index >= collection . length - 1 ? collection [ 0 ] : collection [ index + 1 ] ;
}
return index >= collection . length - 1 ? collection [ collection . length - 1 ] : collection [ index + 1 ] ;
} ;
helpers . previousItem = function ( collection , index , loop ) {
if ( loop ) {
return index <= 0 ? collection [ collection . length - 1 ] : collection [ index - 1 ] ;
}
return index <= 0 ? collection [ 0 ] : collection [ index - 1 ] ;
} ;
// Implementation of the nice number algorithm used in determining where axis labels will go
helpers . niceNum = function ( range , round ) {
var exponent = Math . floor ( helpers . log10 ( range ) ) ;
var fraction = range / Math . pow ( 10 , exponent ) ;
var niceFraction ;
if ( round ) {
if ( fraction < 1.5 ) {
niceFraction = 1 ;
} else if ( fraction < 3 ) {
niceFraction = 2 ;
} else if ( fraction < 7 ) {
niceFraction = 5 ;
} else {
niceFraction = 10 ;
}
} else {
if ( fraction <= 1.0 ) {
niceFraction = 1 ;
} else if ( fraction <= 2 ) {
niceFraction = 2 ;
} else if ( fraction <= 5 ) {
niceFraction = 5 ;
} else {
niceFraction = 10 ;
}
}
return niceFraction * Math . pow ( 10 , exponent ) ;
} ;
2013-12-15 13:55:50 +01:00
//Easing functions adapted from Robert Penner's easing equations
//http://www.robertpenner.com/easing/
2016-09-11 16:30:23 +02:00
var easingEffects = helpers . easingEffects = {
linear : function ( t ) {
2013-12-15 13:55:50 +01:00
return t ;
} ,
2016-09-11 16:30:23 +02:00
easeInQuad : function ( t ) {
return t * t ;
} ,
easeOutQuad : function ( t ) {
return - 1 * t * ( t - 2 ) ;
} ,
easeInOutQuad : function ( t ) {
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * t * t ;
}
return - 1 / 2 * ( ( -- t ) * ( t - 2 ) - 1 ) ;
} ,
easeInCubic : function ( t ) {
return t * t * t ;
} ,
easeOutCubic : function ( t ) {
return 1 * ( ( t = t / 1 - 1 ) * t * t + 1 ) ;
} ,
easeInOutCubic : function ( t ) {
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * t * t * t ;
}
return 1 / 2 * ( ( t -= 2 ) * t * t + 2 ) ;
} ,
easeInQuart : function ( t ) {
return t * t * t * t ;
} ,
easeOutQuart : function ( t ) {
return - 1 * ( ( t = t / 1 - 1 ) * t * t * t - 1 ) ;
} ,
easeInOutQuart : function ( t ) {
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * t * t * t * t ;
}
return - 1 / 2 * ( ( t -= 2 ) * t * t * t - 2 ) ;
} ,
easeInQuint : function ( t ) {
return 1 * ( t /= 1 ) * t * t * t * t ;
} ,
easeOutQuint : function ( t ) {
return 1 * ( ( t = t / 1 - 1 ) * t * t * t * t + 1 ) ;
} ,
easeInOutQuint : function ( t ) {
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * t * t * t * t * t ;
}
return 1 / 2 * ( ( t -= 2 ) * t * t * t * t + 2 ) ;
} ,
easeInSine : function ( t ) {
return - 1 * Math . cos ( t / 1 * ( Math . PI / 2 ) ) + 1 ;
} ,
easeOutSine : function ( t ) {
return 1 * Math . sin ( t / 1 * ( Math . PI / 2 ) ) ;
} ,
easeInOutSine : function ( t ) {
return - 1 / 2 * ( Math . cos ( Math . PI * t / 1 ) - 1 ) ;
} ,
easeInExpo : function ( t ) {
return ( t === 0 ) ? 1 : 1 * Math . pow ( 2 , 10 * ( t / 1 - 1 ) ) ;
} ,
easeOutExpo : function ( t ) {
return ( t === 1 ) ? 1 : 1 * ( - Math . pow ( 2 , - 10 * t / 1 ) + 1 ) ;
} ,
easeInOutExpo : function ( t ) {
if ( t === 0 ) {
return 0 ;
}
if ( t === 1 ) {
return 1 ;
}
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * Math . pow ( 2 , 10 * ( t - 1 ) ) ;
}
return 1 / 2 * ( - Math . pow ( 2 , - 10 * -- t ) + 2 ) ;
} ,
easeInCirc : function ( t ) {
if ( t >= 1 ) {
return t ;
}
return - 1 * ( Math . sqrt ( 1 - ( t /= 1 ) * t ) - 1 ) ;
} ,
easeOutCirc : function ( t ) {
return 1 * Math . sqrt ( 1 - ( t = t / 1 - 1 ) * t ) ;
} ,
easeInOutCirc : function ( t ) {
if ( ( t /= 1 / 2 ) < 1 ) {
return - 1 / 2 * ( Math . sqrt ( 1 - t * t ) - 1 ) ;
}
return 1 / 2 * ( Math . sqrt ( 1 - ( t -= 2 ) * t ) + 1 ) ;
} ,
easeInElastic : function ( t ) {
var s = 1.70158 ;
var p = 0 ;
var a = 1 ;
if ( t === 0 ) {
return 0 ;
}
if ( ( t /= 1 ) === 1 ) {
return 1 ;
}
if ( ! p ) {
p = 1 * 0.3 ;
}
if ( a < Math . abs ( 1 ) ) {
a = 1 ;
s = p / 4 ;
} else {
s = p / ( 2 * Math . PI ) * Math . asin ( 1 / a ) ;
}
return - ( a * Math . pow ( 2 , 10 * ( t -= 1 ) ) * Math . sin ( ( t * 1 - s ) * ( 2 * Math . PI ) / p ) ) ;
} ,
easeOutElastic : function ( t ) {
var s = 1.70158 ;
var p = 0 ;
var a = 1 ;
if ( t === 0 ) {
return 0 ;
}
if ( ( t /= 1 ) === 1 ) {
return 1 ;
}
if ( ! p ) {
p = 1 * 0.3 ;
}
if ( a < Math . abs ( 1 ) ) {
a = 1 ;
s = p / 4 ;
} else {
s = p / ( 2 * Math . PI ) * Math . asin ( 1 / a ) ;
}
return a * Math . pow ( 2 , - 10 * t ) * Math . sin ( ( t * 1 - s ) * ( 2 * Math . PI ) / p ) + 1 ;
} ,
easeInOutElastic : function ( t ) {
var s = 1.70158 ;
var p = 0 ;
var a = 1 ;
if ( t === 0 ) {
return 0 ;
}
if ( ( t /= 1 / 2 ) === 2 ) {
return 1 ;
}
if ( ! p ) {
p = 1 * ( 0.3 * 1.5 ) ;
}
if ( a < Math . abs ( 1 ) ) {
a = 1 ;
s = p / 4 ;
} else {
s = p / ( 2 * Math . PI ) * Math . asin ( 1 / a ) ;
}
if ( t < 1 ) {
return - 0.5 * ( a * Math . pow ( 2 , 10 * ( t -= 1 ) ) * Math . sin ( ( t * 1 - s ) * ( 2 * Math . PI ) / p ) ) ;
}
return a * Math . pow ( 2 , - 10 * ( t -= 1 ) ) * Math . sin ( ( t * 1 - s ) * ( 2 * Math . PI ) / p ) * 0.5 + 1 ;
} ,
easeInBack : function ( t ) {
var s = 1.70158 ;
return 1 * ( t /= 1 ) * t * ( ( s + 1 ) * t - s ) ;
} ,
easeOutBack : function ( t ) {
var s = 1.70158 ;
return 1 * ( ( t = t / 1 - 1 ) * t * ( ( s + 1 ) * t + s ) + 1 ) ;
} ,
easeInOutBack : function ( t ) {
var s = 1.70158 ;
if ( ( t /= 1 / 2 ) < 1 ) {
return 1 / 2 * ( t * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t - s ) ) ;
}
return 1 / 2 * ( ( t -= 2 ) * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t + s ) + 2 ) ;
} ,
easeInBounce : function ( t ) {
return 1 - easingEffects . easeOutBounce ( 1 - t ) ;
} ,
easeOutBounce : function ( t ) {
if ( ( t /= 1 ) < ( 1 / 2.75 ) ) {
return 1 * ( 7.5625 * t * t ) ;
} else if ( t < ( 2 / 2.75 ) ) {
return 1 * ( 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75 ) ;
} else if ( t < ( 2.5 / 2.75 ) ) {
return 1 * ( 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375 ) ;
} else {
return 1 * ( 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375 ) ;
}
} ,
easeInOutBounce : function ( t ) {
if ( t < 1 / 2 ) {
return easingEffects . easeInBounce ( t * 2 ) * 0.5 ;
}
return easingEffects . easeOutBounce ( t * 2 - 1 ) * 0.5 + 1 * 0.5 ;
}
} ;
//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
helpers . requestAnimFrame = ( function ( ) {
return window . requestAnimationFrame ||
window . webkitRequestAnimationFrame ||
window . mozRequestAnimationFrame ||
window . oRequestAnimationFrame ||
window . msRequestAnimationFrame ||
function ( callback ) {
return window . setTimeout ( callback , 1000 / 60 ) ;
} ;
} ) ( ) ;
helpers . cancelAnimFrame = ( function ( ) {
return window . cancelAnimationFrame ||
window . webkitCancelAnimationFrame ||
window . mozCancelAnimationFrame ||
window . oCancelAnimationFrame ||
window . msCancelAnimationFrame ||
function ( callback ) {
return window . clearTimeout ( callback , 1000 / 60 ) ;
} ;
} ) ( ) ;
//-- DOM methods
helpers . getRelativePosition = function ( evt , chart ) {
var mouseX , mouseY ;
var e = evt . originalEvent || evt ,
canvas = evt . currentTarget || evt . srcElement ,
boundingRect = canvas . getBoundingClientRect ( ) ;
var touches = e . touches ;
if ( touches && touches . length > 0 ) {
mouseX = touches [ 0 ] . clientX ;
mouseY = touches [ 0 ] . clientY ;
} else {
mouseX = e . clientX ;
mouseY = e . clientY ;
}
// Scale mouse coordinates into canvas coordinates
// by following the pattern laid out by 'jerryj' in the comments of
// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
var paddingLeft = parseFloat ( helpers . getStyle ( canvas , 'padding-left' ) ) ;
var paddingTop = parseFloat ( helpers . getStyle ( canvas , 'padding-top' ) ) ;
var paddingRight = parseFloat ( helpers . getStyle ( canvas , 'padding-right' ) ) ;
var paddingBottom = parseFloat ( helpers . getStyle ( canvas , 'padding-bottom' ) ) ;
var width = boundingRect . right - boundingRect . left - paddingLeft - paddingRight ;
var height = boundingRect . bottom - boundingRect . top - paddingTop - paddingBottom ;
// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
mouseX = Math . round ( ( mouseX - boundingRect . left - paddingLeft ) / ( width ) * canvas . width / chart . currentDevicePixelRatio ) ;
mouseY = Math . round ( ( mouseY - boundingRect . top - paddingTop ) / ( height ) * canvas . height / chart . currentDevicePixelRatio ) ;
return {
x : mouseX ,
y : mouseY
} ;
} ;
helpers . addEvent = function ( node , eventType , method ) {
if ( node . addEventListener ) {
node . addEventListener ( eventType , method ) ;
} else if ( node . attachEvent ) {
node . attachEvent ( "on" + eventType , method ) ;
} else {
node [ "on" + eventType ] = method ;
}
} ;
helpers . removeEvent = function ( node , eventType , handler ) {
if ( node . removeEventListener ) {
node . removeEventListener ( eventType , handler , false ) ;
} else if ( node . detachEvent ) {
node . detachEvent ( "on" + eventType , handler ) ;
} else {
node [ "on" + eventType ] = helpers . noop ;
}
} ;
helpers . bindEvents = function ( chartInstance , arrayOfEvents , handler ) {
// Create the events object if it's not already present
var events = chartInstance . events = chartInstance . events || { } ;
helpers . each ( arrayOfEvents , function ( eventName ) {
events [ eventName ] = function ( ) {
handler . apply ( chartInstance , arguments ) ;
} ;
helpers . addEvent ( chartInstance . chart . canvas , eventName , events [ eventName ] ) ;
} ) ;
} ;
helpers . unbindEvents = function ( chartInstance , arrayOfEvents ) {
var canvas = chartInstance . chart . canvas ;
helpers . each ( arrayOfEvents , function ( handler , eventName ) {
helpers . removeEvent ( canvas , eventName , handler ) ;
} ) ;
} ;
// Private helper function to convert max-width/max-height values that may be percentages into a number
function parseMaxStyle ( styleValue , node , parentProperty ) {
var valueInPixels ;
if ( typeof ( styleValue ) === 'string' ) {
valueInPixels = parseInt ( styleValue , 10 ) ;
if ( styleValue . indexOf ( '%' ) != - 1 ) {
// percentage * size in dimension
valueInPixels = valueInPixels / 100 * node . parentNode [ parentProperty ] ;
}
} else {
valueInPixels = styleValue ;
}
return valueInPixels ;
}
/ * *
* Returns if the given value contains an effective constraint .
* @ private
* /
function isConstrainedValue ( value ) {
return value !== undefined && value !== null && value !== 'none' ;
}
// Private helper to get a constraint dimension
// @param domNode : the node to check the constraint on
// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
// @param percentageProperty : property of parent to use when calculating width as a percentage
// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
function getConstraintDimension ( domNode , maxStyle , percentageProperty ) {
var view = document . defaultView ;
var parentNode = domNode . parentNode ;
var constrainedNode = view . getComputedStyle ( domNode ) [ maxStyle ] ;
var constrainedContainer = view . getComputedStyle ( parentNode ) [ maxStyle ] ;
var hasCNode = isConstrainedValue ( constrainedNode ) ;
var hasCContainer = isConstrainedValue ( constrainedContainer ) ;
var infinity = Number . POSITIVE _INFINITY ;
if ( hasCNode || hasCContainer ) {
return Math . min (
hasCNode ? parseMaxStyle ( constrainedNode , domNode , percentageProperty ) : infinity ,
hasCContainer ? parseMaxStyle ( constrainedContainer , parentNode , percentageProperty ) : infinity ) ;
}
return 'none' ;
}
// returns Number or undefined if no constraint
helpers . getConstraintWidth = function ( domNode ) {
return getConstraintDimension ( domNode , 'max-width' , 'clientWidth' ) ;
} ;
// returns Number or undefined if no constraint
helpers . getConstraintHeight = function ( domNode ) {
return getConstraintDimension ( domNode , 'max-height' , 'clientHeight' ) ;
} ;
helpers . getMaximumWidth = function ( domNode ) {
var container = domNode . parentNode ;
var padding = parseInt ( helpers . getStyle ( container , 'padding-left' ) ) + parseInt ( helpers . getStyle ( container , 'padding-right' ) ) ;
var w = container . clientWidth - padding ;
var cw = helpers . getConstraintWidth ( domNode ) ;
return isNaN ( cw ) ? w : Math . min ( w , cw ) ;
} ;
helpers . getMaximumHeight = function ( domNode ) {
var container = domNode . parentNode ;
var padding = parseInt ( helpers . getStyle ( container , 'padding-top' ) ) + parseInt ( helpers . getStyle ( container , 'padding-bottom' ) ) ;
var h = container . clientHeight - padding ;
var ch = helpers . getConstraintHeight ( domNode ) ;
return isNaN ( ch ) ? h : Math . min ( h , ch ) ;
} ;
helpers . getStyle = function ( el , property ) {
return el . currentStyle ?
el . currentStyle [ property ] :
document . defaultView . getComputedStyle ( el , null ) . getPropertyValue ( property ) ;
} ;
helpers . retinaScale = function ( chart ) {
var ctx = chart . ctx ;
var canvas = chart . canvas ;
var width = canvas . width ;
var height = canvas . height ;
var pixelRatio = chart . currentDevicePixelRatio = window . devicePixelRatio || 1 ;
if ( pixelRatio !== 1 ) {
canvas . height = height * pixelRatio ;
canvas . width = width * pixelRatio ;
ctx . scale ( pixelRatio , pixelRatio ) ;
// Store the device pixel ratio so that we can go backwards in `destroy`.
// The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
// when destroy is called
chart . originalDevicePixelRatio = chart . originalDevicePixelRatio || pixelRatio ;
}
canvas . style . width = width + 'px' ;
canvas . style . height = height + 'px' ;
} ;
//-- Canvas methods
helpers . clear = function ( chart ) {
chart . ctx . clearRect ( 0 , 0 , chart . width , chart . height ) ;
} ;
helpers . fontString = function ( pixelSize , fontStyle , fontFamily ) {
return fontStyle + " " + pixelSize + "px " + fontFamily ;
} ;
helpers . longestText = function ( ctx , font , arrayOfThings , cache ) {
cache = cache || { } ;
var data = cache . data = cache . data || { } ;
var gc = cache . garbageCollect = cache . garbageCollect || [ ] ;
if ( cache . font !== font ) {
data = cache . data = { } ;
gc = cache . garbageCollect = [ ] ;
cache . font = font ;
}
ctx . font = font ;
var longest = 0 ;
helpers . each ( arrayOfThings , function ( thing ) {
// Undefined strings and arrays should not be measured
if ( thing !== undefined && thing !== null && helpers . isArray ( thing ) !== true ) {
longest = helpers . measureText ( ctx , data , gc , longest , thing ) ;
} else if ( helpers . isArray ( thing ) ) {
// if it is an array lets measure each element
// to do maybe simplify this function a bit so we can do this more recursively?
helpers . each ( thing , function ( nestedThing ) {
// Undefined strings and arrays should not be measured
if ( nestedThing !== undefined && nestedThing !== null && ! helpers . isArray ( nestedThing ) ) {
longest = helpers . measureText ( ctx , data , gc , longest , nestedThing ) ;
}
} ) ;
}
} ) ;
var gcLen = gc . length / 2 ;
if ( gcLen > arrayOfThings . length ) {
for ( var i = 0 ; i < gcLen ; i ++ ) {
delete data [ gc [ i ] ] ;
}
gc . splice ( 0 , gcLen ) ;
}
return longest ;
} ;
helpers . measureText = function ( ctx , data , gc , longest , string ) {
var textWidth = data [ string ] ;
if ( ! textWidth ) {
textWidth = data [ string ] = ctx . measureText ( string ) . width ;
gc . push ( string ) ;
}
if ( textWidth > longest ) {
longest = textWidth ;
}
return longest ;
} ;
helpers . numberOfLabelLines = function ( arrayOfThings ) {
var numberOfLines = 1 ;
helpers . each ( arrayOfThings , function ( thing ) {
if ( helpers . isArray ( thing ) ) {
if ( thing . length > numberOfLines ) {
numberOfLines = thing . length ;
}
}
} ) ;
return numberOfLines ;
} ;
helpers . drawRoundedRectangle = function ( ctx , x , y , width , height , radius ) {
ctx . beginPath ( ) ;
ctx . moveTo ( x + radius , y ) ;
ctx . lineTo ( x + width - radius , y ) ;
ctx . quadraticCurveTo ( x + width , y , x + width , y + radius ) ;
ctx . lineTo ( x + width , y + height - radius ) ;
ctx . quadraticCurveTo ( x + width , y + height , x + width - radius , y + height ) ;
ctx . lineTo ( x + radius , y + height ) ;
ctx . quadraticCurveTo ( x , y + height , x , y + height - radius ) ;
ctx . lineTo ( x , y + radius ) ;
ctx . quadraticCurveTo ( x , y , x + radius , y ) ;
ctx . closePath ( ) ;
} ;
helpers . color = function ( c ) {
if ( ! color ) {
console . log ( 'Color.js not found!' ) ;
return c ;
}
/* global CanvasGradient */
if ( c instanceof CanvasGradient ) {
return color ( Chart . defaults . global . defaultColor ) ;
}
return color ( c ) ;
} ;
helpers . addResizeListener = function ( node , callback ) {
// Hide an iframe before the node
var hiddenIframe = document . createElement ( 'iframe' ) ;
var hiddenIframeClass = 'chartjs-hidden-iframe' ;
if ( hiddenIframe . classlist ) {
// can use classlist
hiddenIframe . classlist . add ( hiddenIframeClass ) ;
} else {
hiddenIframe . setAttribute ( 'class' , hiddenIframeClass ) ;
}
// Set the style
hiddenIframe . tabIndex = - 1 ;
var style = hiddenIframe . style ;
style . width = '100%' ;
style . display = 'block' ;
style . border = 0 ;
style . height = 0 ;
style . margin = 0 ;
style . position = 'absolute' ;
style . left = 0 ;
style . right = 0 ;
style . top = 0 ;
style . bottom = 0 ;
// Insert the iframe so that contentWindow is available
node . insertBefore ( hiddenIframe , node . firstChild ) ;
( hiddenIframe . contentWindow || hiddenIframe ) . onresize = function ( ) {
if ( callback ) {
callback ( ) ;
}
} ;
} ;
helpers . removeResizeListener = function ( node ) {
var hiddenIframe = node . querySelector ( '.chartjs-hidden-iframe' ) ;
// Remove the resize detect iframe
if ( hiddenIframe ) {
hiddenIframe . parentNode . removeChild ( hiddenIframe ) ;
}
} ;
helpers . isArray = Array . isArray ?
function ( obj ) { return Array . isArray ( obj ) ; } :
function ( obj ) {
return Object . prototype . toString . call ( obj ) === '[object Array]' ;
} ;
//! @see http://stackoverflow.com/a/14853974
helpers . arrayEquals = function ( a0 , a1 ) {
var i , ilen , v0 , v1 ;
if ( ! a0 || ! a1 || a0 . length != a1 . length ) {
return false ;
}
for ( i = 0 , ilen = a0 . length ; i < ilen ; ++ i ) {
v0 = a0 [ i ] ;
v1 = a1 [ i ] ;
if ( v0 instanceof Array && v1 instanceof Array ) {
if ( ! helpers . arrayEquals ( v0 , v1 ) ) {
return false ;
}
} else if ( v0 != v1 ) {
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
return false ;
}
}
return true ;
} ;
helpers . callCallback = function ( fn , args , _tArg ) {
if ( fn && typeof fn . call === 'function' ) {
fn . apply ( _tArg , args ) ;
}
} ;
helpers . getHoverColor = function ( color ) {
/* global CanvasPattern */
return ( color instanceof CanvasPattern ) ?
color :
helpers . color ( color ) . saturate ( 0.5 ) . darken ( 0.1 ) . rgbString ( ) ;
} ;
} ;
} , { "3" : 3 } ] , 27 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( ) {
//Occupy the global variable of Chart, and create a simple base class
var Chart = function ( context , config ) {
var me = this ;
var helpers = Chart . helpers ;
me . config = config || {
data : {
datasets : [ ]
}
} ;
// Support a jQuery'd canvas element
if ( context . length && context [ 0 ] . getContext ) {
context = context [ 0 ] ;
}
// Support a canvas domnode
if ( context . getContext ) {
context = context . getContext ( "2d" ) ;
}
me . ctx = context ;
me . canvas = context . canvas ;
context . canvas . style . display = context . canvas . style . display || 'block' ;
// Figure out what the size of the chart will be.
// If the canvas has a specified width and height, we use those else
// we look to see if the canvas node has a CSS width and height.
// If there is still no height, fill the parent container
me . width = context . canvas . width || parseInt ( helpers . getStyle ( context . canvas , 'width' ) , 10 ) || helpers . getMaximumWidth ( context . canvas ) ;
me . height = context . canvas . height || parseInt ( helpers . getStyle ( context . canvas , 'height' ) , 10 ) || helpers . getMaximumHeight ( context . canvas ) ;
me . aspectRatio = me . width / me . height ;
if ( isNaN ( me . aspectRatio ) || isFinite ( me . aspectRatio ) === false ) {
// If the canvas has no size, try and figure out what the aspect ratio will be.
// Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
// else use the canvas default ratio of 2
me . aspectRatio = config . aspectRatio !== undefined ? config . aspectRatio : 2 ;
}
// Store the original style of the element so we can set it back
me . originalCanvasStyleWidth = context . canvas . style . width ;
me . originalCanvasStyleHeight = context . canvas . style . height ;
// High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
helpers . retinaScale ( me ) ;
me . controller = new Chart . Controller ( me ) ;
// Always bind this so that if the responsive state changes we still work
helpers . addResizeListener ( context . canvas . parentNode , function ( ) {
if ( me . controller && me . controller . config . options . responsive ) {
me . controller . resize ( ) ;
}
} ) ;
return me . controller ? me . controller : me ;
} ;
//Globally expose the defaults to allow for user updating/changing
Chart . defaults = {
global : {
responsive : true ,
responsiveAnimationDuration : 0 ,
maintainAspectRatio : true ,
events : [ "mousemove" , "mouseout" , "click" , "touchstart" , "touchmove" ] ,
hover : {
onHover : null ,
mode : 'single' ,
animationDuration : 400
} ,
onClick : null ,
defaultColor : 'rgba(0,0,0,0.1)' ,
defaultFontColor : '#666' ,
defaultFontFamily : "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif" ,
defaultFontSize : 12 ,
defaultFontStyle : 'normal' ,
showLines : true ,
// Element defaults defined in element extensions
elements : { } ,
// Legend callback string
legendCallback : function ( chart ) {
var text = [ ] ;
text . push ( '<ul class="' + chart . id + '-legend">' ) ;
for ( var i = 0 ; i < chart . data . datasets . length ; i ++ ) {
text . push ( '<li><span style="background-color:' + chart . data . datasets [ i ] . backgroundColor + '"></span>' ) ;
if ( chart . data . datasets [ i ] . label ) {
text . push ( chart . data . datasets [ i ] . label ) ;
}
text . push ( '</li>' ) ;
}
text . push ( '</ul>' ) ;
return text . join ( "" ) ;
}
}
} ;
Chart . Chart = Chart ;
return Chart ;
} ;
} , { } ] , 28 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
// The layout service is very self explanatory. It's responsible for the layout within a chart.
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
// It is this service's responsibility of carrying out that layout.
Chart . layoutService = {
defaults : { } ,
// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
addBox : function ( chartInstance , box ) {
if ( ! chartInstance . boxes ) {
chartInstance . boxes = [ ] ;
}
chartInstance . boxes . push ( box ) ;
} ,
removeBox : function ( chartInstance , box ) {
if ( ! chartInstance . boxes ) {
return ;
}
chartInstance . boxes . splice ( chartInstance . boxes . indexOf ( box ) , 1 ) ;
} ,
// The most important function
update : function ( chartInstance , width , height ) {
if ( ! chartInstance ) {
return ;
}
var xPadding = 0 ;
var yPadding = 0 ;
var leftBoxes = helpers . where ( chartInstance . boxes , function ( box ) {
return box . options . position === "left" ;
} ) ;
var rightBoxes = helpers . where ( chartInstance . boxes , function ( box ) {
return box . options . position === "right" ;
} ) ;
var topBoxes = helpers . where ( chartInstance . boxes , function ( box ) {
return box . options . position === "top" ;
} ) ;
var bottomBoxes = helpers . where ( chartInstance . boxes , function ( box ) {
return box . options . position === "bottom" ;
} ) ;
// Boxes that overlay the chartarea such as the radialLinear scale
var chartAreaBoxes = helpers . where ( chartInstance . boxes , function ( box ) {
return box . options . position === "chartArea" ;
} ) ;
// Ensure that full width boxes are at the very top / bottom
topBoxes . sort ( function ( a , b ) {
return ( b . options . fullWidth ? 1 : 0 ) - ( a . options . fullWidth ? 1 : 0 ) ;
} ) ;
bottomBoxes . sort ( function ( a , b ) {
return ( a . options . fullWidth ? 1 : 0 ) - ( b . options . fullWidth ? 1 : 0 ) ;
} ) ;
// Essentially we now have any number of boxes on each of the 4 sides.
// Our canvas looks like the following.
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
// B1 is the bottom axis
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
// an error will be thrown.
//
// |----------------------------------------------------|
// | T1 (Full Width) |
// |----------------------------------------------------|
// | | | T2 | |
// | |----|-------------------------------------|----|
// | | | C1 | | C2 | |
// | | |----| |----| |
// | | | | |
// | L1 | L2 | ChartArea (C0) | R1 |
// | | | | |
// | | |----| |----| |
// | | | C3 | | C4 | |
// | |----|-------------------------------------|----|
// | | | B1 | |
// |----------------------------------------------------|
// | B2 (Full Width) |
// |----------------------------------------------------|
//
// What we do to find the best sizing, we do the following
// 1. Determine the minimum size of the chart area.
// 2. Split the remaining width equally between each vertical axis
// 3. Split the remaining height equally between each horizontal axis
// 4. Give each layout the maximum size it can be. The layout will return it's minimum size
// 5. Adjust the sizes of each axis based on it's minimum reported size.
// 6. Refit each axis
// 7. Position each axis in the final location
// 8. Tell the chart the final location of the chart area
// 9. Tell any axes that overlay the chart area the positions of the chart area
// Step 1
var chartWidth = width - ( 2 * xPadding ) ;
var chartHeight = height - ( 2 * yPadding ) ;
var chartAreaWidth = chartWidth / 2 ; // min 50%
var chartAreaHeight = chartHeight / 2 ; // min 50%
// Step 2
var verticalBoxWidth = ( width - chartAreaWidth ) / ( leftBoxes . length + rightBoxes . length ) ;
// Step 3
var horizontalBoxHeight = ( height - chartAreaHeight ) / ( topBoxes . length + bottomBoxes . length ) ;
// Step 4
var maxChartAreaWidth = chartWidth ;
var maxChartAreaHeight = chartHeight ;
var minBoxSizes = [ ] ;
helpers . each ( leftBoxes . concat ( rightBoxes , topBoxes , bottomBoxes ) , getMinimumBoxSize ) ;
function getMinimumBoxSize ( box ) {
var minSize ;
var isHorizontal = box . isHorizontal ( ) ;
if ( isHorizontal ) {
minSize = box . update ( box . options . fullWidth ? chartWidth : maxChartAreaWidth , horizontalBoxHeight ) ;
maxChartAreaHeight -= minSize . height ;
} else {
minSize = box . update ( verticalBoxWidth , chartAreaHeight ) ;
maxChartAreaWidth -= minSize . width ;
}
minBoxSizes . push ( {
horizontal : isHorizontal ,
minSize : minSize ,
box : box
} ) ;
}
// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
// be if the axes are drawn at their minimum sizes.
// Steps 5 & 6
var totalLeftBoxesWidth = xPadding ;
var totalRightBoxesWidth = xPadding ;
var totalTopBoxesHeight = yPadding ;
var totalBottomBoxesHeight = yPadding ;
// Update, and calculate the left and right margins for the horizontal boxes
helpers . each ( leftBoxes . concat ( rightBoxes ) , fitBox ) ;
helpers . each ( leftBoxes , function ( box ) {
totalLeftBoxesWidth += box . width ;
} ) ;
helpers . each ( rightBoxes , function ( box ) {
totalRightBoxesWidth += box . width ;
} ) ;
// Set the Left and Right margins for the horizontal boxes
helpers . each ( topBoxes . concat ( bottomBoxes ) , fitBox ) ;
// Function to fit a box
function fitBox ( box ) {
var minBoxSize = helpers . findNextWhere ( minBoxSizes , function ( minBoxSize ) {
return minBoxSize . box === box ;
} ) ;
if ( minBoxSize ) {
if ( box . isHorizontal ( ) ) {
var scaleMargin = {
left : totalLeftBoxesWidth ,
right : totalRightBoxesWidth ,
top : 0 ,
bottom : 0
} ;
// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
// on the margin. Sometimes they need to increase in size slightly
box . update ( box . options . fullWidth ? chartWidth : maxChartAreaWidth , chartHeight / 2 , scaleMargin ) ;
} else {
box . update ( minBoxSize . minSize . width , maxChartAreaHeight ) ;
}
}
}
// Figure out how much margin is on the top and bottom of the vertical boxes
helpers . each ( topBoxes , function ( box ) {
totalTopBoxesHeight += box . height ;
} ) ;
helpers . each ( bottomBoxes , function ( box ) {
totalBottomBoxesHeight += box . height ;
} ) ;
// Let the left layout know the final margin
helpers . each ( leftBoxes . concat ( rightBoxes ) , finalFitVerticalBox ) ;
function finalFitVerticalBox ( box ) {
var minBoxSize = helpers . findNextWhere ( minBoxSizes , function ( minBoxSize ) {
return minBoxSize . box === box ;
} ) ;
var scaleMargin = {
left : 0 ,
right : 0 ,
top : totalTopBoxesHeight ,
bottom : totalBottomBoxesHeight
} ;
if ( minBoxSize ) {
box . update ( minBoxSize . minSize . width , maxChartAreaHeight , scaleMargin ) ;
}
}
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
totalLeftBoxesWidth = xPadding ;
totalRightBoxesWidth = xPadding ;
totalTopBoxesHeight = yPadding ;
totalBottomBoxesHeight = yPadding ;
helpers . each ( leftBoxes , function ( box ) {
totalLeftBoxesWidth += box . width ;
} ) ;
helpers . each ( rightBoxes , function ( box ) {
totalRightBoxesWidth += box . width ;
} ) ;
helpers . each ( topBoxes , function ( box ) {
totalTopBoxesHeight += box . height ;
} ) ;
helpers . each ( bottomBoxes , function ( box ) {
totalBottomBoxesHeight += box . height ;
} ) ;
// Figure out if our chart area changed. This would occur if the dataset layout label rotation
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
// without calling `fit` again
var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight ;
var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth ;
if ( newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight ) {
helpers . each ( leftBoxes , function ( box ) {
box . height = newMaxChartAreaHeight ;
} ) ;
helpers . each ( rightBoxes , function ( box ) {
box . height = newMaxChartAreaHeight ;
} ) ;
helpers . each ( topBoxes , function ( box ) {
if ( ! box . options . fullWidth ) {
box . width = newMaxChartAreaWidth ;
}
} ) ;
helpers . each ( bottomBoxes , function ( box ) {
if ( ! box . options . fullWidth ) {
box . width = newMaxChartAreaWidth ;
}
} ) ;
maxChartAreaHeight = newMaxChartAreaHeight ;
maxChartAreaWidth = newMaxChartAreaWidth ;
}
// Step 7 - Position the boxes
var left = xPadding ;
var top = yPadding ;
helpers . each ( leftBoxes . concat ( topBoxes ) , placeBox ) ;
// Account for chart width and height
left += maxChartAreaWidth ;
top += maxChartAreaHeight ;
helpers . each ( rightBoxes , placeBox ) ;
helpers . each ( bottomBoxes , placeBox ) ;
function placeBox ( box ) {
if ( box . isHorizontal ( ) ) {
box . left = box . options . fullWidth ? xPadding : totalLeftBoxesWidth ;
box . right = box . options . fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth ;
box . top = top ;
box . bottom = top + box . height ;
// Move to next point
top = box . bottom ;
} else {
box . left = left ;
box . right = left + box . width ;
box . top = totalTopBoxesHeight ;
box . bottom = totalTopBoxesHeight + maxChartAreaHeight ;
// Move to next point
left = box . right ;
}
}
// Step 8
chartInstance . chartArea = {
left : totalLeftBoxesWidth ,
top : totalTopBoxesHeight ,
right : totalLeftBoxesWidth + maxChartAreaWidth ,
bottom : totalTopBoxesHeight + maxChartAreaHeight
} ;
// Step 9
helpers . each ( chartAreaBoxes , function ( box ) {
box . left = chartInstance . chartArea . left ;
box . top = chartInstance . chartArea . top ;
box . right = chartInstance . chartArea . right ;
box . bottom = chartInstance . chartArea . bottom ;
box . update ( maxChartAreaWidth , maxChartAreaHeight ) ;
} ) ;
}
} ;
} ;
} , { } ] , 29 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var noop = helpers . noop ;
Chart . defaults . global . legend = {
display : true ,
position : 'top' ,
fullWidth : true , // marks that this box should take the full width of the canvas (pushing down other boxes)
reverse : false ,
// a callback that will handle
onClick : function ( e , legendItem ) {
var index = legendItem . datasetIndex ;
var ci = this . chart ;
var meta = ci . getDatasetMeta ( index ) ;
// See controller.isDatasetVisible comment
meta . hidden = meta . hidden === null ? ! ci . data . datasets [ index ] . hidden : null ;
// We hid a dataset ... rerender the chart
ci . update ( ) ;
} ,
labels : {
boxWidth : 40 ,
padding : 10 ,
// Generates labels shown in the legend
// Valid properties to return:
// text : text to display
// fillStyle : fill of coloured box
// strokeStyle: stroke of coloured box
// hidden : if this legend item refers to a hidden item
// lineCap : cap style for line
// lineDash
// lineDashOffset :
// lineJoin :
// lineWidth :
generateLabels : function ( chart ) {
var data = chart . data ;
return helpers . isArray ( data . datasets ) ? data . datasets . map ( function ( dataset , i ) {
return {
text : dataset . label ,
fillStyle : ( ! helpers . isArray ( dataset . backgroundColor ) ? dataset . backgroundColor : dataset . backgroundColor [ 0 ] ) ,
hidden : ! chart . isDatasetVisible ( i ) ,
lineCap : dataset . borderCapStyle ,
lineDash : dataset . borderDash ,
lineDashOffset : dataset . borderDashOffset ,
lineJoin : dataset . borderJoinStyle ,
lineWidth : dataset . borderWidth ,
strokeStyle : dataset . borderColor ,
pointStyle : dataset . pointStyle ,
// Below is extra data used for toggling the datasets
datasetIndex : i
} ;
} , this ) : [ ] ;
}
}
} ;
Chart . Legend = Chart . Element . extend ( {
initialize : function ( config ) {
helpers . extend ( this , config ) ;
// Contains hit boxes for each dataset (in dataset order)
this . legendHitBoxes = [ ] ;
// Are we in doughnut mode which has a different data type
this . doughnutMode = false ;
} ,
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
beforeUpdate : noop ,
update : function ( maxWidth , maxHeight , margins ) {
var me = this ;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me . beforeUpdate ( ) ;
// Absorb the master measurements
me . maxWidth = maxWidth ;
me . maxHeight = maxHeight ;
me . margins = margins ;
// Dimensions
me . beforeSetDimensions ( ) ;
me . setDimensions ( ) ;
me . afterSetDimensions ( ) ;
// Labels
me . beforeBuildLabels ( ) ;
me . buildLabels ( ) ;
me . afterBuildLabels ( ) ;
// Fit
me . beforeFit ( ) ;
me . fit ( ) ;
me . afterFit ( ) ;
//
me . afterUpdate ( ) ;
return me . minSize ;
} ,
afterUpdate : noop ,
//
beforeSetDimensions : noop ,
setDimensions : function ( ) {
var me = this ;
// Set the unconstrained dimension before label rotation
if ( me . isHorizontal ( ) ) {
// Reset position before calculating rotation
me . width = me . maxWidth ;
me . left = 0 ;
me . right = me . width ;
} else {
me . height = me . maxHeight ;
// Reset position before calculating rotation
me . top = 0 ;
me . bottom = me . height ;
}
// Reset padding
me . paddingLeft = 0 ;
me . paddingTop = 0 ;
me . paddingRight = 0 ;
me . paddingBottom = 0 ;
// Reset minSize
me . minSize = {
width : 0 ,
height : 0
} ;
} ,
afterSetDimensions : noop ,
//
beforeBuildLabels : noop ,
buildLabels : function ( ) {
var me = this ;
me . legendItems = me . options . labels . generateLabels . call ( me , me . chart ) ;
if ( me . options . reverse ) {
me . legendItems . reverse ( ) ;
}
} ,
afterBuildLabels : noop ,
//
beforeFit : noop ,
fit : function ( ) {
var me = this ;
var opts = me . options ;
var labelOpts = opts . labels ;
var display = opts . display ;
var ctx = me . ctx ;
var globalDefault = Chart . defaults . global ,
itemOrDefault = helpers . getValueOrDefault ,
fontSize = itemOrDefault ( labelOpts . fontSize , globalDefault . defaultFontSize ) ,
fontStyle = itemOrDefault ( labelOpts . fontStyle , globalDefault . defaultFontStyle ) ,
fontFamily = itemOrDefault ( labelOpts . fontFamily , globalDefault . defaultFontFamily ) ,
labelFont = helpers . fontString ( fontSize , fontStyle , fontFamily ) ;
// Reset hit boxes
var hitboxes = me . legendHitBoxes = [ ] ;
var minSize = me . minSize ;
var isHorizontal = me . isHorizontal ( ) ;
if ( isHorizontal ) {
minSize . width = me . maxWidth ; // fill all the width
minSize . height = display ? 10 : 0 ;
} else {
minSize . width = display ? 10 : 0 ;
minSize . height = me . maxHeight ; // fill all the height
}
// Increase sizes here
if ( display ) {
ctx . font = labelFont ;
if ( isHorizontal ) {
// Labels
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
var lineWidths = me . lineWidths = [ 0 ] ;
var totalHeight = me . legendItems . length ? fontSize + ( labelOpts . padding ) : 0 ;
ctx . textAlign = "left" ;
ctx . textBaseline = 'top' ;
helpers . each ( me . legendItems , function ( legendItem , i ) {
var boxWidth = labelOpts . usePointStyle ?
fontSize * Math . sqrt ( 2 ) :
labelOpts . boxWidth ;
var width = boxWidth + ( fontSize / 2 ) + ctx . measureText ( legendItem . text ) . width ;
if ( lineWidths [ lineWidths . length - 1 ] + width + labelOpts . padding >= me . width ) {
totalHeight += fontSize + ( labelOpts . padding ) ;
lineWidths [ lineWidths . length ] = me . left ;
}
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes [ i ] = {
left : 0 ,
top : 0 ,
width : width ,
height : fontSize
} ;
lineWidths [ lineWidths . length - 1 ] += width + labelOpts . padding ;
} ) ;
minSize . height += totalHeight ;
} else {
var vPadding = labelOpts . padding ;
var columnWidths = me . columnWidths = [ ] ;
var totalWidth = labelOpts . padding ;
var currentColWidth = 0 ;
var currentColHeight = 0 ;
var itemHeight = fontSize + vPadding ;
helpers . each ( me . legendItems , function ( legendItem , i ) {
// If usePointStyle is set, multiple boxWidth by 2 since it represents
// the radius and not truly the width
var boxWidth = labelOpts . usePointStyle ? 2 * labelOpts . boxWidth : labelOpts . boxWidth ;
var itemWidth = boxWidth + ( fontSize / 2 ) + ctx . measureText ( legendItem . text ) . width ;
// If too tall, go to new column
if ( currentColHeight + itemHeight > minSize . height ) {
totalWidth += currentColWidth + labelOpts . padding ;
columnWidths . push ( currentColWidth ) ; // previous column width
currentColWidth = 0 ;
currentColHeight = 0 ;
}
// Get max width
currentColWidth = Math . max ( currentColWidth , itemWidth ) ;
currentColHeight += itemHeight ;
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes [ i ] = {
left : 0 ,
top : 0 ,
width : itemWidth ,
height : fontSize
} ;
} ) ;
totalWidth += currentColWidth ;
columnWidths . push ( currentColWidth ) ;
minSize . width += totalWidth ;
}
}
me . width = minSize . width ;
me . height = minSize . height ;
} ,
afterFit : noop ,
// Shared Methods
isHorizontal : function ( ) {
return this . options . position === "top" || this . options . position === "bottom" ;
} ,
// Actualy draw the legend on the canvas
draw : function ( ) {
var me = this ;
var opts = me . options ;
var labelOpts = opts . labels ;
var globalDefault = Chart . defaults . global ,
lineDefault = globalDefault . elements . line ,
legendWidth = me . width ,
lineWidths = me . lineWidths ;
if ( opts . display ) {
var ctx = me . ctx ,
cursor ,
itemOrDefault = helpers . getValueOrDefault ,
fontColor = itemOrDefault ( labelOpts . fontColor , globalDefault . defaultFontColor ) ,
fontSize = itemOrDefault ( labelOpts . fontSize , globalDefault . defaultFontSize ) ,
fontStyle = itemOrDefault ( labelOpts . fontStyle , globalDefault . defaultFontStyle ) ,
fontFamily = itemOrDefault ( labelOpts . fontFamily , globalDefault . defaultFontFamily ) ,
labelFont = helpers . fontString ( fontSize , fontStyle , fontFamily ) ;
// Canvas setup
ctx . textAlign = "left" ;
ctx . textBaseline = 'top' ;
ctx . lineWidth = 0.5 ;
ctx . strokeStyle = fontColor ; // for strikethrough effect
ctx . fillStyle = fontColor ; // render in correct colour
ctx . font = labelFont ;
var boxWidth = labelOpts . boxWidth ,
hitboxes = me . legendHitBoxes ;
// current position
var drawLegendBox = function ( x , y , legendItem ) {
if ( isNaN ( boxWidth ) || boxWidth <= 0 ) {
return ;
}
// Set the ctx for the box
ctx . save ( ) ;
ctx . fillStyle = itemOrDefault ( legendItem . fillStyle , globalDefault . defaultColor ) ;
ctx . lineCap = itemOrDefault ( legendItem . lineCap , lineDefault . borderCapStyle ) ;
ctx . lineDashOffset = itemOrDefault ( legendItem . lineDashOffset , lineDefault . borderDashOffset ) ;
ctx . lineJoin = itemOrDefault ( legendItem . lineJoin , lineDefault . borderJoinStyle ) ;
ctx . lineWidth = itemOrDefault ( legendItem . lineWidth , lineDefault . borderWidth ) ;
ctx . strokeStyle = itemOrDefault ( legendItem . strokeStyle , globalDefault . defaultColor ) ;
if ( ctx . setLineDash ) {
// IE 9 and 10 do not support line dash
ctx . setLineDash ( itemOrDefault ( legendItem . lineDash , lineDefault . borderDash ) ) ;
}
if ( opts . labels && opts . labels . usePointStyle ) {
// Recalulate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math . SQRT2 / 2 ;
var offSet = radius / Math . SQRT2 ;
var centerX = x + offSet ;
var centerY = y + offSet ;
// Draw pointStyle as legend symbol
Chart . canvasHelpers . drawPoint ( ctx , legendItem . pointStyle , radius , centerX , centerY ) ;
}
else {
// Draw box as legend symbol
ctx . strokeRect ( x , y , boxWidth , fontSize ) ;
ctx . fillRect ( x , y , boxWidth , fontSize ) ;
}
ctx . restore ( ) ;
} ;
var fillText = function ( x , y , legendItem , textWidth ) {
ctx . fillText ( legendItem . text , boxWidth + ( fontSize / 2 ) + x , y ) ;
if ( legendItem . hidden ) {
// Strikethrough the text if hidden
ctx . beginPath ( ) ;
ctx . lineWidth = 2 ;
ctx . moveTo ( boxWidth + ( fontSize / 2 ) + x , y + ( fontSize / 2 ) ) ;
ctx . lineTo ( boxWidth + ( fontSize / 2 ) + x + textWidth , y + ( fontSize / 2 ) ) ;
ctx . stroke ( ) ;
}
} ;
// Horizontal
var isHorizontal = me . isHorizontal ( ) ;
if ( isHorizontal ) {
cursor = {
x : me . left + ( ( legendWidth - lineWidths [ 0 ] ) / 2 ) ,
y : me . top + labelOpts . padding ,
line : 0
} ;
} else {
cursor = {
x : me . left + labelOpts . padding ,
y : me . top + labelOpts . padding ,
line : 0
} ;
}
var itemHeight = fontSize + labelOpts . padding ;
helpers . each ( me . legendItems , function ( legendItem , i ) {
var textWidth = ctx . measureText ( legendItem . text ) . width ,
width = labelOpts . usePointStyle ?
fontSize + ( fontSize / 2 ) + textWidth :
boxWidth + ( fontSize / 2 ) + textWidth ,
x = cursor . x ,
y = cursor . y ;
if ( isHorizontal ) {
if ( x + width >= legendWidth ) {
y = cursor . y += itemHeight ;
cursor . line ++ ;
x = cursor . x = me . left + ( ( legendWidth - lineWidths [ cursor . line ] ) / 2 ) ;
}
} else {
if ( y + itemHeight > me . bottom ) {
x = cursor . x = x + me . columnWidths [ cursor . line ] + labelOpts . padding ;
y = cursor . y = me . top ;
cursor . line ++ ;
}
}
drawLegendBox ( x , y , legendItem ) ;
hitboxes [ i ] . left = x ;
hitboxes [ i ] . top = y ;
// Fill the actual label
fillText ( x , y , legendItem , textWidth ) ;
if ( isHorizontal ) {
cursor . x += width + ( labelOpts . padding ) ;
} else {
cursor . y += itemHeight ;
}
} ) ;
}
} ,
// Handle an event
handleEvent : function ( e ) {
var me = this ;
var position = helpers . getRelativePosition ( e , me . chart . chart ) ,
x = position . x ,
y = position . y ,
opts = me . options ;
if ( x >= me . left && x <= me . right && y >= me . top && y <= me . bottom ) {
// See if we are touching one of the dataset boxes
var lh = me . legendHitBoxes ;
for ( var i = 0 ; i < lh . length ; ++ i ) {
var hitBox = lh [ i ] ;
if ( x >= hitBox . left && x <= hitBox . left + hitBox . width && y >= hitBox . top && y <= hitBox . top + hitBox . height ) {
// Touching an element
if ( opts . onClick ) {
opts . onClick . call ( me , e , me . legendItems [ i ] ) ;
}
break ;
}
}
}
}
} ) ;
// Register the legend plugin
Chart . plugins . register ( {
beforeInit : function ( chartInstance ) {
var opts = chartInstance . options ;
var legendOpts = opts . legend ;
if ( legendOpts ) {
chartInstance . legend = new Chart . Legend ( {
ctx : chartInstance . chart . ctx ,
options : legendOpts ,
chart : chartInstance
} ) ;
Chart . layoutService . addBox ( chartInstance , chartInstance . legend ) ;
}
}
} ) ;
} ;
} , { } ] , 30 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var noop = Chart . helpers . noop ;
/ * *
* The plugin service singleton
* @ namespace Chart . plugins
* @ since 2.1 . 0
* /
Chart . plugins = {
_plugins : [ ] ,
/ * *
* Registers the given plugin ( s ) if not already registered .
* @ param { Array | Object } plugins plugin instance ( s ) .
* /
register : function ( plugins ) {
var p = this . _plugins ;
( [ ] ) . concat ( plugins ) . forEach ( function ( plugin ) {
if ( p . indexOf ( plugin ) === - 1 ) {
p . push ( plugin ) ;
}
} ) ;
} ,
/ * *
* Unregisters the given plugin ( s ) only if registered .
* @ param { Array | Object } plugins plugin instance ( s ) .
* /
unregister : function ( plugins ) {
var p = this . _plugins ;
( [ ] ) . concat ( plugins ) . forEach ( function ( plugin ) {
var idx = p . indexOf ( plugin ) ;
if ( idx !== - 1 ) {
p . splice ( idx , 1 ) ;
}
} ) ;
} ,
/ * *
* Remove all registered p ^ lugins .
* @ since 2.1 . 5
* /
clear : function ( ) {
this . _plugins = [ ] ;
} ,
/ * *
* Returns the number of registered plugins ?
* @ returns { Number }
* @ since 2.1 . 5
* /
count : function ( ) {
return this . _plugins . length ;
} ,
/ * *
* Returns all registered plugin intances .
* @ returns { Array } array of plugin objects .
* @ since 2.1 . 5
* /
getAll : function ( ) {
return this . _plugins ;
} ,
/ * *
* Calls registered plugins on the specified extension , with the given args . This
* method immediately returns as soon as a plugin explicitly returns false . The
* returned value can be used , for instance , to interrupt the current action .
* @ param { String } extension the name of the plugin method to call ( e . g . 'beforeUpdate' ) .
* @ param { Array } [ args ] extra arguments to apply to the extension call .
* @ returns { Boolean } false if any of the plugins return false , else returns true .
* /
notify : function ( extension , args ) {
var plugins = this . _plugins ;
var ilen = plugins . length ;
var i , plugin ;
for ( i = 0 ; i < ilen ; ++ i ) {
plugin = plugins [ i ] ;
if ( typeof plugin [ extension ] === 'function' ) {
if ( plugin [ extension ] . apply ( plugin , args || [ ] ) === false ) {
return false ;
}
}
}
return true ;
}
} ;
/ * *
* Plugin extension methods .
* @ interface Chart . PluginBase
* @ since 2.1 . 0
* /
Chart . PluginBase = Chart . Element . extend ( {
// Called at start of chart init
beforeInit : noop ,
// Called at end of chart init
afterInit : noop ,
// Called at start of update
beforeUpdate : noop ,
// Called at end of update
afterUpdate : noop ,
// Called at start of draw
beforeDraw : noop ,
// Called at end of draw
afterDraw : noop ,
// Called during destroy
destroy : noop
} ) ;
/ * *
* Provided for backward compatibility , use Chart . plugins instead
* @ namespace Chart . pluginService
* @ deprecated since version 2.1 . 5
* @ todo remove me at version 3
* /
Chart . pluginService = Chart . plugins ;
} ;
} , { } ] , 31 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . scale = {
display : true ,
position : "left" ,
// grid line settings
gridLines : {
display : true ,
color : "rgba(0, 0, 0, 0.1)" ,
lineWidth : 1 ,
drawBorder : true ,
drawOnChartArea : true ,
drawTicks : true ,
tickMarkLength : 10 ,
zeroLineWidth : 1 ,
zeroLineColor : "rgba(0,0,0,0.25)" ,
offsetGridLines : false ,
borderDash : [ ] ,
borderDashOffset : 0.0
} ,
// scale label
scaleLabel : {
// actual label
labelString : '' ,
// display property
display : false
} ,
// label settings
ticks : {
beginAtZero : false ,
minRotation : 0 ,
maxRotation : 50 ,
mirror : false ,
padding : 10 ,
reverse : false ,
display : true ,
autoSkip : true ,
autoSkipPadding : 0 ,
labelOffset : 0 ,
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
callback : function ( value ) {
return helpers . isArray ( value ) ? value : '' + value ;
}
}
} ;
Chart . Scale = Chart . Element . extend ( {
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
beforeUpdate : function ( ) {
helpers . callCallback ( this . options . beforeUpdate , [ this ] ) ;
} ,
update : function ( maxWidth , maxHeight , margins ) {
var me = this ;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me . beforeUpdate ( ) ;
// Absorb the master measurements
me . maxWidth = maxWidth ;
me . maxHeight = maxHeight ;
me . margins = helpers . extend ( {
left : 0 ,
right : 0 ,
top : 0 ,
bottom : 0
} , margins ) ;
// Dimensions
me . beforeSetDimensions ( ) ;
me . setDimensions ( ) ;
me . afterSetDimensions ( ) ;
// Data min/max
me . beforeDataLimits ( ) ;
me . determineDataLimits ( ) ;
me . afterDataLimits ( ) ;
// Ticks
me . beforeBuildTicks ( ) ;
me . buildTicks ( ) ;
me . afterBuildTicks ( ) ;
me . beforeTickToLabelConversion ( ) ;
me . convertTicksToLabels ( ) ;
me . afterTickToLabelConversion ( ) ;
// Tick Rotation
me . beforeCalculateTickRotation ( ) ;
me . calculateTickRotation ( ) ;
me . afterCalculateTickRotation ( ) ;
// Fit
me . beforeFit ( ) ;
me . fit ( ) ;
me . afterFit ( ) ;
//
me . afterUpdate ( ) ;
return me . minSize ;
} ,
afterUpdate : function ( ) {
helpers . callCallback ( this . options . afterUpdate , [ this ] ) ;
} ,
//
beforeSetDimensions : function ( ) {
helpers . callCallback ( this . options . beforeSetDimensions , [ this ] ) ;
} ,
setDimensions : function ( ) {
var me = this ;
// Set the unconstrained dimension before label rotation
if ( me . isHorizontal ( ) ) {
// Reset position before calculating rotation
me . width = me . maxWidth ;
me . left = 0 ;
me . right = me . width ;
} else {
me . height = me . maxHeight ;
// Reset position before calculating rotation
me . top = 0 ;
me . bottom = me . height ;
}
// Reset padding
me . paddingLeft = 0 ;
me . paddingTop = 0 ;
me . paddingRight = 0 ;
me . paddingBottom = 0 ;
} ,
afterSetDimensions : function ( ) {
helpers . callCallback ( this . options . afterSetDimensions , [ this ] ) ;
} ,
// Data limits
beforeDataLimits : function ( ) {
helpers . callCallback ( this . options . beforeDataLimits , [ this ] ) ;
} ,
determineDataLimits : helpers . noop ,
afterDataLimits : function ( ) {
helpers . callCallback ( this . options . afterDataLimits , [ this ] ) ;
} ,
//
beforeBuildTicks : function ( ) {
helpers . callCallback ( this . options . beforeBuildTicks , [ this ] ) ;
} ,
buildTicks : helpers . noop ,
afterBuildTicks : function ( ) {
helpers . callCallback ( this . options . afterBuildTicks , [ this ] ) ;
} ,
beforeTickToLabelConversion : function ( ) {
helpers . callCallback ( this . options . beforeTickToLabelConversion , [ this ] ) ;
} ,
convertTicksToLabels : function ( ) {
var me = this ;
// Convert ticks to strings
me . ticks = me . ticks . map ( function ( numericalTick , index , ticks ) {
if ( me . options . ticks . userCallback ) {
return me . options . ticks . userCallback ( numericalTick , index , ticks ) ;
}
return me . options . ticks . callback ( numericalTick , index , ticks ) ;
} ,
me ) ;
} ,
afterTickToLabelConversion : function ( ) {
helpers . callCallback ( this . options . afterTickToLabelConversion , [ this ] ) ;
} ,
//
beforeCalculateTickRotation : function ( ) {
helpers . callCallback ( this . options . beforeCalculateTickRotation , [ this ] ) ;
} ,
calculateTickRotation : function ( ) {
var me = this ;
var context = me . ctx ;
var globalDefaults = Chart . defaults . global ;
var optionTicks = me . options . ticks ;
//Get the width of each grid by calculating the difference
//between x offsets between 0 and 1.
var tickFontSize = helpers . getValueOrDefault ( optionTicks . fontSize , globalDefaults . defaultFontSize ) ;
var tickFontStyle = helpers . getValueOrDefault ( optionTicks . fontStyle , globalDefaults . defaultFontStyle ) ;
var tickFontFamily = helpers . getValueOrDefault ( optionTicks . fontFamily , globalDefaults . defaultFontFamily ) ;
var tickLabelFont = helpers . fontString ( tickFontSize , tickFontStyle , tickFontFamily ) ;
context . font = tickLabelFont ;
var firstWidth = context . measureText ( me . ticks [ 0 ] ) . width ;
var lastWidth = context . measureText ( me . ticks [ me . ticks . length - 1 ] ) . width ;
var firstRotated ;
me . labelRotation = optionTicks . minRotation || 0 ;
me . paddingRight = 0 ;
me . paddingLeft = 0 ;
if ( me . options . display ) {
if ( me . isHorizontal ( ) ) {
me . paddingRight = lastWidth / 2 + 3 ;
me . paddingLeft = firstWidth / 2 + 3 ;
if ( ! me . longestTextCache ) {
me . longestTextCache = { } ;
}
var originalLabelWidth = helpers . longestText ( context , tickLabelFont , me . ticks , me . longestTextCache ) ;
var labelWidth = originalLabelWidth ;
var cosRotation ;
var sinRotation ;
// Allow 3 pixels x2 padding either side for label readability
// only the index matters for a dataset scale, but we want a consistent interface between scales
var tickWidth = me . getPixelForTick ( 1 ) - me . getPixelForTick ( 0 ) - 6 ;
//Max label rotation can be set or default to 90 - also act as a loop counter
while ( labelWidth > tickWidth && me . labelRotation < optionTicks . maxRotation ) {
cosRotation = Math . cos ( helpers . toRadians ( me . labelRotation ) ) ;
sinRotation = Math . sin ( helpers . toRadians ( me . labelRotation ) ) ;
firstRotated = cosRotation * firstWidth ;
// We're right aligning the text now.
if ( firstRotated + tickFontSize / 2 > me . yLabelWidth ) {
me . paddingLeft = firstRotated + tickFontSize / 2 ;
}
me . paddingRight = tickFontSize / 2 ;
if ( sinRotation * originalLabelWidth > me . maxHeight ) {
// go back one step
me . labelRotation -- ;
break ;
}
me . labelRotation ++ ;
labelWidth = cosRotation * originalLabelWidth ;
}
}
}
if ( me . margins ) {
me . paddingLeft = Math . max ( me . paddingLeft - me . margins . left , 0 ) ;
me . paddingRight = Math . max ( me . paddingRight - me . margins . right , 0 ) ;
}
} ,
afterCalculateTickRotation : function ( ) {
helpers . callCallback ( this . options . afterCalculateTickRotation , [ this ] ) ;
} ,
//
beforeFit : function ( ) {
helpers . callCallback ( this . options . beforeFit , [ this ] ) ;
} ,
fit : function ( ) {
var me = this ;
// Reset
var minSize = me . minSize = {
width : 0 ,
height : 0
} ;
var opts = me . options ;
var globalDefaults = Chart . defaults . global ;
var tickOpts = opts . ticks ;
var scaleLabelOpts = opts . scaleLabel ;
var gridLineOpts = opts . gridLines ;
var display = opts . display ;
var isHorizontal = me . isHorizontal ( ) ;
var tickFontSize = helpers . getValueOrDefault ( tickOpts . fontSize , globalDefaults . defaultFontSize ) ;
var tickFontStyle = helpers . getValueOrDefault ( tickOpts . fontStyle , globalDefaults . defaultFontStyle ) ;
var tickFontFamily = helpers . getValueOrDefault ( tickOpts . fontFamily , globalDefaults . defaultFontFamily ) ;
var tickLabelFont = helpers . fontString ( tickFontSize , tickFontStyle , tickFontFamily ) ;
var scaleLabelFontSize = helpers . getValueOrDefault ( scaleLabelOpts . fontSize , globalDefaults . defaultFontSize ) ;
var tickMarkLength = opts . gridLines . tickMarkLength ;
// Width
if ( isHorizontal ) {
// subtract the margins to line up with the chartArea if we are a full width scale
minSize . width = me . isFullWidth ( ) ? me . maxWidth - me . margins . left - me . margins . right : me . maxWidth ;
} else {
minSize . width = display && gridLineOpts . drawTicks ? tickMarkLength : 0 ;
}
// height
if ( isHorizontal ) {
minSize . height = display && gridLineOpts . drawTicks ? tickMarkLength : 0 ;
} else {
minSize . height = me . maxHeight ; // fill all the height
}
// Are we showing a title for the scale?
if ( scaleLabelOpts . display && display ) {
if ( isHorizontal ) {
minSize . height += ( scaleLabelFontSize * 1.5 ) ;
} else {
minSize . width += ( scaleLabelFontSize * 1.5 ) ;
}
}
if ( tickOpts . display && display ) {
// Don't bother fitting the ticks if we are not showing them
if ( ! me . longestTextCache ) {
me . longestTextCache = { } ;
}
var largestTextWidth = helpers . longestText ( me . ctx , tickLabelFont , me . ticks , me . longestTextCache ) ;
var tallestLabelHeightInLines = helpers . numberOfLabelLines ( me . ticks ) ;
var lineSpace = tickFontSize * 0.5 ;
if ( isHorizontal ) {
// A horizontal axis is more constrained by the height.
me . longestLabelWidth = largestTextWidth ;
// TODO - improve this calculation
var labelHeight = ( Math . sin ( helpers . toRadians ( me . labelRotation ) ) * me . longestLabelWidth ) + ( tickFontSize * tallestLabelHeightInLines ) + ( lineSpace * tallestLabelHeightInLines ) ;
minSize . height = Math . min ( me . maxHeight , minSize . height + labelHeight ) ;
me . ctx . font = tickLabelFont ;
var firstLabelWidth = me . ctx . measureText ( me . ticks [ 0 ] ) . width ;
var lastLabelWidth = me . ctx . measureText ( me . ticks [ me . ticks . length - 1 ] ) . width ;
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
// by the font height
var cosRotation = Math . cos ( helpers . toRadians ( me . labelRotation ) ) ;
var sinRotation = Math . sin ( helpers . toRadians ( me . labelRotation ) ) ;
me . paddingLeft = me . labelRotation !== 0 ? ( cosRotation * firstLabelWidth ) + 3 : firstLabelWidth / 2 + 3 ; // add 3 px to move away from canvas edges
me . paddingRight = me . labelRotation !== 0 ? ( sinRotation * ( tickFontSize / 2 ) ) + 3 : lastLabelWidth / 2 + 3 ; // when rotated
} else {
// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
var maxLabelWidth = me . maxWidth - minSize . width ;
// Account for padding
var mirror = tickOpts . mirror ;
if ( ! mirror ) {
largestTextWidth += me . options . ticks . padding ;
} else {
// If mirrored text is on the inside so don't expand
largestTextWidth = 0 ;
}
if ( largestTextWidth < maxLabelWidth ) {
// We don't need all the room
minSize . width += largestTextWidth ;
} else {
// Expand to max size
minSize . width = me . maxWidth ;
}
me . paddingTop = tickFontSize / 2 ;
me . paddingBottom = tickFontSize / 2 ;
}
}
if ( me . margins ) {
me . paddingLeft = Math . max ( me . paddingLeft - me . margins . left , 0 ) ;
me . paddingTop = Math . max ( me . paddingTop - me . margins . top , 0 ) ;
me . paddingRight = Math . max ( me . paddingRight - me . margins . right , 0 ) ;
me . paddingBottom = Math . max ( me . paddingBottom - me . margins . bottom , 0 ) ;
}
me . width = minSize . width ;
me . height = minSize . height ;
} ,
afterFit : function ( ) {
helpers . callCallback ( this . options . afterFit , [ this ] ) ;
} ,
// Shared Methods
isHorizontal : function ( ) {
return this . options . position === "top" || this . options . position === "bottom" ;
} ,
isFullWidth : function ( ) {
return ( this . options . fullWidth ) ;
} ,
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
getRightValue : function ( rawValue ) {
// Null and undefined values first
if ( rawValue === null || typeof ( rawValue ) === 'undefined' ) {
return NaN ;
}
// isNaN(object) returns true, so make sure NaN is checking for a number
if ( typeof ( rawValue ) === 'number' && isNaN ( rawValue ) ) {
return NaN ;
}
// If it is in fact an object, dive in one more level
if ( typeof ( rawValue ) === "object" ) {
if ( ( rawValue instanceof Date ) || ( rawValue . isValid ) ) {
return rawValue ;
} else {
return this . getRightValue ( this . isHorizontal ( ) ? rawValue . x : rawValue . y ) ;
}
}
// Value is good, return it
return rawValue ;
} ,
// Used to get the value to display in the tooltip for the data at the given index
// function getLabelForIndex(index, datasetIndex)
getLabelForIndex : helpers . noop ,
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue : helpers . noop ,
// Used to get the data value from a given pixel. This is the inverse of getPixelForValue
getValueForPixel : helpers . noop ,
// Used for tick location, should
getPixelForTick : function ( index , includeOffset ) {
var me = this ;
if ( me . isHorizontal ( ) ) {
var innerWidth = me . width - ( me . paddingLeft + me . paddingRight ) ;
var tickWidth = innerWidth / Math . max ( ( me . ticks . length - ( ( me . options . gridLines . offsetGridLines ) ? 0 : 1 ) ) , 1 ) ;
var pixel = ( tickWidth * index ) + me . paddingLeft ;
if ( includeOffset ) {
pixel += tickWidth / 2 ;
}
var finalVal = me . left + Math . round ( pixel ) ;
finalVal += me . isFullWidth ( ) ? me . margins . left : 0 ;
return finalVal ;
} else {
var innerHeight = me . height - ( me . paddingTop + me . paddingBottom ) ;
return me . top + ( index * ( innerHeight / ( me . ticks . length - 1 ) ) ) ;
}
} ,
// Utility for getting the pixel location of a percentage of scale
getPixelForDecimal : function ( decimal /*, includeOffset*/ ) {
var me = this ;
if ( me . isHorizontal ( ) ) {
var innerWidth = me . width - ( me . paddingLeft + me . paddingRight ) ;
var valueOffset = ( innerWidth * decimal ) + me . paddingLeft ;
var finalVal = me . left + Math . round ( valueOffset ) ;
finalVal += me . isFullWidth ( ) ? me . margins . left : 0 ;
return finalVal ;
} else {
return me . top + ( decimal * me . height ) ;
}
} ,
getBasePixel : function ( ) {
var me = this ;
var min = me . min ;
var max = me . max ;
return me . getPixelForValue (
me . beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0 ) ;
} ,
// Actualy draw the scale on the canvas
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
draw : function ( chartArea ) {
var me = this ;
var options = me . options ;
if ( ! options . display ) {
return ;
}
var context = me . ctx ;
var globalDefaults = Chart . defaults . global ;
var optionTicks = options . ticks ;
var gridLines = options . gridLines ;
var scaleLabel = options . scaleLabel ;
var isRotated = me . labelRotation !== 0 ;
var skipRatio ;
var useAutoskipper = optionTicks . autoSkip ;
var isHorizontal = me . isHorizontal ( ) ;
// figure out the maximum number of gridlines to show
var maxTicks ;
if ( optionTicks . maxTicksLimit ) {
maxTicks = optionTicks . maxTicksLimit ;
}
var tickFontColor = helpers . getValueOrDefault ( optionTicks . fontColor , globalDefaults . defaultFontColor ) ;
var tickFontSize = helpers . getValueOrDefault ( optionTicks . fontSize , globalDefaults . defaultFontSize ) ;
var tickFontStyle = helpers . getValueOrDefault ( optionTicks . fontStyle , globalDefaults . defaultFontStyle ) ;
var tickFontFamily = helpers . getValueOrDefault ( optionTicks . fontFamily , globalDefaults . defaultFontFamily ) ;
var tickLabelFont = helpers . fontString ( tickFontSize , tickFontStyle , tickFontFamily ) ;
var tl = gridLines . tickMarkLength ;
var borderDash = helpers . getValueOrDefault ( gridLines . borderDash , globalDefaults . borderDash ) ;
var borderDashOffset = helpers . getValueOrDefault ( gridLines . borderDashOffset , globalDefaults . borderDashOffset ) ;
var scaleLabelFontColor = helpers . getValueOrDefault ( scaleLabel . fontColor , globalDefaults . defaultFontColor ) ;
var scaleLabelFontSize = helpers . getValueOrDefault ( scaleLabel . fontSize , globalDefaults . defaultFontSize ) ;
var scaleLabelFontStyle = helpers . getValueOrDefault ( scaleLabel . fontStyle , globalDefaults . defaultFontStyle ) ;
var scaleLabelFontFamily = helpers . getValueOrDefault ( scaleLabel . fontFamily , globalDefaults . defaultFontFamily ) ;
var scaleLabelFont = helpers . fontString ( scaleLabelFontSize , scaleLabelFontStyle , scaleLabelFontFamily ) ;
var labelRotationRadians = helpers . toRadians ( me . labelRotation ) ;
var cosRotation = Math . cos ( labelRotationRadians ) ;
var longestRotatedLabel = me . longestLabelWidth * cosRotation ;
// Make sure we draw text in the correct color and font
context . fillStyle = tickFontColor ;
var itemsToDraw = [ ] ;
if ( isHorizontal ) {
skipRatio = false ;
// Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation
// See #2584
if ( isRotated ) {
longestRotatedLabel /= 2 ;
}
if ( ( longestRotatedLabel + optionTicks . autoSkipPadding ) * me . ticks . length > ( me . width - ( me . paddingLeft + me . paddingRight ) ) ) {
skipRatio = 1 + Math . floor ( ( ( longestRotatedLabel + optionTicks . autoSkipPadding ) * me . ticks . length ) / ( me . width - ( me . paddingLeft + me . paddingRight ) ) ) ;
}
// if they defined a max number of optionTicks,
// increase skipRatio until that number is met
if ( maxTicks && me . ticks . length > maxTicks ) {
while ( ! skipRatio || me . ticks . length / ( skipRatio || 1 ) > maxTicks ) {
if ( ! skipRatio ) {
skipRatio = 1 ;
}
skipRatio += 1 ;
}
}
if ( ! useAutoskipper ) {
skipRatio = false ;
}
}
var xTickStart = options . position === "right" ? me . left : me . right - tl ;
var xTickEnd = options . position === "right" ? me . left + tl : me . right ;
var yTickStart = options . position === "bottom" ? me . top : me . bottom - tl ;
var yTickEnd = options . position === "bottom" ? me . top + tl : me . bottom ;
helpers . each ( me . ticks , function ( label , index ) {
// If the callback returned a null or undefined value, do not draw this line
if ( label === undefined || label === null ) {
return ;
}
var isLastTick = me . ticks . length === index + 1 ;
// Since we always show the last tick,we need may need to hide the last shown one before
var shouldSkip = ( skipRatio > 1 && index % skipRatio > 0 ) || ( index % skipRatio === 0 && index + skipRatio >= me . ticks . length ) ;
if ( shouldSkip && ! isLastTick || ( label === undefined || label === null ) ) {
return ;
}
var lineWidth , lineColor ;
if ( index === ( typeof me . zeroLineIndex !== 'undefined' ? me . zeroLineIndex : 0 ) ) {
// Draw the first index specially
lineWidth = gridLines . zeroLineWidth ;
lineColor = gridLines . zeroLineColor ;
} else {
lineWidth = helpers . getValueAtIndexOrDefault ( gridLines . lineWidth , index ) ;
lineColor = helpers . getValueAtIndexOrDefault ( gridLines . color , index ) ;
}
// Common properties
var tx1 , ty1 , tx2 , ty2 , x1 , y1 , x2 , y2 , labelX , labelY ;
var textAlign , textBaseline = 'middle' ;
if ( isHorizontal ) {
if ( ! isRotated ) {
textBaseline = options . position === 'top' ? 'bottom' : 'top' ;
}
textAlign = isRotated ? 'right' : 'center' ;
var xLineValue = me . getPixelForTick ( index ) + helpers . aliasPixel ( lineWidth ) ; // xvalues for grid lines
labelX = me . getPixelForTick ( index , gridLines . offsetGridLines ) + optionTicks . labelOffset ; // x values for optionTicks (need to consider offsetLabel option)
labelY = ( isRotated ) ? me . top + 12 : options . position === 'top' ? me . bottom - tl : me . top + tl ;
tx1 = tx2 = x1 = x2 = xLineValue ;
ty1 = yTickStart ;
ty2 = yTickEnd ;
y1 = chartArea . top ;
y2 = chartArea . bottom ;
} else {
if ( options . position === 'left' ) {
if ( optionTicks . mirror ) {
labelX = me . right + optionTicks . padding ;
textAlign = 'left' ;
} else {
labelX = me . right - optionTicks . padding ;
textAlign = 'right' ;
}
} else {
// right side
if ( optionTicks . mirror ) {
labelX = me . left - optionTicks . padding ;
textAlign = 'right' ;
} else {
labelX = me . left + optionTicks . padding ;
textAlign = 'left' ;
}
}
var yLineValue = me . getPixelForTick ( index ) ; // xvalues for grid lines
yLineValue += helpers . aliasPixel ( lineWidth ) ;
labelY = me . getPixelForTick ( index , gridLines . offsetGridLines ) ;
tx1 = xTickStart ;
tx2 = xTickEnd ;
x1 = chartArea . left ;
x2 = chartArea . right ;
ty1 = ty2 = y1 = y2 = yLineValue ;
}
itemsToDraw . push ( {
tx1 : tx1 ,
ty1 : ty1 ,
tx2 : tx2 ,
ty2 : ty2 ,
x1 : x1 ,
y1 : y1 ,
x2 : x2 ,
y2 : y2 ,
labelX : labelX ,
labelY : labelY ,
glWidth : lineWidth ,
glColor : lineColor ,
glBorderDash : borderDash ,
glBorderDashOffset : borderDashOffset ,
rotation : - 1 * labelRotationRadians ,
label : label ,
textBaseline : textBaseline ,
textAlign : textAlign
} ) ;
} ) ;
// Draw all of the tick labels, tick marks, and grid lines at the correct places
helpers . each ( itemsToDraw , function ( itemToDraw ) {
if ( gridLines . display ) {
context . save ( ) ;
context . lineWidth = itemToDraw . glWidth ;
context . strokeStyle = itemToDraw . glColor ;
if ( context . setLineDash ) {
context . setLineDash ( itemToDraw . glBorderDash ) ;
context . lineDashOffset = itemToDraw . glBorderDashOffset ;
}
context . beginPath ( ) ;
if ( gridLines . drawTicks ) {
context . moveTo ( itemToDraw . tx1 , itemToDraw . ty1 ) ;
context . lineTo ( itemToDraw . tx2 , itemToDraw . ty2 ) ;
}
if ( gridLines . drawOnChartArea ) {
context . moveTo ( itemToDraw . x1 , itemToDraw . y1 ) ;
context . lineTo ( itemToDraw . x2 , itemToDraw . y2 ) ;
}
context . stroke ( ) ;
context . restore ( ) ;
}
if ( optionTicks . display ) {
context . save ( ) ;
context . translate ( itemToDraw . labelX , itemToDraw . labelY ) ;
context . rotate ( itemToDraw . rotation ) ;
context . font = tickLabelFont ;
context . textBaseline = itemToDraw . textBaseline ;
context . textAlign = itemToDraw . textAlign ;
var label = itemToDraw . label ;
if ( helpers . isArray ( label ) ) {
for ( var i = 0 , y = 0 ; i < label . length ; ++ i ) {
// We just make sure the multiline element is a string here..
context . fillText ( '' + label [ i ] , 0 , y ) ;
// apply same lineSpacing as calculated @ L#320
y += ( tickFontSize * 1.5 ) ;
}
} else {
context . fillText ( label , 0 , 0 ) ;
}
context . restore ( ) ;
}
} ) ;
if ( scaleLabel . display ) {
// Draw the scale label
var scaleLabelX ;
var scaleLabelY ;
var rotation = 0 ;
if ( isHorizontal ) {
scaleLabelX = me . left + ( ( me . right - me . left ) / 2 ) ; // midpoint of the width
scaleLabelY = options . position === 'bottom' ? me . bottom - ( scaleLabelFontSize / 2 ) : me . top + ( scaleLabelFontSize / 2 ) ;
} else {
var isLeft = options . position === 'left' ;
scaleLabelX = isLeft ? me . left + ( scaleLabelFontSize / 2 ) : me . right - ( scaleLabelFontSize / 2 ) ;
scaleLabelY = me . top + ( ( me . bottom - me . top ) / 2 ) ;
rotation = isLeft ? - 0.5 * Math . PI : 0.5 * Math . PI ;
}
context . save ( ) ;
context . translate ( scaleLabelX , scaleLabelY ) ;
context . rotate ( rotation ) ;
context . textAlign = 'center' ;
context . textBaseline = 'middle' ;
context . fillStyle = scaleLabelFontColor ; // render in correct colour
context . font = scaleLabelFont ;
context . fillText ( scaleLabel . labelString , 0 , 0 ) ;
context . restore ( ) ;
}
if ( gridLines . drawBorder ) {
// Draw the line at the edge of the axis
context . lineWidth = helpers . getValueAtIndexOrDefault ( gridLines . lineWidth , 0 ) ;
context . strokeStyle = helpers . getValueAtIndexOrDefault ( gridLines . color , 0 ) ;
var x1 = me . left ,
x2 = me . right ,
y1 = me . top ,
y2 = me . bottom ;
var aliasPixel = helpers . aliasPixel ( context . lineWidth ) ;
if ( isHorizontal ) {
y1 = y2 = options . position === 'top' ? me . bottom : me . top ;
y1 += aliasPixel ;
y2 += aliasPixel ;
} else {
x1 = x2 = options . position === 'left' ? me . right : me . left ;
x1 += aliasPixel ;
x2 += aliasPixel ;
}
context . beginPath ( ) ;
context . moveTo ( x1 , y1 ) ;
context . lineTo ( x2 , y2 ) ;
context . stroke ( ) ;
}
}
} ) ;
} ;
} , { } ] , 32 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale
constructors : { } ,
// Use a registration function so that we can move to an ES6 map when we no longer need to support
// old browsers
// Scale config defaults
defaults : { } ,
registerScaleType : function ( type , scaleConstructor , defaults ) {
this . constructors [ type ] = scaleConstructor ;
this . defaults [ type ] = helpers . clone ( defaults ) ;
} ,
getScaleConstructor : function ( type ) {
return this . constructors . hasOwnProperty ( type ) ? this . constructors [ type ] : undefined ;
} ,
getScaleDefaults : function ( type ) {
// Return the scale defaults merged with the global settings so that we always use the latest ones
return this . defaults . hasOwnProperty ( type ) ? helpers . scaleMerge ( Chart . defaults . scale , this . defaults [ type ] ) : { } ;
} ,
updateScaleDefaults : function ( type , additions ) {
var defaults = this . defaults ;
if ( defaults . hasOwnProperty ( type ) ) {
defaults [ type ] = helpers . extend ( defaults [ type ] , additions ) ;
}
} ,
addScalesToLayout : function ( chartInstance ) {
// Adds each scale to the chart.boxes array to be sized accordingly
helpers . each ( chartInstance . scales , function ( scale ) {
Chart . layoutService . addBox ( chartInstance , scale ) ;
} ) ;
}
} ;
} ;
} , { } ] , 33 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . global . title = {
display : false ,
position : 'top' ,
fullWidth : true , // marks that this box should take the full width of the canvas (pushing down other boxes)
fontStyle : 'bold' ,
padding : 10 ,
// actual title
text : ''
} ;
var noop = helpers . noop ;
Chart . Title = Chart . Element . extend ( {
initialize : function ( config ) {
var me = this ;
helpers . extend ( me , config ) ;
me . options = helpers . configMerge ( Chart . defaults . global . title , config . options ) ;
// Contains hit boxes for each dataset (in dataset order)
me . legendHitBoxes = [ ] ;
} ,
// These methods are ordered by lifecyle. Utilities then follow.
beforeUpdate : function ( ) {
var chartOpts = this . chart . options ;
if ( chartOpts && chartOpts . title ) {
this . options = helpers . configMerge ( Chart . defaults . global . title , chartOpts . title ) ;
}
} ,
update : function ( maxWidth , maxHeight , margins ) {
var me = this ;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me . beforeUpdate ( ) ;
// Absorb the master measurements
me . maxWidth = maxWidth ;
me . maxHeight = maxHeight ;
me . margins = margins ;
// Dimensions
me . beforeSetDimensions ( ) ;
me . setDimensions ( ) ;
me . afterSetDimensions ( ) ;
// Labels
me . beforeBuildLabels ( ) ;
me . buildLabels ( ) ;
me . afterBuildLabels ( ) ;
// Fit
me . beforeFit ( ) ;
me . fit ( ) ;
me . afterFit ( ) ;
//
me . afterUpdate ( ) ;
return me . minSize ;
} ,
afterUpdate : noop ,
//
beforeSetDimensions : noop ,
setDimensions : function ( ) {
var me = this ;
// Set the unconstrained dimension before label rotation
if ( me . isHorizontal ( ) ) {
// Reset position before calculating rotation
me . width = me . maxWidth ;
me . left = 0 ;
me . right = me . width ;
} else {
me . height = me . maxHeight ;
// Reset position before calculating rotation
me . top = 0 ;
me . bottom = me . height ;
}
// Reset padding
me . paddingLeft = 0 ;
me . paddingTop = 0 ;
me . paddingRight = 0 ;
me . paddingBottom = 0 ;
// Reset minSize
me . minSize = {
width : 0 ,
height : 0
} ;
} ,
afterSetDimensions : noop ,
//
beforeBuildLabels : noop ,
buildLabels : noop ,
afterBuildLabels : noop ,
//
beforeFit : noop ,
fit : function ( ) {
var me = this ,
valueOrDefault = helpers . getValueOrDefault ,
opts = me . options ,
globalDefaults = Chart . defaults . global ,
display = opts . display ,
fontSize = valueOrDefault ( opts . fontSize , globalDefaults . defaultFontSize ) ,
minSize = me . minSize ;
if ( me . isHorizontal ( ) ) {
minSize . width = me . maxWidth ; // fill all the width
minSize . height = display ? fontSize + ( opts . padding * 2 ) : 0 ;
} else {
minSize . width = display ? fontSize + ( opts . padding * 2 ) : 0 ;
minSize . height = me . maxHeight ; // fill all the height
}
me . width = minSize . width ;
me . height = minSize . height ;
} ,
afterFit : noop ,
// Shared Methods
isHorizontal : function ( ) {
var pos = this . options . position ;
return pos === "top" || pos === "bottom" ;
} ,
// Actualy draw the title block on the canvas
draw : function ( ) {
var me = this ,
ctx = me . ctx ,
valueOrDefault = helpers . getValueOrDefault ,
opts = me . options ,
globalDefaults = Chart . defaults . global ;
if ( opts . display ) {
var fontSize = valueOrDefault ( opts . fontSize , globalDefaults . defaultFontSize ) ,
fontStyle = valueOrDefault ( opts . fontStyle , globalDefaults . defaultFontStyle ) ,
fontFamily = valueOrDefault ( opts . fontFamily , globalDefaults . defaultFontFamily ) ,
titleFont = helpers . fontString ( fontSize , fontStyle , fontFamily ) ,
rotation = 0 ,
titleX ,
titleY ,
top = me . top ,
left = me . left ,
bottom = me . bottom ,
right = me . right ;
ctx . fillStyle = valueOrDefault ( opts . fontColor , globalDefaults . defaultFontColor ) ; // render in correct colour
ctx . font = titleFont ;
// Horizontal
if ( me . isHorizontal ( ) ) {
titleX = left + ( ( right - left ) / 2 ) ; // midpoint of the width
titleY = top + ( ( bottom - top ) / 2 ) ; // midpoint of the height
} else {
titleX = opts . position === 'left' ? left + ( fontSize / 2 ) : right - ( fontSize / 2 ) ;
titleY = top + ( ( bottom - top ) / 2 ) ;
rotation = Math . PI * ( opts . position === 'left' ? - 0.5 : 0.5 ) ;
}
ctx . save ( ) ;
ctx . translate ( titleX , titleY ) ;
ctx . rotate ( rotation ) ;
ctx . textAlign = 'center' ;
ctx . textBaseline = 'middle' ;
ctx . fillText ( opts . text , 0 , 0 ) ;
ctx . restore ( ) ;
}
}
} ) ;
// Register the title plugin
Chart . plugins . register ( {
beforeInit : function ( chartInstance ) {
var opts = chartInstance . options ;
var titleOpts = opts . title ;
if ( titleOpts ) {
chartInstance . titleBlock = new Chart . Title ( {
ctx : chartInstance . chart . ctx ,
options : titleOpts ,
chart : chartInstance
} ) ;
Chart . layoutService . addBox ( chartInstance , chartInstance . titleBlock ) ;
}
}
} ) ;
} ;
} , { } ] , 34 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . global . tooltips = {
enabled : true ,
custom : null ,
mode : 'single' ,
backgroundColor : "rgba(0,0,0,0.8)" ,
titleFontStyle : "bold" ,
titleSpacing : 2 ,
titleMarginBottom : 6 ,
titleFontColor : "#fff" ,
titleAlign : "left" ,
bodySpacing : 2 ,
bodyFontColor : "#fff" ,
bodyAlign : "left" ,
footerFontStyle : "bold" ,
footerSpacing : 2 ,
footerMarginTop : 6 ,
footerFontColor : "#fff" ,
footerAlign : "left" ,
yPadding : 6 ,
xPadding : 6 ,
yAlign : 'center' ,
xAlign : 'center' ,
caretSize : 5 ,
cornerRadius : 6 ,
multiKeyBackground : '#fff' ,
callbacks : {
// Args are: (tooltipItems, data)
beforeTitle : helpers . noop ,
title : function ( tooltipItems , data ) {
// Pick first xLabel for now
var title = '' ;
var labels = data . labels ;
var labelCount = labels ? labels . length : 0 ;
if ( tooltipItems . length > 0 ) {
var item = tooltipItems [ 0 ] ;
if ( item . xLabel ) {
title = item . xLabel ;
} else if ( labelCount > 0 && item . index < labelCount ) {
title = labels [ item . index ] ;
}
}
return title ;
} ,
afterTitle : helpers . noop ,
// Args are: (tooltipItems, data)
beforeBody : helpers . noop ,
// Args are: (tooltipItem, data)
beforeLabel : helpers . noop ,
label : function ( tooltipItem , data ) {
var datasetLabel = data . datasets [ tooltipItem . datasetIndex ] . label || '' ;
return datasetLabel + ': ' + tooltipItem . yLabel ;
} ,
labelColor : function ( tooltipItem , chartInstance ) {
var meta = chartInstance . getDatasetMeta ( tooltipItem . datasetIndex ) ;
var activeElement = meta . data [ tooltipItem . index ] ;
var view = activeElement . _view ;
return {
borderColor : view . borderColor ,
backgroundColor : view . backgroundColor
} ;
} ,
afterLabel : helpers . noop ,
// Args are: (tooltipItems, data)
afterBody : helpers . noop ,
// Args are: (tooltipItems, data)
beforeFooter : helpers . noop ,
footer : helpers . noop ,
afterFooter : helpers . noop
}
} ;
// Helper to push or concat based on if the 2nd parameter is an array or not
function pushOrConcat ( base , toPush ) {
if ( toPush ) {
if ( helpers . isArray ( toPush ) ) {
//base = base.concat(toPush);
Array . prototype . push . apply ( base , toPush ) ;
} else {
base . push ( toPush ) ;
}
}
return base ;
}
function getAveragePosition ( elements ) {
if ( ! elements . length ) {
return false ;
}
var i , len ;
var xPositions = [ ] ;
var yPositions = [ ] ;
for ( i = 0 , len = elements . length ; i < len ; ++ i ) {
var el = elements [ i ] ;
if ( el && el . hasValue ( ) ) {
var pos = el . tooltipPosition ( ) ;
xPositions . push ( pos . x ) ;
yPositions . push ( pos . y ) ;
}
}
var x = 0 ,
y = 0 ;
for ( i = 0 ; i < xPositions . length ; ++ i ) {
if ( xPositions [ i ] ) {
x += xPositions [ i ] ;
y += yPositions [ i ] ;
}
}
return {
x : Math . round ( x / xPositions . length ) ,
y : Math . round ( y / xPositions . length )
} ;
}
// Private helper to create a tooltip iteam model
// @param element : the chart element (point, arc, bar) to create the tooltip item for
// @return : new tooltip item
function createTooltipItem ( element ) {
var xScale = element . _xScale ;
var yScale = element . _yScale || element . _scale ; // handle radar || polarArea charts
var index = element . _index ,
datasetIndex = element . _datasetIndex ;
return {
xLabel : xScale ? xScale . getLabelForIndex ( index , datasetIndex ) : '' ,
yLabel : yScale ? yScale . getLabelForIndex ( index , datasetIndex ) : '' ,
index : index ,
datasetIndex : datasetIndex
} ;
}
Chart . Tooltip = Chart . Element . extend ( {
initialize : function ( ) {
var me = this ;
var globalDefaults = Chart . defaults . global ;
var tooltipOpts = me . _options ;
var getValueOrDefault = helpers . getValueOrDefault ;
helpers . extend ( me , {
_model : {
// Positioning
xPadding : tooltipOpts . xPadding ,
yPadding : tooltipOpts . yPadding ,
xAlign : tooltipOpts . xAlign ,
yAlign : tooltipOpts . yAlign ,
// Body
bodyFontColor : tooltipOpts . bodyFontColor ,
_bodyFontFamily : getValueOrDefault ( tooltipOpts . bodyFontFamily , globalDefaults . defaultFontFamily ) ,
_bodyFontStyle : getValueOrDefault ( tooltipOpts . bodyFontStyle , globalDefaults . defaultFontStyle ) ,
_bodyAlign : tooltipOpts . bodyAlign ,
bodyFontSize : getValueOrDefault ( tooltipOpts . bodyFontSize , globalDefaults . defaultFontSize ) ,
bodySpacing : tooltipOpts . bodySpacing ,
// Title
titleFontColor : tooltipOpts . titleFontColor ,
_titleFontFamily : getValueOrDefault ( tooltipOpts . titleFontFamily , globalDefaults . defaultFontFamily ) ,
_titleFontStyle : getValueOrDefault ( tooltipOpts . titleFontStyle , globalDefaults . defaultFontStyle ) ,
titleFontSize : getValueOrDefault ( tooltipOpts . titleFontSize , globalDefaults . defaultFontSize ) ,
_titleAlign : tooltipOpts . titleAlign ,
titleSpacing : tooltipOpts . titleSpacing ,
titleMarginBottom : tooltipOpts . titleMarginBottom ,
// Footer
footerFontColor : tooltipOpts . footerFontColor ,
_footerFontFamily : getValueOrDefault ( tooltipOpts . footerFontFamily , globalDefaults . defaultFontFamily ) ,
_footerFontStyle : getValueOrDefault ( tooltipOpts . footerFontStyle , globalDefaults . defaultFontStyle ) ,
footerFontSize : getValueOrDefault ( tooltipOpts . footerFontSize , globalDefaults . defaultFontSize ) ,
_footerAlign : tooltipOpts . footerAlign ,
footerSpacing : tooltipOpts . footerSpacing ,
footerMarginTop : tooltipOpts . footerMarginTop ,
// Appearance
caretSize : tooltipOpts . caretSize ,
cornerRadius : tooltipOpts . cornerRadius ,
backgroundColor : tooltipOpts . backgroundColor ,
opacity : 0 ,
legendColorBackground : tooltipOpts . multiKeyBackground
}
} ) ;
} ,
// Get the title
// Args are: (tooltipItem, data)
getTitle : function ( ) {
var me = this ;
var opts = me . _options ;
var callbacks = opts . callbacks ;
var beforeTitle = callbacks . beforeTitle . apply ( me , arguments ) ,
title = callbacks . title . apply ( me , arguments ) ,
afterTitle = callbacks . afterTitle . apply ( me , arguments ) ;
var lines = [ ] ;
lines = pushOrConcat ( lines , beforeTitle ) ;
lines = pushOrConcat ( lines , title ) ;
lines = pushOrConcat ( lines , afterTitle ) ;
return lines ;
} ,
// Args are: (tooltipItem, data)
getBeforeBody : function ( ) {
var lines = this . _options . callbacks . beforeBody . apply ( this , arguments ) ;
return helpers . isArray ( lines ) ? lines : lines !== undefined ? [ lines ] : [ ] ;
} ,
// Args are: (tooltipItem, data)
getBody : function ( tooltipItems , data ) {
var me = this ;
var callbacks = me . _options . callbacks ;
var bodyItems = [ ] ;
helpers . each ( tooltipItems , function ( tooltipItem ) {
var bodyItem = {
before : [ ] ,
lines : [ ] ,
after : [ ]
} ;
pushOrConcat ( bodyItem . before , callbacks . beforeLabel . call ( me , tooltipItem , data ) ) ;
pushOrConcat ( bodyItem . lines , callbacks . label . call ( me , tooltipItem , data ) ) ;
pushOrConcat ( bodyItem . after , callbacks . afterLabel . call ( me , tooltipItem , data ) ) ;
bodyItems . push ( bodyItem ) ;
} ) ;
return bodyItems ;
} ,
// Args are: (tooltipItem, data)
getAfterBody : function ( ) {
var lines = this . _options . callbacks . afterBody . apply ( this , arguments ) ;
return helpers . isArray ( lines ) ? lines : lines !== undefined ? [ lines ] : [ ] ;
} ,
// Get the footer and beforeFooter and afterFooter lines
// Args are: (tooltipItem, data)
getFooter : function ( ) {
var me = this ;
var callbacks = me . _options . callbacks ;
var beforeFooter = callbacks . beforeFooter . apply ( me , arguments ) ;
var footer = callbacks . footer . apply ( me , arguments ) ;
var afterFooter = callbacks . afterFooter . apply ( me , arguments ) ;
var lines = [ ] ;
lines = pushOrConcat ( lines , beforeFooter ) ;
lines = pushOrConcat ( lines , footer ) ;
lines = pushOrConcat ( lines , afterFooter ) ;
return lines ;
} ,
update : function ( changed ) {
var me = this ;
var opts = me . _options ;
var model = me . _model ;
var active = me . _active ;
var data = me . _data ;
var chartInstance = me . _chartInstance ;
var i , len ;
if ( active . length ) {
model . opacity = 1 ;
var labelColors = [ ] ,
tooltipPosition = getAveragePosition ( active ) ;
var tooltipItems = [ ] ;
for ( i = 0 , len = active . length ; i < len ; ++ i ) {
tooltipItems . push ( createTooltipItem ( active [ i ] ) ) ;
}
// If the user provided a sorting function, use it to modify the tooltip items
if ( opts . itemSort ) {
tooltipItems = tooltipItems . sort ( function ( a , b ) {
return opts . itemSort ( a , b , data ) ;
} ) ;
}
// If there is more than one item, show color items
if ( active . length > 1 ) {
helpers . each ( tooltipItems , function ( tooltipItem ) {
labelColors . push ( opts . callbacks . labelColor . call ( me , tooltipItem , chartInstance ) ) ;
} ) ;
}
// Build the Text Lines
helpers . extend ( model , {
title : me . getTitle ( tooltipItems , data ) ,
beforeBody : me . getBeforeBody ( tooltipItems , data ) ,
body : me . getBody ( tooltipItems , data ) ,
afterBody : me . getAfterBody ( tooltipItems , data ) ,
footer : me . getFooter ( tooltipItems , data ) ,
x : Math . round ( tooltipPosition . x ) ,
y : Math . round ( tooltipPosition . y ) ,
caretPadding : helpers . getValueOrDefault ( tooltipPosition . padding , 2 ) ,
labelColors : labelColors
} ) ;
// We need to determine alignment of
var tooltipSize = me . getTooltipSize ( model ) ;
me . determineAlignment ( tooltipSize ) ; // Smart Tooltip placement to stay on the canvas
helpers . extend ( model , me . getBackgroundPoint ( model , tooltipSize ) ) ;
} else {
me . _model . opacity = 0 ;
}
if ( changed && opts . custom ) {
opts . custom . call ( me , model ) ;
}
return me ;
} ,
getTooltipSize : function ( vm ) {
var ctx = this . _chart . ctx ;
var size = {
height : vm . yPadding * 2 , // Tooltip Padding
width : 0
} ;
// Count of all lines in the body
var body = vm . body ;
var combinedBodyLength = body . reduce ( function ( count , bodyItem ) {
return count + bodyItem . before . length + bodyItem . lines . length + bodyItem . after . length ;
} , 0 ) ;
combinedBodyLength += vm . beforeBody . length + vm . afterBody . length ;
var titleLineCount = vm . title . length ;
var footerLineCount = vm . footer . length ;
var titleFontSize = vm . titleFontSize ,
bodyFontSize = vm . bodyFontSize ,
footerFontSize = vm . footerFontSize ;
size . height += titleLineCount * titleFontSize ; // Title Lines
size . height += ( titleLineCount - 1 ) * vm . titleSpacing ; // Title Line Spacing
size . height += titleLineCount ? vm . titleMarginBottom : 0 ; // Title's bottom Margin
size . height += combinedBodyLength * bodyFontSize ; // Body Lines
size . height += combinedBodyLength ? ( combinedBodyLength - 1 ) * vm . bodySpacing : 0 ; // Body Line Spacing
size . height += footerLineCount ? vm . footerMarginTop : 0 ; // Footer Margin
size . height += footerLineCount * ( footerFontSize ) ; // Footer Lines
size . height += footerLineCount ? ( footerLineCount - 1 ) * vm . footerSpacing : 0 ; // Footer Line Spacing
// Title width
var widthPadding = 0 ;
var maxLineWidth = function ( line ) {
size . width = Math . max ( size . width , ctx . measureText ( line ) . width + widthPadding ) ;
} ;
ctx . font = helpers . fontString ( titleFontSize , vm . _titleFontStyle , vm . _titleFontFamily ) ;
helpers . each ( vm . title , maxLineWidth ) ;
// Body width
ctx . font = helpers . fontString ( bodyFontSize , vm . _bodyFontStyle , vm . _bodyFontFamily ) ;
helpers . each ( vm . beforeBody . concat ( vm . afterBody ) , maxLineWidth ) ;
// Body lines may include some extra width due to the color box
widthPadding = body . length > 1 ? ( bodyFontSize + 2 ) : 0 ;
helpers . each ( body , function ( bodyItem ) {
helpers . each ( bodyItem . before , maxLineWidth ) ;
helpers . each ( bodyItem . lines , maxLineWidth ) ;
helpers . each ( bodyItem . after , maxLineWidth ) ;
} ) ;
// Reset back to 0
widthPadding = 0 ;
// Footer width
ctx . font = helpers . fontString ( footerFontSize , vm . _footerFontStyle , vm . _footerFontFamily ) ;
helpers . each ( vm . footer , maxLineWidth ) ;
// Add padding
size . width += 2 * vm . xPadding ;
return size ;
} ,
determineAlignment : function ( size ) {
var me = this ;
var model = me . _model ;
var chart = me . _chart ;
var chartArea = me . _chartInstance . chartArea ;
if ( model . y < size . height ) {
model . yAlign = 'top' ;
} else if ( model . y > ( chart . height - size . height ) ) {
model . yAlign = 'bottom' ;
}
var lf , rf ; // functions to determine left, right alignment
var olf , orf ; // functions to determine if left/right alignment causes tooltip to go outside chart
var yf ; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = ( chartArea . left + chartArea . right ) / 2 ;
var midY = ( chartArea . top + chartArea . bottom ) / 2 ;
if ( model . yAlign === 'center' ) {
lf = function ( x ) {
return x <= midX ;
} ;
rf = function ( x ) {
return x > midX ;
} ;
} else {
lf = function ( x ) {
return x <= ( size . width / 2 ) ;
} ;
rf = function ( x ) {
return x >= ( chart . width - ( size . width / 2 ) ) ;
} ;
}
olf = function ( x ) {
return x + size . width > chart . width ;
} ;
orf = function ( x ) {
return x - size . width < 0 ;
} ;
yf = function ( y ) {
return y <= midY ? 'top' : 'bottom' ;
} ;
if ( lf ( model . x ) ) {
model . xAlign = 'left' ;
// Is tooltip too wide and goes over the right side of the chart.?
if ( olf ( model . x ) ) {
model . xAlign = 'center' ;
model . yAlign = yf ( model . y ) ;
}
} else if ( rf ( model . x ) ) {
model . xAlign = 'right' ;
// Is tooltip too wide and goes outside left edge of canvas?
if ( orf ( model . x ) ) {
model . xAlign = 'center' ;
model . yAlign = yf ( model . y ) ;
}
}
} ,
getBackgroundPoint : function ( vm , size ) {
// Background Position
var pt = {
x : vm . x ,
y : vm . y
} ;
var caretSize = vm . caretSize ,
caretPadding = vm . caretPadding ,
cornerRadius = vm . cornerRadius ,
xAlign = vm . xAlign ,
yAlign = vm . yAlign ,
paddingAndSize = caretSize + caretPadding ,
radiusAndPadding = cornerRadius + caretPadding ;
if ( xAlign === 'right' ) {
pt . x -= size . width ;
} else if ( xAlign === 'center' ) {
pt . x -= ( size . width / 2 ) ;
}
if ( yAlign === 'top' ) {
pt . y += paddingAndSize ;
} else if ( yAlign === 'bottom' ) {
pt . y -= size . height + paddingAndSize ;
} else {
pt . y -= ( size . height / 2 ) ;
}
if ( yAlign === 'center' ) {
if ( xAlign === 'left' ) {
pt . x += paddingAndSize ;
} else if ( xAlign === 'right' ) {
pt . x -= paddingAndSize ;
}
} else {
if ( xAlign === 'left' ) {
pt . x -= radiusAndPadding ;
} else if ( xAlign === 'right' ) {
pt . x += radiusAndPadding ;
}
}
return pt ;
} ,
drawCaret : function ( tooltipPoint , size , opacity ) {
var vm = this . _view ;
var ctx = this . _chart . ctx ;
var x1 , x2 , x3 ;
var y1 , y2 , y3 ;
var caretSize = vm . caretSize ;
var cornerRadius = vm . cornerRadius ;
var xAlign = vm . xAlign ,
yAlign = vm . yAlign ;
var ptX = tooltipPoint . x ,
ptY = tooltipPoint . y ;
var width = size . width ,
height = size . height ;
if ( yAlign === 'center' ) {
// Left or right side
if ( xAlign === 'left' ) {
x1 = ptX ;
x2 = x1 - caretSize ;
x3 = x1 ;
} else {
x1 = ptX + width ;
x2 = x1 + caretSize ;
x3 = x1 ;
}
y2 = ptY + ( height / 2 ) ;
y1 = y2 - caretSize ;
y3 = y2 + caretSize ;
} else {
if ( xAlign === 'left' ) {
x1 = ptX + cornerRadius ;
x2 = x1 + caretSize ;
x3 = x2 + caretSize ;
} else if ( xAlign === 'right' ) {
x1 = ptX + width - cornerRadius ;
x2 = x1 - caretSize ;
x3 = x2 - caretSize ;
} else {
x2 = ptX + ( width / 2 ) ;
x1 = x2 - caretSize ;
x3 = x2 + caretSize ;
}
if ( yAlign === 'top' ) {
y1 = ptY ;
y2 = y1 - caretSize ;
y3 = y1 ;
} else {
y1 = ptY + height ;
y2 = y1 + caretSize ;
y3 = y1 ;
}
}
var bgColor = helpers . color ( vm . backgroundColor ) ;
ctx . fillStyle = bgColor . alpha ( opacity * bgColor . alpha ( ) ) . rgbString ( ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( x1 , y1 ) ;
ctx . lineTo ( x2 , y2 ) ;
ctx . lineTo ( x3 , y3 ) ;
ctx . closePath ( ) ;
ctx . fill ( ) ;
} ,
drawTitle : function ( pt , vm , ctx , opacity ) {
var title = vm . title ;
if ( title . length ) {
ctx . textAlign = vm . _titleAlign ;
ctx . textBaseline = "top" ;
var titleFontSize = vm . titleFontSize ,
titleSpacing = vm . titleSpacing ;
var titleFontColor = helpers . color ( vm . titleFontColor ) ;
ctx . fillStyle = titleFontColor . alpha ( opacity * titleFontColor . alpha ( ) ) . rgbString ( ) ;
ctx . font = helpers . fontString ( titleFontSize , vm . _titleFontStyle , vm . _titleFontFamily ) ;
var i , len ;
for ( i = 0 , len = title . length ; i < len ; ++ i ) {
ctx . fillText ( title [ i ] , pt . x , pt . y ) ;
pt . y += titleFontSize + titleSpacing ; // Line Height and spacing
if ( i + 1 === title . length ) {
pt . y += vm . titleMarginBottom - titleSpacing ; // If Last, add margin, remove spacing
}
}
}
} ,
drawBody : function ( pt , vm , ctx , opacity ) {
var bodyFontSize = vm . bodyFontSize ;
var bodySpacing = vm . bodySpacing ;
var body = vm . body ;
ctx . textAlign = vm . _bodyAlign ;
ctx . textBaseline = "top" ;
var bodyFontColor = helpers . color ( vm . bodyFontColor ) ;
var textColor = bodyFontColor . alpha ( opacity * bodyFontColor . alpha ( ) ) . rgbString ( ) ;
ctx . fillStyle = textColor ;
ctx . font = helpers . fontString ( bodyFontSize , vm . _bodyFontStyle , vm . _bodyFontFamily ) ;
// Before Body
var xLinePadding = 0 ;
var fillLineOfText = function ( line ) {
ctx . fillText ( line , pt . x + xLinePadding , pt . y ) ;
pt . y += bodyFontSize + bodySpacing ;
} ;
// Before body lines
helpers . each ( vm . beforeBody , fillLineOfText ) ;
var drawColorBoxes = body . length > 1 ;
xLinePadding = drawColorBoxes ? ( bodyFontSize + 2 ) : 0 ;
// Draw body lines now
helpers . each ( body , function ( bodyItem , i ) {
helpers . each ( bodyItem . before , fillLineOfText ) ;
helpers . each ( bodyItem . lines , function ( line ) {
// Draw Legend-like boxes if needed
if ( drawColorBoxes ) {
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx . fillStyle = helpers . color ( vm . legendColorBackground ) . alpha ( opacity ) . rgbaString ( ) ;
ctx . fillRect ( pt . x , pt . y , bodyFontSize , bodyFontSize ) ;
// Border
ctx . strokeStyle = helpers . color ( vm . labelColors [ i ] . borderColor ) . alpha ( opacity ) . rgbaString ( ) ;
ctx . strokeRect ( pt . x , pt . y , bodyFontSize , bodyFontSize ) ;
// Inner square
ctx . fillStyle = helpers . color ( vm . labelColors [ i ] . backgroundColor ) . alpha ( opacity ) . rgbaString ( ) ;
ctx . fillRect ( pt . x + 1 , pt . y + 1 , bodyFontSize - 2 , bodyFontSize - 2 ) ;
ctx . fillStyle = textColor ;
}
fillLineOfText ( line ) ;
} ) ;
helpers . each ( bodyItem . after , fillLineOfText ) ;
} ) ;
// Reset back to 0 for after body
xLinePadding = 0 ;
// After body lines
helpers . each ( vm . afterBody , fillLineOfText ) ;
pt . y -= bodySpacing ; // Remove last body spacing
} ,
drawFooter : function ( pt , vm , ctx , opacity ) {
var footer = vm . footer ;
if ( footer . length ) {
pt . y += vm . footerMarginTop ;
ctx . textAlign = vm . _footerAlign ;
ctx . textBaseline = "top" ;
var footerFontColor = helpers . color ( vm . footerFontColor ) ;
ctx . fillStyle = footerFontColor . alpha ( opacity * footerFontColor . alpha ( ) ) . rgbString ( ) ;
ctx . font = helpers . fontString ( vm . footerFontSize , vm . _footerFontStyle , vm . _footerFontFamily ) ;
helpers . each ( footer , function ( line ) {
ctx . fillText ( line , pt . x , pt . y ) ;
pt . y += vm . footerFontSize + vm . footerSpacing ;
} ) ;
}
} ,
draw : function ( ) {
var ctx = this . _chart . ctx ;
var vm = this . _view ;
if ( vm . opacity === 0 ) {
return ;
}
var tooltipSize = this . getTooltipSize ( vm ) ;
var pt = {
x : vm . x ,
y : vm . y
} ;
// IE11/Edge does not like very small opacities, so snap to 0
var opacity = Math . abs ( vm . opacity < 1e-3 ) ? 0 : vm . opacity ;
if ( this . _options . enabled ) {
// Draw Background
var bgColor = helpers . color ( vm . backgroundColor ) ;
ctx . fillStyle = bgColor . alpha ( opacity * bgColor . alpha ( ) ) . rgbString ( ) ;
helpers . drawRoundedRectangle ( ctx , pt . x , pt . y , tooltipSize . width , tooltipSize . height , vm . cornerRadius ) ;
ctx . fill ( ) ;
// Draw Caret
this . drawCaret ( pt , tooltipSize , opacity ) ;
// Draw Title, Body, and Footer
pt . x += vm . xPadding ;
pt . y += vm . yPadding ;
// Titles
this . drawTitle ( pt , vm , ctx , opacity ) ;
// Body
this . drawBody ( pt , vm , ctx , opacity ) ;
// Footer
this . drawFooter ( pt , vm , ctx , opacity ) ;
}
}
} ) ;
} ;
} , { } ] , 35 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ,
globalOpts = Chart . defaults . global ;
globalOpts . elements . arc = {
backgroundColor : globalOpts . defaultColor ,
borderColor : "#fff" ,
borderWidth : 2
} ;
Chart . elements . Arc = Chart . Element . extend ( {
inLabelRange : function ( mouseX ) {
var vm = this . _view ;
if ( vm ) {
return ( Math . pow ( mouseX - vm . x , 2 ) < Math . pow ( vm . radius + vm . hoverRadius , 2 ) ) ;
} else {
return false ;
}
} ,
inRange : function ( chartX , chartY ) {
var vm = this . _view ;
if ( vm ) {
var pointRelativePosition = helpers . getAngleFromPoint ( vm , {
x : chartX ,
y : chartY
} ) ,
angle = pointRelativePosition . angle ,
distance = pointRelativePosition . distance ;
//Sanitise angle range
var startAngle = vm . startAngle ;
var endAngle = vm . endAngle ;
while ( endAngle < startAngle ) {
endAngle += 2.0 * Math . PI ;
}
while ( angle > endAngle ) {
angle -= 2.0 * Math . PI ;
}
while ( angle < startAngle ) {
angle += 2.0 * Math . PI ;
}
//Check if within the range of the open/close angle
var betweenAngles = ( angle >= startAngle && angle <= endAngle ) ,
withinRadius = ( distance >= vm . innerRadius && distance <= vm . outerRadius ) ;
return ( betweenAngles && withinRadius ) ;
} else {
return false ;
}
} ,
tooltipPosition : function ( ) {
var vm = this . _view ;
var centreAngle = vm . startAngle + ( ( vm . endAngle - vm . startAngle ) / 2 ) ,
rangeFromCentre = ( vm . outerRadius - vm . innerRadius ) / 2 + vm . innerRadius ;
return {
x : vm . x + ( Math . cos ( centreAngle ) * rangeFromCentre ) ,
y : vm . y + ( Math . sin ( centreAngle ) * rangeFromCentre )
} ;
} ,
draw : function ( ) {
var ctx = this . _chart . ctx ,
vm = this . _view ,
sA = vm . startAngle ,
eA = vm . endAngle ;
ctx . beginPath ( ) ;
ctx . arc ( vm . x , vm . y , vm . outerRadius , sA , eA ) ;
ctx . arc ( vm . x , vm . y , vm . innerRadius , eA , sA , true ) ;
ctx . closePath ( ) ;
ctx . strokeStyle = vm . borderColor ;
ctx . lineWidth = vm . borderWidth ;
ctx . fillStyle = vm . backgroundColor ;
ctx . fill ( ) ;
ctx . lineJoin = 'bevel' ;
if ( vm . borderWidth ) {
ctx . stroke ( ) ;
}
}
} ) ;
} ;
} , { } ] , 36 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var globalDefaults = Chart . defaults . global ;
Chart . defaults . global . elements . line = {
tension : 0.4 ,
backgroundColor : globalDefaults . defaultColor ,
borderWidth : 3 ,
borderColor : globalDefaults . defaultColor ,
borderCapStyle : 'butt' ,
borderDash : [ ] ,
borderDashOffset : 0.0 ,
borderJoinStyle : 'miter' ,
capBezierPoints : true ,
fill : true // do we fill in the area between the line and its base axis
} ;
Chart . elements . Line = Chart . Element . extend ( {
draw : function ( ) {
var me = this ;
var vm = me . _view ;
var spanGaps = vm . spanGaps ;
var scaleZero = vm . scaleZero ;
var loop = me . _loop ;
var ctx = me . _chart . ctx ;
ctx . save ( ) ;
// Helper function to draw a line to a point
function lineToPoint ( previousPoint , point ) {
var vm = point . _view ;
if ( point . _view . steppedLine === true ) {
ctx . lineTo ( point . _view . x , previousPoint . _view . y ) ;
ctx . lineTo ( point . _view . x , point . _view . y ) ;
} else if ( point . _view . tension === 0 ) {
ctx . lineTo ( vm . x , vm . y ) ;
} else {
ctx . bezierCurveTo (
previousPoint . _view . controlPointNextX ,
previousPoint . _view . controlPointNextY ,
vm . controlPointPreviousX ,
vm . controlPointPreviousY ,
vm . x ,
vm . y
) ;
}
}
var points = me . _children . slice ( ) ; // clone array
var lastDrawnIndex = - 1 ;
// If we are looping, adding the first point again
if ( loop && points . length ) {
points . push ( points [ 0 ] ) ;
}
var index , current , previous , currentVM ;
// Fill Line
if ( points . length && vm . fill ) {
ctx . beginPath ( ) ;
for ( index = 0 ; index < points . length ; ++ index ) {
current = points [ index ] ;
previous = helpers . previousItem ( points , index ) ;
currentVM = current . _view ;
// First point moves to it's starting position no matter what
if ( index === 0 ) {
if ( loop ) {
ctx . moveTo ( scaleZero . x , scaleZero . y ) ;
} else {
ctx . moveTo ( currentVM . x , scaleZero ) ;
}
if ( ! currentVM . skip ) {
lastDrawnIndex = index ;
ctx . lineTo ( currentVM . x , currentVM . y ) ;
}
} else {
previous = lastDrawnIndex === - 1 ? previous : points [ lastDrawnIndex ] ;
if ( currentVM . skip ) {
// Only do this if this is the first point that is skipped
if ( ! spanGaps && lastDrawnIndex === ( index - 1 ) ) {
if ( loop ) {
ctx . lineTo ( scaleZero . x , scaleZero . y ) ;
} else {
ctx . lineTo ( previous . _view . x , scaleZero ) ;
}
}
} else {
if ( lastDrawnIndex !== ( index - 1 ) ) {
// There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case.
// If the first data point is NaN, then there is no real gap to skip
if ( spanGaps && lastDrawnIndex !== - 1 ) {
// We are spanning the gap, so simple draw a line to this point
lineToPoint ( previous , current ) ;
} else {
if ( loop ) {
ctx . lineTo ( currentVM . x , currentVM . y ) ;
} else {
ctx . lineTo ( currentVM . x , scaleZero ) ;
ctx . lineTo ( currentVM . x , currentVM . y ) ;
}
}
} else {
// Line to next point
lineToPoint ( previous , current ) ;
}
lastDrawnIndex = index ;
}
}
}
if ( ! loop && lastDrawnIndex !== - 1 ) {
ctx . lineTo ( points [ lastDrawnIndex ] . _view . x , scaleZero ) ;
}
ctx . fillStyle = vm . backgroundColor || globalDefaults . defaultColor ;
ctx . closePath ( ) ;
ctx . fill ( ) ;
}
// Stroke Line Options
var globalOptionLineElements = globalDefaults . elements . line ;
ctx . lineCap = vm . borderCapStyle || globalOptionLineElements . borderCapStyle ;
// IE 9 and 10 do not support line dash
if ( ctx . setLineDash ) {
ctx . setLineDash ( vm . borderDash || globalOptionLineElements . borderDash ) ;
}
ctx . lineDashOffset = vm . borderDashOffset || globalOptionLineElements . borderDashOffset ;
ctx . lineJoin = vm . borderJoinStyle || globalOptionLineElements . borderJoinStyle ;
ctx . lineWidth = vm . borderWidth || globalOptionLineElements . borderWidth ;
ctx . strokeStyle = vm . borderColor || globalDefaults . defaultColor ;
// Stroke Line
ctx . beginPath ( ) ;
lastDrawnIndex = - 1 ;
for ( index = 0 ; index < points . length ; ++ index ) {
current = points [ index ] ;
previous = helpers . previousItem ( points , index ) ;
currentVM = current . _view ;
// First point moves to it's starting position no matter what
if ( index === 0 ) {
if ( ! currentVM . skip ) {
ctx . moveTo ( currentVM . x , currentVM . y ) ;
lastDrawnIndex = index ;
}
} else {
previous = lastDrawnIndex === - 1 ? previous : points [ lastDrawnIndex ] ;
if ( ! currentVM . skip ) {
if ( ( lastDrawnIndex !== ( index - 1 ) && ! spanGaps ) || lastDrawnIndex === - 1 ) {
// There was a gap and this is the first point after the gap
ctx . moveTo ( currentVM . x , currentVM . y ) ;
} else {
// Line to next point
lineToPoint ( previous , current ) ;
}
lastDrawnIndex = index ;
}
}
}
ctx . stroke ( ) ;
ctx . restore ( ) ;
}
} ) ;
} ;
} , { } ] , 37 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ,
globalOpts = Chart . defaults . global ,
defaultColor = globalOpts . defaultColor ;
globalOpts . elements . point = {
radius : 3 ,
pointStyle : 'circle' ,
backgroundColor : defaultColor ,
borderWidth : 1 ,
borderColor : defaultColor ,
// Hover
hitRadius : 1 ,
hoverRadius : 4 ,
hoverBorderWidth : 1
} ;
Chart . elements . Point = Chart . Element . extend ( {
inRange : function ( mouseX , mouseY ) {
var vm = this . _view ;
return vm ? ( ( Math . pow ( mouseX - vm . x , 2 ) + Math . pow ( mouseY - vm . y , 2 ) ) < Math . pow ( vm . hitRadius + vm . radius , 2 ) ) : false ;
} ,
inLabelRange : function ( mouseX ) {
var vm = this . _view ;
return vm ? ( Math . pow ( mouseX - vm . x , 2 ) < Math . pow ( vm . radius + vm . hitRadius , 2 ) ) : false ;
} ,
tooltipPosition : function ( ) {
var vm = this . _view ;
return {
x : vm . x ,
y : vm . y ,
padding : vm . radius + vm . borderWidth
} ;
} ,
draw : function ( ) {
var vm = this . _view ;
var ctx = this . _chart . ctx ;
var pointStyle = vm . pointStyle ;
var radius = vm . radius ;
var x = vm . x ;
var y = vm . y ;
if ( vm . skip ) {
return ;
}
ctx . strokeStyle = vm . borderColor || defaultColor ;
ctx . lineWidth = helpers . getValueOrDefault ( vm . borderWidth , globalOpts . elements . point . borderWidth ) ;
ctx . fillStyle = vm . backgroundColor || defaultColor ;
Chart . canvasHelpers . drawPoint ( ctx , pointStyle , radius , x , y ) ;
}
} ) ;
} ;
} , { } ] , 38 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var globalOpts = Chart . defaults . global ;
globalOpts . elements . rectangle = {
backgroundColor : globalOpts . defaultColor ,
borderWidth : 0 ,
borderColor : globalOpts . defaultColor ,
borderSkipped : 'bottom'
} ;
Chart . elements . Rectangle = Chart . Element . extend ( {
draw : function ( ) {
var ctx = this . _chart . ctx ;
var vm = this . _view ;
var halfWidth = vm . width / 2 ,
leftX = vm . x - halfWidth ,
rightX = vm . x + halfWidth ,
top = vm . base - ( vm . base - vm . y ) ,
halfStroke = vm . borderWidth / 2 ;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if ( vm . borderWidth ) {
leftX += halfStroke ;
rightX -= halfStroke ;
top += halfStroke ;
}
ctx . beginPath ( ) ;
ctx . fillStyle = vm . backgroundColor ;
ctx . strokeStyle = vm . borderColor ;
ctx . lineWidth = vm . borderWidth ;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[ leftX , vm . base ] ,
[ leftX , top ] ,
[ rightX , top ] ,
[ rightX , vm . base ]
] ;
// Find first (starting) corner with fallback to 'bottom'
var borders = [ 'bottom' , 'left' , 'top' , 'right' ] ;
var startCorner = borders . indexOf ( vm . borderSkipped , 0 ) ;
if ( startCorner === - 1 )
startCorner = 0 ;
function cornerAt ( index ) {
return corners [ ( startCorner + index ) % 4 ] ;
}
// Draw rectangle from 'startCorner'
ctx . moveTo . apply ( ctx , cornerAt ( 0 ) ) ;
for ( var i = 1 ; i < 4 ; i ++ )
ctx . lineTo . apply ( ctx , cornerAt ( i ) ) ;
ctx . fill ( ) ;
if ( vm . borderWidth ) {
ctx . stroke ( ) ;
}
} ,
height : function ( ) {
var vm = this . _view ;
return vm . base - vm . y ;
} ,
inRange : function ( mouseX , mouseY ) {
var vm = this . _view ;
return vm ?
( vm . y < vm . base ?
( mouseX >= vm . x - vm . width / 2 && mouseX <= vm . x + vm . width / 2 ) && ( mouseY >= vm . y && mouseY <= vm . base ) :
( mouseX >= vm . x - vm . width / 2 && mouseX <= vm . x + vm . width / 2 ) && ( mouseY >= vm . base && mouseY <= vm . y ) ) :
false ;
} ,
inLabelRange : function ( mouseX ) {
var vm = this . _view ;
return vm ? ( mouseX >= vm . x - vm . width / 2 && mouseX <= vm . x + vm . width / 2 ) : false ;
} ,
tooltipPosition : function ( ) {
var vm = this . _view ;
return {
x : vm . x ,
y : vm . y
} ;
}
} ) ;
} ;
} , { } ] , 39 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
// Default config for a category scale
var defaultConfig = {
position : "bottom"
} ;
var DatasetScale = Chart . Scale . extend ( {
/ * *
* Internal function to get the correct labels . If data . xLabels or data . yLabels are defined , use tose
* else fall back to data . labels
* @ private
* /
getLabels : function ( ) {
var data = this . chart . data ;
return ( this . isHorizontal ( ) ? data . xLabels : data . yLabels ) || data . labels ;
} ,
// Implement this so that
determineDataLimits : function ( ) {
var me = this ;
var labels = me . getLabels ( ) ;
me . minIndex = 0 ;
me . maxIndex = labels . length - 1 ;
var findIndex ;
if ( me . options . ticks . min !== undefined ) {
// user specified min value
findIndex = helpers . indexOf ( labels , me . options . ticks . min ) ;
me . minIndex = findIndex !== - 1 ? findIndex : me . minIndex ;
}
if ( me . options . ticks . max !== undefined ) {
// user specified max value
findIndex = helpers . indexOf ( labels , me . options . ticks . max ) ;
me . maxIndex = findIndex !== - 1 ? findIndex : me . maxIndex ;
}
me . min = labels [ me . minIndex ] ;
me . max = labels [ me . maxIndex ] ;
} ,
buildTicks : function ( ) {
var me = this ;
var labels = me . getLabels ( ) ;
// If we are viewing some subset of labels, slice the original array
me . ticks = ( me . minIndex === 0 && me . maxIndex === labels . length - 1 ) ? labels : labels . slice ( me . minIndex , me . maxIndex + 1 ) ;
} ,
getLabelForIndex : function ( index ) {
return this . ticks [ index ] ;
} ,
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue : function ( value , index , datasetIndex , includeOffset ) {
var me = this ;
// 1 is added because we need the length but we have the indexes
var offsetAmt = Math . max ( ( me . maxIndex + 1 - me . minIndex - ( ( me . options . gridLines . offsetGridLines ) ? 0 : 1 ) ) , 1 ) ;
if ( value !== undefined && isNaN ( index ) ) {
var labels = me . getLabels ( ) ;
var idx = labels . indexOf ( value ) ;
index = idx !== - 1 ? idx : index ;
}
if ( me . isHorizontal ( ) ) {
var innerWidth = me . width - ( me . paddingLeft + me . paddingRight ) ;
var valueWidth = innerWidth / offsetAmt ;
var widthOffset = ( valueWidth * ( index - me . minIndex ) ) + me . paddingLeft ;
if ( me . options . gridLines . offsetGridLines && includeOffset || me . maxIndex === me . minIndex && includeOffset ) {
widthOffset += ( valueWidth / 2 ) ;
}
return me . left + Math . round ( widthOffset ) ;
} else {
var innerHeight = me . height - ( me . paddingTop + me . paddingBottom ) ;
var valueHeight = innerHeight / offsetAmt ;
var heightOffset = ( valueHeight * ( index - me . minIndex ) ) + me . paddingTop ;
if ( me . options . gridLines . offsetGridLines && includeOffset ) {
heightOffset += ( valueHeight / 2 ) ;
}
return me . top + Math . round ( heightOffset ) ;
}
} ,
getPixelForTick : function ( index , includeOffset ) {
return this . getPixelForValue ( this . ticks [ index ] , index + this . minIndex , null , includeOffset ) ;
} ,
getValueForPixel : function ( pixel ) {
var me = this ;
var value ;
var offsetAmt = Math . max ( ( me . ticks . length - ( ( me . options . gridLines . offsetGridLines ) ? 0 : 1 ) ) , 1 ) ;
var horz = me . isHorizontal ( ) ;
var innerDimension = horz ? me . width - ( me . paddingLeft + me . paddingRight ) : me . height - ( me . paddingTop + me . paddingBottom ) ;
var valueDimension = innerDimension / offsetAmt ;
pixel -= horz ? me . left : me . top ;
if ( me . options . gridLines . offsetGridLines ) {
pixel -= ( valueDimension / 2 ) ;
}
pixel -= horz ? me . paddingLeft : me . paddingTop ;
if ( pixel <= 0 ) {
value = 0 ;
} else {
value = Math . round ( pixel / valueDimension ) ;
}
return value ;
} ,
getBasePixel : function ( ) {
return this . bottom ;
}
} ) ;
Chart . scaleService . registerScaleType ( "category" , DatasetScale , defaultConfig ) ;
} ;
} , { } ] , 40 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var defaultConfig = {
position : "left" ,
ticks : {
callback : function ( tickValue , index , ticks ) {
// If we have lots of ticks, don't use the ones
var delta = ticks . length > 3 ? ticks [ 2 ] - ticks [ 1 ] : ticks [ 1 ] - ticks [ 0 ] ;
// If we have a number like 2.5 as the delta, figure out how many decimal places we need
if ( Math . abs ( delta ) > 1 ) {
if ( tickValue !== Math . floor ( tickValue ) ) {
// not an integer
delta = tickValue - Math . floor ( tickValue ) ;
}
}
var logDelta = helpers . log10 ( Math . abs ( delta ) ) ;
var tickString = '' ;
if ( tickValue !== 0 ) {
var numDecimal = - 1 * Math . floor ( logDelta ) ;
numDecimal = Math . max ( Math . min ( numDecimal , 20 ) , 0 ) ; // toFixed has a max of 20 decimal places
tickString = tickValue . toFixed ( numDecimal ) ;
} else {
tickString = '0' ; // never show decimal places for 0
}
return tickString ;
}
}
} ;
var LinearScale = Chart . LinearScaleBase . extend ( {
determineDataLimits : function ( ) {
var me = this ;
var opts = me . options ;
var chart = me . chart ;
var data = chart . data ;
var datasets = data . datasets ;
var isHorizontal = me . isHorizontal ( ) ;
function IDMatches ( meta ) {
return isHorizontal ? meta . xAxisID === me . id : meta . yAxisID === me . id ;
}
// First Calculate the range
me . min = null ;
me . max = null ;
if ( opts . stacked ) {
var valuesPerType = { } ;
var hasPositiveValues = false ;
var hasNegativeValues = false ;
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( valuesPerType [ meta . type ] === undefined ) {
valuesPerType [ meta . type ] = {
positiveValues : [ ] ,
negativeValues : [ ]
} ;
}
// Store these per type
var positiveValues = valuesPerType [ meta . type ] . positiveValues ;
var negativeValues = valuesPerType [ meta . type ] . negativeValues ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) ) {
helpers . each ( dataset . data , function ( rawValue , index ) {
var value = + me . getRightValue ( rawValue ) ;
if ( isNaN ( value ) || meta . data [ index ] . hidden ) {
return ;
}
positiveValues [ index ] = positiveValues [ index ] || 0 ;
negativeValues [ index ] = negativeValues [ index ] || 0 ;
if ( opts . relativePoints ) {
positiveValues [ index ] = 100 ;
} else {
if ( value < 0 ) {
hasNegativeValues = true ;
negativeValues [ index ] += value ;
} else {
hasPositiveValues = true ;
positiveValues [ index ] += value ;
}
}
} ) ;
}
} ) ;
helpers . each ( valuesPerType , function ( valuesForType ) {
var values = valuesForType . positiveValues . concat ( valuesForType . negativeValues ) ;
var minVal = helpers . min ( values ) ;
var maxVal = helpers . max ( values ) ;
me . min = me . min === null ? minVal : Math . min ( me . min , minVal ) ;
me . max = me . max === null ? maxVal : Math . max ( me . max , maxVal ) ;
} ) ;
} else {
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) ) {
helpers . each ( dataset . data , function ( rawValue , index ) {
var value = + me . getRightValue ( rawValue ) ;
if ( isNaN ( value ) || meta . data [ index ] . hidden ) {
return ;
}
if ( me . min === null ) {
me . min = value ;
} else if ( value < me . min ) {
me . min = value ;
}
if ( me . max === null ) {
me . max = value ;
} else if ( value > me . max ) {
me . max = value ;
}
} ) ;
}
} ) ;
}
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
this . handleTickRangeOptions ( ) ;
} ,
getTickLimit : function ( ) {
var maxTicks ;
var me = this ;
var tickOpts = me . options . ticks ;
if ( me . isHorizontal ( ) ) {
maxTicks = Math . min ( tickOpts . maxTicksLimit ? tickOpts . maxTicksLimit : 11 , Math . ceil ( me . width / 50 ) ) ;
} else {
// The factor of 2 used to scale the font size has been experimentally determined.
var tickFontSize = helpers . getValueOrDefault ( tickOpts . fontSize , Chart . defaults . global . defaultFontSize ) ;
maxTicks = Math . min ( tickOpts . maxTicksLimit ? tickOpts . maxTicksLimit : 11 , Math . ceil ( me . height / ( 2 * tickFontSize ) ) ) ;
}
return maxTicks ;
} ,
// Called after the ticks are built. We need
handleDirectionalChanges : function ( ) {
if ( ! this . isHorizontal ( ) ) {
// We are in a vertical orientation. The top value is the highest. So reverse the array
this . ticks . reverse ( ) ;
}
} ,
getLabelForIndex : function ( index , datasetIndex ) {
return + this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
} ,
// Utils
getPixelForValue : function ( value ) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
var me = this ;
var paddingLeft = me . paddingLeft ;
var paddingBottom = me . paddingBottom ;
var start = me . start ;
var rightValue = + me . getRightValue ( value ) ;
var pixel ;
var innerDimension ;
var range = me . end - start ;
if ( me . isHorizontal ( ) ) {
innerDimension = me . width - ( paddingLeft + me . paddingRight ) ;
pixel = me . left + ( innerDimension / range * ( rightValue - start ) ) ;
return Math . round ( pixel + paddingLeft ) ;
} else {
innerDimension = me . height - ( me . paddingTop + paddingBottom ) ;
pixel = ( me . bottom - paddingBottom ) - ( innerDimension / range * ( rightValue - start ) ) ;
return Math . round ( pixel ) ;
}
} ,
getValueForPixel : function ( pixel ) {
var me = this ;
var isHorizontal = me . isHorizontal ( ) ;
var paddingLeft = me . paddingLeft ;
var paddingBottom = me . paddingBottom ;
var innerDimension = isHorizontal ? me . width - ( paddingLeft + me . paddingRight ) : me . height - ( me . paddingTop + paddingBottom ) ;
var offset = ( isHorizontal ? pixel - me . left - paddingLeft : me . bottom - paddingBottom - pixel ) / innerDimension ;
return me . start + ( ( me . end - me . start ) * offset ) ;
} ,
getPixelForTick : function ( index ) {
return this . getPixelForValue ( this . ticksAsNumbers [ index ] ) ;
}
} ) ;
Chart . scaleService . registerScaleType ( "linear" , LinearScale , defaultConfig ) ;
} ;
} , { } ] , 41 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ,
noop = helpers . noop ;
Chart . LinearScaleBase = Chart . Scale . extend ( {
handleTickRangeOptions : function ( ) {
var me = this ;
var opts = me . options ;
var tickOpts = opts . ticks ;
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
// do nothing since that would make the chart weird. If the user really wants a weird chart
// axis, they can manually override it
if ( tickOpts . beginAtZero ) {
var minSign = helpers . sign ( me . min ) ;
var maxSign = helpers . sign ( me . max ) ;
if ( minSign < 0 && maxSign < 0 ) {
// move the top up to 0
me . max = 0 ;
} else if ( minSign > 0 && maxSign > 0 ) {
// move the botttom down to 0
me . min = 0 ;
}
}
if ( tickOpts . min !== undefined ) {
me . min = tickOpts . min ;
} else if ( tickOpts . suggestedMin !== undefined ) {
me . min = Math . min ( me . min , tickOpts . suggestedMin ) ;
}
if ( tickOpts . max !== undefined ) {
me . max = tickOpts . max ;
} else if ( tickOpts . suggestedMax !== undefined ) {
me . max = Math . max ( me . max , tickOpts . suggestedMax ) ;
}
if ( me . min === me . max ) {
me . max ++ ;
if ( ! tickOpts . beginAtZero ) {
me . min -- ;
}
}
} ,
getTickLimit : noop ,
handleDirectionalChanges : noop ,
buildTicks : function ( ) {
var me = this ;
var opts = me . options ;
var ticks = me . ticks = [ ] ;
var tickOpts = opts . ticks ;
var getValueOrDefault = helpers . getValueOrDefault ;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var maxTicks = me . getTickLimit ( ) ;
// Make sure we always have at least 2 ticks
maxTicks = Math . max ( 2 , maxTicks ) ;
// To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details.
var spacing ;
var fixedStepSizeSet = ( tickOpts . fixedStepSize && tickOpts . fixedStepSize > 0 ) || ( tickOpts . stepSize && tickOpts . stepSize > 0 ) ;
if ( fixedStepSizeSet ) {
spacing = getValueOrDefault ( tickOpts . fixedStepSize , tickOpts . stepSize ) ;
} else {
var niceRange = helpers . niceNum ( me . max - me . min , false ) ;
spacing = helpers . niceNum ( niceRange / ( maxTicks - 1 ) , true ) ;
}
var niceMin = Math . floor ( me . min / spacing ) * spacing ;
var niceMax = Math . ceil ( me . max / spacing ) * spacing ;
var numSpaces = ( niceMax - niceMin ) / spacing ;
// If very close to our rounded value, use it.
if ( helpers . almostEquals ( numSpaces , Math . round ( numSpaces ) , spacing / 1000 ) ) {
numSpaces = Math . round ( numSpaces ) ;
} else {
numSpaces = Math . ceil ( numSpaces ) ;
}
// Put the values into the ticks array
ticks . push ( tickOpts . min !== undefined ? tickOpts . min : niceMin ) ;
for ( var j = 1 ; j < numSpaces ; ++ j ) {
ticks . push ( niceMin + ( j * spacing ) ) ;
}
ticks . push ( tickOpts . max !== undefined ? tickOpts . max : niceMax ) ;
me . handleDirectionalChanges ( ) ;
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
me . max = helpers . max ( ticks ) ;
me . min = helpers . min ( ticks ) ;
if ( tickOpts . reverse ) {
ticks . reverse ( ) ;
me . start = me . max ;
me . end = me . min ;
} else {
me . start = me . min ;
me . end = me . max ;
}
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
convertTicksToLabels : function ( ) {
var me = this ;
me . ticksAsNumbers = me . ticks . slice ( ) ;
me . zeroLineIndex = me . ticks . indexOf ( 0 ) ;
Chart . Scale . prototype . convertTicksToLabels . call ( me ) ;
}
} ) ;
} ;
} , { } ] , 42 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var defaultConfig = {
position : "left" ,
// label settings
ticks : {
callback : function ( value , index , arr ) {
var remain = value / ( Math . pow ( 10 , Math . floor ( helpers . log10 ( value ) ) ) ) ;
if ( value === 0 ) {
return '0' ;
} else if ( remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr . length - 1 ) {
return value . toExponential ( ) ;
} else {
return '' ;
}
}
}
} ;
var LogarithmicScale = Chart . Scale . extend ( {
determineDataLimits : function ( ) {
var me = this ;
var opts = me . options ;
var tickOpts = opts . ticks ;
var chart = me . chart ;
var data = chart . data ;
var datasets = data . datasets ;
var getValueOrDefault = helpers . getValueOrDefault ;
var isHorizontal = me . isHorizontal ( ) ;
function IDMatches ( meta ) {
return isHorizontal ? meta . xAxisID === me . id : meta . yAxisID === me . id ;
}
// Calculate Range
me . min = null ;
me . max = null ;
me . minNotZero = null ;
if ( opts . stacked ) {
var valuesPerType = { } ;
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) ) {
if ( valuesPerType [ meta . type ] === undefined ) {
valuesPerType [ meta . type ] = [ ] ;
}
helpers . each ( dataset . data , function ( rawValue , index ) {
var values = valuesPerType [ meta . type ] ;
var value = + me . getRightValue ( rawValue ) ;
if ( isNaN ( value ) || meta . data [ index ] . hidden ) {
return ;
}
values [ index ] = values [ index ] || 0 ;
if ( opts . relativePoints ) {
values [ index ] = 100 ;
} else {
// Don't need to split positive and negative since the log scale can't handle a 0 crossing
values [ index ] += value ;
}
} ) ;
}
} ) ;
helpers . each ( valuesPerType , function ( valuesForType ) {
var minVal = helpers . min ( valuesForType ) ;
var maxVal = helpers . max ( valuesForType ) ;
me . min = me . min === null ? minVal : Math . min ( me . min , minVal ) ;
me . max = me . max === null ? maxVal : Math . max ( me . max , maxVal ) ;
} ) ;
} else {
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) ) {
helpers . each ( dataset . data , function ( rawValue , index ) {
var value = + me . getRightValue ( rawValue ) ;
if ( isNaN ( value ) || meta . data [ index ] . hidden ) {
return ;
}
if ( me . min === null ) {
me . min = value ;
} else if ( value < me . min ) {
me . min = value ;
}
if ( me . max === null ) {
me . max = value ;
} else if ( value > me . max ) {
me . max = value ;
}
if ( value !== 0 && ( me . minNotZero === null || value < me . minNotZero ) ) {
me . minNotZero = value ;
}
} ) ;
}
} ) ;
}
me . min = getValueOrDefault ( tickOpts . min , me . min ) ;
me . max = getValueOrDefault ( tickOpts . max , me . max ) ;
if ( me . min === me . max ) {
if ( me . min !== 0 && me . min !== null ) {
me . min = Math . pow ( 10 , Math . floor ( helpers . log10 ( me . min ) ) - 1 ) ;
me . max = Math . pow ( 10 , Math . floor ( helpers . log10 ( me . max ) ) + 1 ) ;
} else {
me . min = 1 ;
me . max = 10 ;
}
}
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
buildTicks : function ( ) {
var me = this ;
var opts = me . options ;
var tickOpts = opts . ticks ;
var getValueOrDefault = helpers . getValueOrDefault ;
// Reset the ticks array. Later on, we will draw a grid line at these positions
// The array simply contains the numerical value of the spots where ticks will be
var ticks = me . ticks = [ ] ;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = getValueOrDefault ( tickOpts . min , Math . pow ( 10 , Math . floor ( helpers . log10 ( me . min ) ) ) ) ;
while ( tickVal < me . max ) {
ticks . push ( tickVal ) ;
var exp ;
var significand ;
if ( tickVal === 0 ) {
exp = Math . floor ( helpers . log10 ( me . minNotZero ) ) ;
significand = Math . round ( me . minNotZero / Math . pow ( 10 , exp ) ) ;
} else {
exp = Math . floor ( helpers . log10 ( tickVal ) ) ;
significand = Math . floor ( tickVal / Math . pow ( 10 , exp ) ) + 1 ;
}
if ( significand === 10 ) {
significand = 1 ;
++ exp ;
}
tickVal = significand * Math . pow ( 10 , exp ) ;
}
var lastTick = getValueOrDefault ( tickOpts . max , tickVal ) ;
ticks . push ( lastTick ) ;
if ( ! me . isHorizontal ( ) ) {
// We are in a vertical orientation. The top value is the highest. So reverse the array
ticks . reverse ( ) ;
}
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
me . max = helpers . max ( ticks ) ;
me . min = helpers . min ( ticks ) ;
if ( tickOpts . reverse ) {
ticks . reverse ( ) ;
me . start = me . max ;
me . end = me . min ;
} else {
me . start = me . min ;
me . end = me . max ;
}
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
convertTicksToLabels : function ( ) {
this . tickValues = this . ticks . slice ( ) ;
Chart . Scale . prototype . convertTicksToLabels . call ( this ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
// Get the correct tooltip label
getLabelForIndex : function ( index , datasetIndex ) {
return + this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getPixelForTick : function ( index ) {
return this . getPixelForValue ( this . tickValues [ index ] ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getPixelForValue : function ( value ) {
var me = this ;
var innerDimension ;
var pixel ;
var start = me . start ;
var newVal = + me . getRightValue ( value ) ;
var range ;
var paddingTop = me . paddingTop ;
var paddingBottom = me . paddingBottom ;
var paddingLeft = me . paddingLeft ;
var opts = me . options ;
var tickOpts = opts . ticks ;
if ( me . isHorizontal ( ) ) {
range = helpers . log10 ( me . end ) - helpers . log10 ( start ) ; // todo: if start === 0
if ( newVal === 0 ) {
pixel = me . left + paddingLeft ;
} else {
innerDimension = me . width - ( paddingLeft + me . paddingRight ) ;
pixel = me . left + ( innerDimension / range * ( helpers . log10 ( newVal ) - helpers . log10 ( start ) ) ) ;
pixel += paddingLeft ;
}
} else {
// Bottom - top since pixels increase downard on a screen
innerDimension = me . height - ( paddingTop + paddingBottom ) ;
if ( start === 0 && ! tickOpts . reverse ) {
range = helpers . log10 ( me . end ) - helpers . log10 ( me . minNotZero ) ;
if ( newVal === start ) {
pixel = me . bottom - paddingBottom ;
} else if ( newVal === me . minNotZero ) {
pixel = me . bottom - paddingBottom - innerDimension * 0.02 ;
} else {
pixel = me . bottom - paddingBottom - innerDimension * 0.02 - ( innerDimension * 0.98 / range * ( helpers . log10 ( newVal ) - helpers . log10 ( me . minNotZero ) ) ) ;
}
} else if ( me . end === 0 && tickOpts . reverse ) {
range = helpers . log10 ( me . start ) - helpers . log10 ( me . minNotZero ) ;
if ( newVal === me . end ) {
pixel = me . top + paddingTop ;
} else if ( newVal === me . minNotZero ) {
pixel = me . top + paddingTop + innerDimension * 0.02 ;
} else {
pixel = me . top + paddingTop + innerDimension * 0.02 + ( innerDimension * 0.98 / range * ( helpers . log10 ( newVal ) - helpers . log10 ( me . minNotZero ) ) ) ;
}
} else {
range = helpers . log10 ( me . end ) - helpers . log10 ( start ) ;
innerDimension = me . height - ( paddingTop + paddingBottom ) ;
pixel = ( me . bottom - paddingBottom ) - ( innerDimension / range * ( helpers . log10 ( newVal ) - helpers . log10 ( start ) ) ) ;
}
}
return pixel ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getValueForPixel : function ( pixel ) {
var me = this ;
var range = helpers . log10 ( me . end ) - helpers . log10 ( me . start ) ;
var value , innerDimension ;
if ( me . isHorizontal ( ) ) {
innerDimension = me . width - ( me . paddingLeft + me . paddingRight ) ;
value = me . start * Math . pow ( 10 , ( pixel - me . left - me . paddingLeft ) * range / innerDimension ) ;
} else { // todo: if start === 0
innerDimension = me . height - ( me . paddingTop + me . paddingBottom ) ;
value = Math . pow ( 10 , ( me . bottom - me . paddingBottom - pixel ) * range / innerDimension ) / me . start ;
}
return value ;
}
} ) ;
Chart . scaleService . registerScaleType ( "logarithmic" , LogarithmicScale , defaultConfig ) ;
} ;
} , { } ] , 43 : [ function ( require , module , exports ) {
"use strict" ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var globalDefaults = Chart . defaults . global ;
var defaultConfig = {
display : true ,
//Boolean - Whether to animate scaling the chart from the centre
animate : true ,
lineArc : false ,
position : "chartArea" ,
angleLines : {
display : true ,
color : "rgba(0, 0, 0, 0.1)" ,
lineWidth : 1
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
// label settings
ticks : {
//Boolean - Show a backdrop to the scale label
showLabelBackdrop : true ,
//String - The colour of the label backdrop
backdropColor : "rgba(255,255,255,0.75)" ,
//Number - The backdrop padding above & below the label in pixels
backdropPaddingY : 2 ,
//Number - The backdrop padding to the side of the label in pixels
backdropPaddingX : 2
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
pointLabels : {
//Number - Point label font size in pixels
fontSize : 10 ,
//Function - Used to convert point labels
callback : function ( label ) {
return label ;
}
}
} ;
var LinearRadialScale = Chart . LinearScaleBase . extend ( {
getValueCount : function ( ) {
return this . chart . data . labels . length ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
setDimensions : function ( ) {
var me = this ;
var opts = me . options ;
var tickOpts = opts . ticks ;
// Set the unconstrained dimension before label rotation
me . width = me . maxWidth ;
me . height = me . maxHeight ;
me . xCenter = Math . round ( me . width / 2 ) ;
me . yCenter = Math . round ( me . height / 2 ) ;
var minSize = helpers . min ( [ me . height , me . width ] ) ;
var tickFontSize = helpers . getValueOrDefault ( tickOpts . fontSize , globalDefaults . defaultFontSize ) ;
me . drawingArea = opts . display ? ( minSize / 2 ) - ( tickFontSize / 2 + tickOpts . backdropPaddingY ) : ( minSize / 2 ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
determineDataLimits : function ( ) {
var me = this ;
var chart = me . chart ;
me . min = null ;
me . max = null ;
helpers . each ( chart . data . datasets , function ( dataset , datasetIndex ) {
if ( chart . isDatasetVisible ( datasetIndex ) ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
helpers . each ( dataset . data , function ( rawValue , index ) {
var value = + me . getRightValue ( rawValue ) ;
if ( isNaN ( value ) || meta . data [ index ] . hidden ) {
return ;
}
if ( me . min === null ) {
me . min = value ;
} else if ( value < me . min ) {
me . min = value ;
}
if ( me . max === null ) {
me . max = value ;
} else if ( value > me . max ) {
me . max = value ;
}
} ) ;
}
} ) ;
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
me . handleTickRangeOptions ( ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getTickLimit : function ( ) {
var tickOpts = this . options . ticks ;
var tickFontSize = helpers . getValueOrDefault ( tickOpts . fontSize , globalDefaults . defaultFontSize ) ;
return Math . min ( tickOpts . maxTicksLimit ? tickOpts . maxTicksLimit : 11 , Math . ceil ( this . drawingArea / ( 1.5 * tickFontSize ) ) ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
convertTicksToLabels : function ( ) {
var me = this ;
Chart . LinearScaleBase . prototype . convertTicksToLabels . call ( me ) ;
// Point labels
me . pointLabels = me . chart . data . labels . map ( me . options . pointLabels . callback , me ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getLabelForIndex : function ( index , datasetIndex ) {
return + this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
fit : function ( ) {
/ *
* Right , this is really confusing and there is a lot of maths going on here
* The gist of the problem is here : https : //gist.github.com/nnnick/696cc9c55f4b0beb8fe9
*
* Reaction : https : //dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
*
* Solution :
*
* We assume the radius of the polygon is half the size of the canvas at first
* at each index we check if the text overlaps .
*
* Where it does , we store that angle and that index .
*
* After finding the largest index and angle we calculate how much we need to remove
* from the shape radius to move the point inwards by that x .
*
* We average the left and right distances to get the maximum shape radius that can fit in the box
* along with labels .
*
* Once we have that , we can find the centre point for the chart , by taking the x text protrusion
* on each side , removing that from the size , halving it and adding the left x protrusion width .
*
* This will mean we have a shape fitted to the canvas , as large as it can be with the labels
* and position it in the most space efficient manner
*
* https : //dl.dropboxusercontent.com/u/34601363/yeahscience.gif
* /
var pointLabels = this . options . pointLabels ;
var pointLabelFontSize = helpers . getValueOrDefault ( pointLabels . fontSize , globalDefaults . defaultFontSize ) ;
var pointLabeFontStyle = helpers . getValueOrDefault ( pointLabels . fontStyle , globalDefaults . defaultFontStyle ) ;
var pointLabeFontFamily = helpers . getValueOrDefault ( pointLabels . fontFamily , globalDefaults . defaultFontFamily ) ;
var pointLabeFont = helpers . fontString ( pointLabelFontSize , pointLabeFontStyle , pointLabeFontFamily ) ;
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
var largestPossibleRadius = helpers . min ( [ ( this . height / 2 - pointLabelFontSize - 5 ) , this . width / 2 ] ) ,
pointPosition ,
i ,
textWidth ,
halfTextWidth ,
furthestRight = this . width ,
furthestRightIndex ,
furthestRightAngle ,
furthestLeft = 0 ,
furthestLeftIndex ,
furthestLeftAngle ,
xProtrusionLeft ,
xProtrusionRight ,
radiusReductionRight ,
radiusReductionLeft ;
this . ctx . font = pointLabeFont ;
for ( i = 0 ; i < this . getValueCount ( ) ; i ++ ) {
// 5px to space the text slightly out - similar to what we do in the draw function.
pointPosition = this . getPointPosition ( i , largestPossibleRadius ) ;
textWidth = this . ctx . measureText ( this . pointLabels [ i ] ? this . pointLabels [ i ] : '' ) . width + 5 ;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = this . getIndexAngle ( i ) + ( Math . PI / 2 ) ;
var angle = ( angleRadians * 360 / ( 2 * Math . PI ) ) % 360 ;
if ( angle === 0 || angle === 180 ) {
// At angle 0 and 180, we're at exactly the top/bottom
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
// w/left and right text sizes
halfTextWidth = textWidth / 2 ;
if ( pointPosition . x + halfTextWidth > furthestRight ) {
furthestRight = pointPosition . x + halfTextWidth ;
furthestRightIndex = i ;
}
if ( pointPosition . x - halfTextWidth < furthestLeft ) {
furthestLeft = pointPosition . x - halfTextWidth ;
furthestLeftIndex = i ;
}
} else if ( angle < 180 ) {
// Less than half the values means we'll left align the text
if ( pointPosition . x + textWidth > furthestRight ) {
furthestRight = pointPosition . x + textWidth ;
furthestRightIndex = i ;
}
} else {
// More than half the values means we'll right align the text
if ( pointPosition . x - textWidth < furthestLeft ) {
furthestLeft = pointPosition . x - textWidth ;
furthestLeftIndex = i ;
}
}
}
xProtrusionLeft = furthestLeft ;
xProtrusionRight = Math . ceil ( furthestRight - this . width ) ;
furthestRightAngle = this . getIndexAngle ( furthestRightIndex ) ;
furthestLeftAngle = this . getIndexAngle ( furthestLeftIndex ) ;
radiusReductionRight = xProtrusionRight / Math . sin ( furthestRightAngle + Math . PI / 2 ) ;
radiusReductionLeft = xProtrusionLeft / Math . sin ( furthestLeftAngle + Math . PI / 2 ) ;
// Ensure we actually need to reduce the size of the chart
radiusReductionRight = ( helpers . isNumber ( radiusReductionRight ) ) ? radiusReductionRight : 0 ;
radiusReductionLeft = ( helpers . isNumber ( radiusReductionLeft ) ) ? radiusReductionLeft : 0 ;
this . drawingArea = Math . round ( largestPossibleRadius - ( radiusReductionLeft + radiusReductionRight ) / 2 ) ;
this . setCenterPoint ( radiusReductionLeft , radiusReductionRight ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
setCenterPoint : function ( leftMovement , rightMovement ) {
var me = this ;
var maxRight = me . width - rightMovement - me . drawingArea ,
maxLeft = leftMovement + me . drawingArea ;
me . xCenter = Math . round ( ( ( maxLeft + maxRight ) / 2 ) + me . left ) ;
// Always vertically in the centre as the text height doesn't change
me . yCenter = Math . round ( ( me . height / 2 ) + me . top ) ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getIndexAngle : function ( index ) {
var angleMultiplier = ( Math . PI * 2 ) / this . getValueCount ( ) ;
var startAngle = this . chart . options && this . chart . options . startAngle ?
this . chart . options . startAngle :
0 ;
var startAngleRadians = startAngle * Math . PI * 2 / 360 ;
// Start from the top instead of right, so remove a quarter of the circle
return index * angleMultiplier - ( Math . PI / 2 ) + startAngleRadians ;
2013-12-15 13:55:50 +01:00
} ,
2016-09-11 16:30:23 +02:00
getDistanceFromCenterForValue : function ( value ) {
var me = this ;
if ( value === null ) {
return 0 ; // null always in center
}
// Take into account half font size + the yPadding of the top value
var scalingFactor = me . drawingArea / ( me . max - me . min ) ;
if ( me . options . reverse ) {
return ( me . max - value ) * scalingFactor ;
2013-12-15 13:55:50 +01:00
} else {
2016-09-11 16:30:23 +02:00
return ( value - me . min ) * scalingFactor ;
2013-12-15 13:55:50 +01:00
}
} ,
2016-09-11 16:30:23 +02:00
getPointPosition : function ( index , distanceFromCenter ) {
var me = this ;
var thisAngle = me . getIndexAngle ( index ) ;
return {
x : Math . round ( Math . cos ( thisAngle ) * distanceFromCenter ) + me . xCenter ,
y : Math . round ( Math . sin ( thisAngle ) * distanceFromCenter ) + me . yCenter
} ;
} ,
getPointPositionForValue : function ( index , value ) {
return this . getPointPosition ( index , this . getDistanceFromCenterForValue ( value ) ) ;
} ,
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
getBasePosition : function ( ) {
var me = this ;
var min = me . min ;
var max = me . max ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
return me . getPointPositionForValue ( 0 ,
me . beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0 ) ;
} ,
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
draw : function ( ) {
var me = this ;
var opts = me . options ;
var gridLineOpts = opts . gridLines ;
var tickOpts = opts . ticks ;
var angleLineOpts = opts . angleLines ;
var pointLabelOpts = opts . pointLabels ;
var getValueOrDefault = helpers . getValueOrDefault ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( opts . display ) {
var ctx = me . ctx ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Tick Font
var tickFontSize = getValueOrDefault ( tickOpts . fontSize , globalDefaults . defaultFontSize ) ;
var tickFontStyle = getValueOrDefault ( tickOpts . fontStyle , globalDefaults . defaultFontStyle ) ;
var tickFontFamily = getValueOrDefault ( tickOpts . fontFamily , globalDefaults . defaultFontFamily ) ;
var tickLabelFont = helpers . fontString ( tickFontSize , tickFontStyle , tickFontFamily ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
helpers . each ( me . ticks , function ( label , index ) {
// Don't draw a centre value (if it is minimum)
if ( index > 0 || opts . reverse ) {
var yCenterOffset = me . getDistanceFromCenterForValue ( me . ticksAsNumbers [ index ] ) ;
var yHeight = me . yCenter - yCenterOffset ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Draw circular lines around the scale
if ( gridLineOpts . display && index !== 0 ) {
ctx . strokeStyle = helpers . getValueAtIndexOrDefault ( gridLineOpts . color , index - 1 ) ;
ctx . lineWidth = helpers . getValueAtIndexOrDefault ( gridLineOpts . lineWidth , index - 1 ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( opts . lineArc ) {
// Draw circular arcs between the points
ctx . beginPath ( ) ;
ctx . arc ( me . xCenter , me . yCenter , yCenterOffset , 0 , Math . PI * 2 ) ;
ctx . closePath ( ) ;
ctx . stroke ( ) ;
} else {
// Draw straight lines connecting each index
ctx . beginPath ( ) ;
for ( var i = 0 ; i < me . getValueCount ( ) ; i ++ ) {
var pointPosition = me . getPointPosition ( i , yCenterOffset ) ;
if ( i === 0 ) {
ctx . moveTo ( pointPosition . x , pointPosition . y ) ;
} else {
ctx . lineTo ( pointPosition . x , pointPosition . y ) ;
}
}
ctx . closePath ( ) ;
ctx . stroke ( ) ;
}
}
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( tickOpts . display ) {
var tickFontColor = getValueOrDefault ( tickOpts . fontColor , globalDefaults . defaultFontColor ) ;
ctx . font = tickLabelFont ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( tickOpts . showLabelBackdrop ) {
var labelWidth = ctx . measureText ( label ) . width ;
ctx . fillStyle = tickOpts . backdropColor ;
ctx . fillRect (
me . xCenter - labelWidth / 2 - tickOpts . backdropPaddingX ,
yHeight - tickFontSize / 2 - tickOpts . backdropPaddingY ,
labelWidth + tickOpts . backdropPaddingX * 2 ,
tickFontSize + tickOpts . backdropPaddingY * 2
) ;
}
ctx . textAlign = 'center' ;
ctx . textBaseline = "middle" ;
ctx . fillStyle = tickFontColor ;
ctx . fillText ( label , me . xCenter , yHeight ) ;
}
}
} ) ;
if ( ! opts . lineArc ) {
ctx . lineWidth = angleLineOpts . lineWidth ;
ctx . strokeStyle = angleLineOpts . color ;
var outerDistance = me . getDistanceFromCenterForValue ( opts . reverse ? me . min : me . max ) ;
// Point Label Font
var pointLabelFontSize = getValueOrDefault ( pointLabelOpts . fontSize , globalDefaults . defaultFontSize ) ;
var pointLabeFontStyle = getValueOrDefault ( pointLabelOpts . fontStyle , globalDefaults . defaultFontStyle ) ;
var pointLabeFontFamily = getValueOrDefault ( pointLabelOpts . fontFamily , globalDefaults . defaultFontFamily ) ;
var pointLabeFont = helpers . fontString ( pointLabelFontSize , pointLabeFontStyle , pointLabeFontFamily ) ;
for ( var i = me . getValueCount ( ) - 1 ; i >= 0 ; i -- ) {
if ( angleLineOpts . display ) {
var outerPosition = me . getPointPosition ( i , outerDistance ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( me . xCenter , me . yCenter ) ;
ctx . lineTo ( outerPosition . x , outerPosition . y ) ;
ctx . stroke ( ) ;
ctx . closePath ( ) ;
}
// Extra 3px out for some label spacing
var pointLabelPosition = me . getPointPosition ( i , outerDistance + 5 ) ;
// Keep this in loop since we may support array properties here
var pointLabelFontColor = getValueOrDefault ( pointLabelOpts . fontColor , globalDefaults . defaultFontColor ) ;
ctx . font = pointLabeFont ;
ctx . fillStyle = pointLabelFontColor ;
var pointLabels = me . pointLabels ;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = this . getIndexAngle ( i ) + ( Math . PI / 2 ) ;
var angle = ( angleRadians * 360 / ( 2 * Math . PI ) ) % 360 ;
if ( angle === 0 || angle === 180 ) {
ctx . textAlign = 'center' ;
} else if ( angle < 180 ) {
ctx . textAlign = 'left' ;
} else {
ctx . textAlign = 'right' ;
}
// Set the correct text baseline based on outer positioning
if ( angle === 90 || angle === 270 ) {
ctx . textBaseline = 'middle' ;
} else if ( angle > 270 || angle < 90 ) {
ctx . textBaseline = 'bottom' ;
} else {
ctx . textBaseline = 'top' ;
}
ctx . fillText ( pointLabels [ i ] ? pointLabels [ i ] : '' , pointLabelPosition . x , pointLabelPosition . y ) ;
2013-12-15 13:55:50 +01:00
}
}
}
}
2016-09-11 16:30:23 +02:00
} ) ;
Chart . scaleService . registerScaleType ( "radialLinear" , LinearRadialScale , defaultConfig ) ;
} ;
} , { } ] , 44 : [ function ( require , module , exports ) {
/*global window: false */
"use strict" ;
var moment = require ( 1 ) ;
moment = typeof ( moment ) === 'function' ? moment : window . moment ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var time = {
units : [ {
name : 'millisecond' ,
steps : [ 1 , 2 , 5 , 10 , 20 , 50 , 100 , 250 , 500 ]
} , {
name : 'second' ,
steps : [ 1 , 2 , 5 , 10 , 30 ]
} , {
name : 'minute' ,
steps : [ 1 , 2 , 5 , 10 , 30 ]
} , {
name : 'hour' ,
steps : [ 1 , 2 , 3 , 6 , 12 ]
} , {
name : 'day' ,
steps : [ 1 , 2 , 5 ]
} , {
name : 'week' ,
maxStep : 4
} , {
name : 'month' ,
maxStep : 3
} , {
name : 'quarter' ,
maxStep : 4
} , {
name : 'year' ,
maxStep : false
} ]
} ;
var defaultConfig = {
position : "bottom" ,
time : {
parser : false , // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
format : false , // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
unit : false , // false == automatic or override with week, month, year, etc.
round : false , // none, or override with week, month, year, etc.
displayFormat : false , // DEPRECATED
isoWeekday : false , // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
displayFormats : {
'millisecond' : 'h:mm:ss.SSS a' , // 11:20:01.123 AM,
'second' : 'h:mm:ss a' , // 11:20:01 AM
'minute' : 'h:mm:ss a' , // 11:20:01 AM
'hour' : 'MMM D, hA' , // Sept 4, 5PM
'day' : 'll' , // Sep 4 2015
'week' : 'll' , // Week 46, or maybe "[W]WW - YYYY" ?
'month' : 'MMM YYYY' , // Sept 2015
'quarter' : '[Q]Q - YYYY' , // Q3
'year' : 'YYYY' // 2015
}
} ,
ticks : {
autoSkip : false
}
} ;
var TimeScale = Chart . Scale . extend ( {
initialize : function ( ) {
if ( ! moment ) {
throw new Error ( 'Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com' ) ;
}
Chart . Scale . prototype . initialize . call ( this ) ;
} ,
getLabelMoment : function ( datasetIndex , index ) {
if ( typeof this . labelMoments [ datasetIndex ] != 'undefined' ) {
return this . labelMoments [ datasetIndex ] [ index ] ;
}
return null ;
} ,
getMomentStartOf : function ( tick ) {
var me = this ;
if ( me . options . time . unit === 'week' && me . options . time . isoWeekday !== false ) {
return tick . clone ( ) . startOf ( 'isoWeek' ) . isoWeekday ( me . options . time . isoWeekday ) ;
} else {
return tick . clone ( ) . startOf ( me . tickUnit ) ;
}
} ,
determineDataLimits : function ( ) {
var me = this ;
me . labelMoments = [ ] ;
// Only parse these once. If the dataset does not have data as x,y pairs, we will use
// these
var scaleLabelMoments = [ ] ;
if ( me . chart . data . labels && me . chart . data . labels . length > 0 ) {
helpers . each ( me . chart . data . labels , function ( label ) {
var labelMoment = me . parseTime ( label ) ;
if ( labelMoment . isValid ( ) ) {
if ( me . options . time . round ) {
labelMoment . startOf ( me . options . time . round ) ;
}
scaleLabelMoments . push ( labelMoment ) ;
}
} , me ) ;
me . firstTick = moment . min . call ( me , scaleLabelMoments ) ;
me . lastTick = moment . max . call ( me , scaleLabelMoments ) ;
} else {
me . firstTick = null ;
me . lastTick = null ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
helpers . each ( me . chart . data . datasets , function ( dataset , datasetIndex ) {
var momentsForDataset = [ ] ;
var datasetVisible = me . chart . isDatasetVisible ( datasetIndex ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( typeof dataset . data [ 0 ] === 'object' && dataset . data [ 0 ] !== null ) {
helpers . each ( dataset . data , function ( value ) {
var labelMoment = me . parseTime ( me . getRightValue ( value ) ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( labelMoment . isValid ( ) ) {
if ( me . options . time . round ) {
labelMoment . startOf ( me . options . time . round ) ;
}
momentsForDataset . push ( labelMoment ) ;
if ( datasetVisible ) {
// May have gone outside the scale ranges, make sure we keep the first and last ticks updated
me . firstTick = me . firstTick !== null ? moment . min ( me . firstTick , labelMoment ) : labelMoment ;
me . lastTick = me . lastTick !== null ? moment . max ( me . lastTick , labelMoment ) : labelMoment ;
}
}
} , me ) ;
} else {
// We have no labels. Use the ones from the scale
momentsForDataset = scaleLabelMoments ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
me . labelMoments . push ( momentsForDataset ) ;
} , me ) ;
// Set these after we've done all the data
if ( me . options . time . min ) {
me . firstTick = me . parseTime ( me . options . time . min ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
if ( me . options . time . max ) {
me . lastTick = me . parseTime ( me . options . time . max ) ;
}
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// We will modify these, so clone for later
me . firstTick = ( me . firstTick || moment ( ) ) . clone ( ) ;
me . lastTick = ( me . lastTick || moment ( ) ) . clone ( ) ;
} ,
buildTicks : function ( ) {
var me = this ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
me . ctx . save ( ) ;
var tickFontSize = helpers . getValueOrDefault ( me . options . ticks . fontSize , Chart . defaults . global . defaultFontSize ) ;
var tickFontStyle = helpers . getValueOrDefault ( me . options . ticks . fontStyle , Chart . defaults . global . defaultFontStyle ) ;
var tickFontFamily = helpers . getValueOrDefault ( me . options . ticks . fontFamily , Chart . defaults . global . defaultFontFamily ) ;
var tickLabelFont = helpers . fontString ( tickFontSize , tickFontStyle , tickFontFamily ) ;
me . ctx . font = tickLabelFont ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
me . ticks = [ ] ;
me . unitScale = 1 ; // How much we scale the unit by, ie 2 means 2x unit per step
me . scaleSizeInUnits = 0 ; // How large the scale is in the base unit (seconds, minutes, etc)
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Set unit override if applicable
if ( me . options . time . unit ) {
me . tickUnit = me . options . time . unit || 'day' ;
me . displayFormat = me . options . time . displayFormats [ me . tickUnit ] ;
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
me . unitScale = helpers . getValueOrDefault ( me . options . time . unitStepSize , 1 ) ;
} else {
// Determine the smallest needed unit of the time
var innerWidth = me . isHorizontal ( ) ? me . width - ( me . paddingLeft + me . paddingRight ) : me . height - ( me . paddingTop + me . paddingBottom ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Crude approximation of what the label length might be
var tempFirstLabel = me . tickFormatFunction ( me . firstTick , 0 , [ ] ) ;
var tickLabelWidth = me . ctx . measureText ( tempFirstLabel ) . width ;
var cosRotation = Math . cos ( helpers . toRadians ( me . options . ticks . maxRotation ) ) ;
var sinRotation = Math . sin ( helpers . toRadians ( me . options . ticks . maxRotation ) ) ;
tickLabelWidth = ( tickLabelWidth * cosRotation ) + ( tickFontSize * sinRotation ) ;
var labelCapacity = innerWidth / ( tickLabelWidth ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Start as small as possible
me . tickUnit = 'millisecond' ;
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
me . displayFormat = me . options . time . displayFormats [ me . tickUnit ] ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
var unitDefinitionIndex = 0 ;
var unitDefinition = time . units [ unitDefinitionIndex ] ;
// While we aren't ideal and we don't have units left
while ( unitDefinitionIndex < time . units . length ) {
// Can we scale this unit. If `false` we can scale infinitely
me . unitScale = 1 ;
if ( helpers . isArray ( unitDefinition . steps ) && Math . ceil ( me . scaleSizeInUnits / labelCapacity ) < helpers . max ( unitDefinition . steps ) ) {
// Use one of the prefedined steps
for ( var idx = 0 ; idx < unitDefinition . steps . length ; ++ idx ) {
if ( unitDefinition . steps [ idx ] >= Math . ceil ( me . scaleSizeInUnits / labelCapacity ) ) {
me . unitScale = helpers . getValueOrDefault ( me . options . time . unitStepSize , unitDefinition . steps [ idx ] ) ;
break ;
}
}
break ;
} else if ( ( unitDefinition . maxStep === false ) || ( Math . ceil ( me . scaleSizeInUnits / labelCapacity ) < unitDefinition . maxStep ) ) {
// We have a max step. Scale this unit
me . unitScale = helpers . getValueOrDefault ( me . options . time . unitStepSize , Math . ceil ( me . scaleSizeInUnits / labelCapacity ) ) ;
break ;
} else {
// Move to the next unit up
++ unitDefinitionIndex ;
unitDefinition = time . units [ unitDefinitionIndex ] ;
me . tickUnit = unitDefinition . name ;
var leadingUnitBuffer = me . firstTick . diff ( me . getMomentStartOf ( me . firstTick ) , me . tickUnit , true ) ;
var trailingUnitBuffer = me . getMomentStartOf ( me . lastTick . clone ( ) . add ( 1 , me . tickUnit ) ) . diff ( me . lastTick , me . tickUnit , true ) ;
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) + leadingUnitBuffer + trailingUnitBuffer ;
me . displayFormat = me . options . time . displayFormats [ unitDefinition . name ] ;
}
2013-12-15 13:55:50 +01:00
}
}
2016-09-11 16:30:23 +02:00
var roundedStart ;
// Only round the first tick if we have no hard minimum
if ( ! me . options . time . min ) {
me . firstTick = me . getMomentStartOf ( me . firstTick ) ;
roundedStart = me . firstTick ;
} else {
roundedStart = me . getMomentStartOf ( me . firstTick ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
// Only round the last tick if we have no hard maximum
if ( ! me . options . time . max ) {
var roundedEnd = me . getMomentStartOf ( me . lastTick ) ;
var delta = roundedEnd . diff ( me . lastTick , me . tickUnit , true ) ;
if ( delta < 0 ) {
// Do not use end of because we need me to be in the next time unit
me . lastTick = me . getMomentStartOf ( me . lastTick . add ( 1 , me . tickUnit ) ) ;
} else if ( delta >= 0 ) {
me . lastTick = roundedEnd ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
// Tick displayFormat override
if ( me . options . time . displayFormat ) {
me . displayFormat = me . options . time . displayFormat ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
// first tick. will have been rounded correctly if options.time.min is not specified
me . ticks . push ( me . firstTick . clone ( ) ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
for ( var i = 1 ; i <= me . scaleSizeInUnits ; ++ i ) {
var newTick = roundedStart . clone ( ) . add ( i , me . tickUnit ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
// Are we greater than the max time
if ( me . options . time . max && newTick . diff ( me . lastTick , me . tickUnit , true ) >= 0 ) {
break ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
if ( i % me . unitScale === 0 ) {
me . ticks . push ( newTick ) ;
2013-12-15 13:55:50 +01:00
}
}
2016-09-11 16:30:23 +02:00
// Always show the right tick
var diff = me . ticks [ me . ticks . length - 1 ] . diff ( me . lastTick , me . tickUnit ) ;
if ( diff !== 0 || me . scaleSizeInUnits === 0 ) {
// this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
// but the last tick was not rounded.
if ( me . options . time . max ) {
me . ticks . push ( me . lastTick . clone ( ) ) ;
me . scaleSizeInUnits = me . lastTick . diff ( me . ticks [ 0 ] , me . tickUnit , true ) ;
} else {
me . ticks . push ( me . lastTick . clone ( ) ) ;
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
2013-12-15 13:55:50 +01:00
}
}
2016-09-11 16:30:23 +02:00
me . ctx . restore ( ) ;
} ,
// Get tooltip label
getLabelForIndex : function ( index , datasetIndex ) {
var me = this ;
var label = me . chart . data . labels && index < me . chart . data . labels . length ? me . chart . data . labels [ index ] : '' ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( typeof me . chart . data . datasets [ datasetIndex ] . data [ 0 ] === 'object' ) {
label = me . getRightValue ( me . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
// Format nicely
if ( me . options . time . tooltipFormat ) {
label = me . parseTime ( label ) . format ( me . options . time . tooltipFormat ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
return label ;
} ,
// Function to format an individual tick mark
tickFormatFunction : function ( tick , index , ticks ) {
var formattedTick = tick . format ( this . displayFormat ) ;
var tickOpts = this . options . ticks ;
var callback = helpers . getValueOrDefault ( tickOpts . callback , tickOpts . userCallback ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( callback ) {
return callback ( formattedTick , index , ticks ) ;
2013-12-15 13:55:50 +01:00
} else {
2016-09-11 16:30:23 +02:00
return formattedTick ;
}
} ,
convertTicksToLabels : function ( ) {
var me = this ;
me . tickMoments = me . ticks ;
me . ticks = me . ticks . map ( me . tickFormatFunction , me ) ;
} ,
getPixelForValue : function ( value , index , datasetIndex ) {
var me = this ;
if ( ! value || ! value . isValid ) {
// not already a moment object
value = me . parseTime ( me . getRightValue ( value ) ) ;
}
var labelMoment = value && value . isValid && value . isValid ( ) ? value : me . getLabelMoment ( datasetIndex , index ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( labelMoment ) {
var offset = labelMoment . diff ( me . firstTick , me . tickUnit , true ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
var decimal = offset !== 0 ? offset / me . scaleSizeInUnits : offset ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
if ( me . isHorizontal ( ) ) {
var innerWidth = me . width - ( me . paddingLeft + me . paddingRight ) ;
var valueOffset = ( innerWidth * decimal ) + me . paddingLeft ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
return me . left + Math . round ( valueOffset ) ;
} else {
var innerHeight = me . height - ( me . paddingTop + me . paddingBottom ) ;
var heightOffset = ( innerHeight * decimal ) + me . paddingTop ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
return me . top + Math . round ( heightOffset ) ;
}
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
} ,
getPixelForTick : function ( index ) {
return this . getPixelForValue ( this . tickMoments [ index ] , null , null ) ;
} ,
getValueForPixel : function ( pixel ) {
var me = this ;
var innerDimension = me . isHorizontal ( ) ? me . width - ( me . paddingLeft + me . paddingRight ) : me . height - ( me . paddingTop + me . paddingBottom ) ;
var offset = ( pixel - ( me . isHorizontal ( ) ? me . left + me . paddingLeft : me . top + me . paddingTop ) ) / innerDimension ;
offset *= me . scaleSizeInUnits ;
return me . firstTick . clone ( ) . add ( moment . duration ( offset , me . tickUnit ) . asSeconds ( ) , 'seconds' ) ;
} ,
parseTime : function ( label ) {
var me = this ;
if ( typeof me . options . time . parser === 'string' ) {
return moment ( label , me . options . time . parser ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
if ( typeof me . options . time . parser === 'function' ) {
return me . options . time . parser ( label ) ;
}
// Date objects
if ( typeof label . getMonth === 'function' || typeof label === 'number' ) {
return moment ( label ) ;
}
// Moment support
if ( label . isValid && label . isValid ( ) ) {
return label ;
}
// Custom parsing (return an instance of moment)
if ( typeof me . options . time . format !== 'string' && me . options . time . format . call ) {
console . warn ( "options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale" ) ;
return me . options . time . format ( label ) ;
}
// Moment format parsing
return moment ( label , me . options . time . format ) ;
2013-12-15 13:55:50 +01:00
}
2016-09-11 16:30:23 +02:00
} ) ;
Chart . scaleService . registerScaleType ( "time" , TimeScale , defaultConfig ) ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
} ;
2013-12-15 13:55:50 +01:00
2016-09-11 16:30:23 +02:00
} , { "1" : 1 } ] } , { } , [ 7 ] ) ( 7 )
} ) ;