2016-03-25 01:18:05 -07:00
/ * !
* Chart . js
* http : //chartjs.org/
2020-04-06 21:17:26 -07:00
* Version : 2.5 . 0
2016-03-25 01:18:05 -07:00
*
2020-04-06 21:17:26 -07:00
* Copyright 2017 Nick Downie
2016-03-25 01:18:05 -07:00
* Released under the MIT license
2020-04-06 21:17:26 -07:00
* https : //github.com/chartjs/Chart.js/blob/master/LICENSE.md
2016-03-25 01:18:05 -07:00
* /
2020-04-06 21:17:26 -07: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 ) {
} , { } ] , 2 : [ function ( require , module , exports ) {
/* MIT license */
var colorNames = require ( 6 ) ;
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 ( 28 ) ( ) ;
require ( 26 ) ( Chart ) ;
require ( 42 ) ( Chart ) ;
require ( 22 ) ( Chart ) ;
require ( 31 ) ( Chart ) ;
require ( 25 ) ( Chart ) ;
require ( 21 ) ( Chart ) ;
require ( 23 ) ( Chart ) ;
require ( 24 ) ( Chart ) ;
require ( 29 ) ( Chart ) ;
require ( 33 ) ( Chart ) ;
require ( 34 ) ( Chart ) ;
require ( 32 ) ( Chart ) ;
require ( 35 ) ( Chart ) ;
require ( 30 ) ( Chart ) ;
require ( 27 ) ( Chart ) ;
require ( 36 ) ( Chart ) ;
require ( 37 ) ( Chart ) ;
require ( 38 ) ( Chart ) ;
require ( 39 ) ( Chart ) ;
require ( 40 ) ( Chart ) ;
require ( 45 ) ( Chart ) ;
require ( 43 ) ( Chart ) ;
require ( 44 ) ( Chart ) ;
require ( 46 ) ( Chart ) ;
require ( 47 ) ( Chart ) ;
require ( 48 ) ( 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 , "42" : 42 , "43" : 43 , "44" : 44 , "45" : 45 , "46" : 46 , "47" : 47 , "48" : 48 , "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 ) {
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
Chart . Line = function ( context , config ) {
config . type = 'line' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
return new Chart ( context , config ) ;
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} , { } ] , 12 : [ function ( require , module , exports ) {
'use strict' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
module . exports = function ( Chart ) {
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
Chart . PolarArea = function ( context , config ) {
config . type = 'polarArea' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
return new Chart ( context , config ) ;
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} , { } ] , 13 : [ function ( require , module , exports ) {
'use strict' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
module . exports = function ( Chart ) {
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
Chart . Radar = function ( context , config ) {
config . type = 'radar' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
return new Chart ( context , config ) ;
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} , { } ] , 14 : [ function ( require , module , exports ) {
'use strict' ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
module . exports = function ( Chart ) {
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
var defaultConfig = {
hover : {
mode : 'single'
} ,
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
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'
} ]
} ,
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
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 + ')' ;
}
}
}
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
// Register the default config for this type
Chart . defaults . scatter = defaultConfig ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
// Scatter charts use line controllers
Chart . controllers . scatter = Chart . controllers . line ;
Chart . Scatter = function ( context , config ) {
config . type = 'scatter' ;
return new Chart ( context , config ) ;
} ;
2016-03-25 01:18:05 -07:00
2020-04-06 21:17:26 -07:00
} ;
} , { } ] , 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 ) ;
var me = this ;
var meta = me . getMeta ( ) ;
var dataset = me . getDataset ( ) ;
meta . stack = dataset . stack ;
// Use this to indicate that this is a bar dataset.
meta . bar = true ;
} ,
// Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible
getStackCount : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var stacks = [ ] ;
helpers . each ( me . chart . data . datasets , function ( dataset , datasetIndex ) {
var dsMeta = me . chart . getDatasetMeta ( datasetIndex ) ;
if ( dsMeta . bar && me . chart . isDatasetVisible ( datasetIndex ) &&
( yScale . options . stacked === false ||
( yScale . options . stacked === true && stacks . indexOf ( dsMeta . stack ) === - 1 ) ||
( yScale . options . stacked === undefined && ( dsMeta . stack === undefined || stacks . indexOf ( dsMeta . stack ) === - 1 ) ) ) ) {
stacks . push ( dsMeta . stack ) ;
}
} , me ) ;
return stacks . length ;
} ,
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 ( ) ;
rectangle . _xScale = xScale ;
rectangle . _yScale = yScale ;
rectangle . _datasetIndex = me . index ;
rectangle . _index = index ;
var ruler = me . getRuler ( index ) ; // The index argument for compatible
rectangle . _model = {
x : me . calculateBarX ( index , me . index , ruler ) ,
y : reset ? scaleBase : me . calculateBarY ( index , me . index ) ,
// Tooltip
label : me . chart . data . labels [ index ] ,
datasetLabel : dataset . label ,
// Appearance
horizontal : false ,
base : reset ? scaleBase : me . calculateBarBase ( me . index , index ) ,
width : me . calculateBarWidth ( ruler ) ,
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 = yScale . getBaseValue ( ) ;
var original = base ;
if ( ( yScale . options . stacked === true ) ||
( yScale . options . stacked === undefined && meta . stack !== undefined ) ) {
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 ) &&
meta . stack === currentDsMeta . stack ) {
var currentVal = Number ( currentDs . data [ index ] ) ;
base += value < 0 ? Math . min ( currentVal , original ) : Math . max ( currentVal , original ) ;
}
}
return yScale . getPixelForValue ( base ) ;
}
return yScale . getBasePixel ( ) ;
} ,
getRuler : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var stackCount = me . getStackCount ( ) ;
var tickWidth = xScale . width / xScale . ticks . length ;
var categoryWidth = tickWidth * xScale . options . categoryPercentage ;
var categorySpacing = ( tickWidth - ( tickWidth * xScale . options . categoryPercentage ) ) / 2 ;
var fullBarWidth = categoryWidth / stackCount ;
var barWidth = fullBarWidth * xScale . options . barPercentage ;
var barSpacing = fullBarWidth - ( fullBarWidth * xScale . options . barPercentage ) ;
return {
stackCount : stackCount ,
tickWidth : tickWidth ,
categoryWidth : categoryWidth ,
categorySpacing : categorySpacing ,
fullBarWidth : fullBarWidth ,
barWidth : barWidth ,
barSpacing : barSpacing
} ;
} ,
calculateBarWidth : function ( ruler ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
if ( xScale . options . barThickness ) {
return xScale . options . barThickness ;
}
return ruler . barWidth ;
} ,
// Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible
getStackIndex : function ( datasetIndex ) {
var me = this ;
var meta = me . chart . getDatasetMeta ( datasetIndex ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var dsMeta , j ;
var stacks = [ meta . stack ] ;
for ( j = 0 ; j < datasetIndex ; ++ j ) {
dsMeta = this . chart . getDatasetMeta ( j ) ;
if ( dsMeta . bar && this . chart . isDatasetVisible ( j ) &&
( yScale . options . stacked === false ||
( yScale . options . stacked === true && stacks . indexOf ( dsMeta . stack ) === - 1 ) ||
( yScale . options . stacked === undefined && ( dsMeta . stack === undefined || stacks . indexOf ( dsMeta . stack ) === - 1 ) ) ) ) {
stacks . push ( dsMeta . stack ) ;
}
}
return stacks . length - 1 ;
} ,
calculateBarX : function ( index , datasetIndex , ruler ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var stackIndex = me . getStackIndex ( datasetIndex ) ;
var leftTick = xScale . getPixelForValue ( null , index , datasetIndex , me . chart . isCombo ) ;
leftTick -= me . chart . isCombo ? ( ruler . tickWidth / 2 ) : 0 ;
return leftTick +
( ruler . barWidth / 2 ) +
ruler . categorySpacing +
( ruler . barWidth * stackIndex ) +
( ruler . barSpacing / 2 ) +
( ruler . barSpacing * stackIndex ) ;
} ,
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 ||
( yScale . options . stacked === undefined && meta . stack !== undefined ) ) {
var base = yScale . getBaseValue ( ) ;
var sumPos = base ,
sumNeg = base ;
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 ) &&
meta . stack === dsMeta . stack ) {
var stackedVal = Number ( ds . data [ index ] ) ;
if ( stackedVal < 0 ) {
sumNeg += stackedVal || 0 ;
} else {
sumPos += stackedVal || 0 ;
}
}
}
if ( value < 0 ) {
return yScale . getPixelForValue ( sumNeg + value ) ;
}
return yScale . getPixelForValue ( sumPos + value ) ;
}
return yScale . getPixelForValue ( value ) ;
} ,
draw : function ( ease ) {
var me = this ;
var easingDecimal = ease || 1 ;
var metaData = me . getMeta ( ) . data ;
var dataset = me . getDataset ( ) ;
var i , len ;
Chart . canvasHelpers . clipArea ( me . chart . chart . ctx , me . chart . chartArea ) ;
for ( i = 0 , len = metaData . length ; i < len ; ++ i ) {
var d = dataset . data [ i ] ;
if ( d !== null && d !== undefined && ! isNaN ( d ) ) {
metaData [ i ] . transition ( easingDecimal ) . draw ( ) ;
}
}
Chart . canvasHelpers . unclipArea ( me . chart . chart . ctx ) ;
} ,
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 ( {
// Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible
getStackCount : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var stacks = [ ] ;
helpers . each ( me . chart . data . datasets , function ( dataset , datasetIndex ) {
var dsMeta = me . chart . getDatasetMeta ( datasetIndex ) ;
if ( dsMeta . bar && me . chart . isDatasetVisible ( datasetIndex ) &&
( xScale . options . stacked === false ||
( xScale . options . stacked === true && stacks . indexOf ( dsMeta . stack ) === - 1 ) ||
( xScale . options . stacked === undefined && ( dsMeta . stack === undefined || stacks . indexOf ( dsMeta . stack ) === - 1 ) ) ) ) {
stacks . push ( dsMeta . stack ) ;
}
} , me ) ;
return stacks . length ;
} ,
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 ;
rectangle . _xScale = xScale ;
rectangle . _yScale = yScale ;
rectangle . _datasetIndex = me . index ;
rectangle . _index = index ;
var ruler = me . getRuler ( index ) ; // The index argument for compatible
rectangle . _model = {
x : reset ? scaleBase : me . calculateBarX ( index , me . index ) ,
y : me . calculateBarY ( index , me . index , ruler ) ,
// Tooltip
label : me . chart . data . labels [ index ] ,
datasetLabel : dataset . label ,
// Appearance
horizontal : true ,
base : reset ? scaleBase : me . calculateBarBase ( me . index , index ) ,
height : me . calculateBarHeight ( ruler ) ,
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 xScale = me . getScaleForId ( meta . xAxisID ) ;
var base = xScale . getBaseValue ( ) ;
var originalBase = base ;
if ( xScale . options . stacked ||
( xScale . options . stacked === undefined && meta . stack !== undefined ) ) {
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 ) &&
meta . stack === currentDsMeta . stack ) {
var currentVal = Number ( currentDs . data [ index ] ) ;
base += value < 0 ? Math . min ( currentVal , originalBase ) : Math . max ( currentVal , originalBase ) ;
}
}
return xScale . getPixelForValue ( base ) ;
}
return xScale . getBasePixel ( ) ;
} ,
getRuler : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var stackCount = me . getStackCount ( ) ;
var tickHeight = yScale . height / yScale . ticks . length ;
var categoryHeight = tickHeight * yScale . options . categoryPercentage ;
var categorySpacing = ( tickHeight - ( tickHeight * yScale . options . categoryPercentage ) ) / 2 ;
var fullBarHeight = categoryHeight / stackCount ;
var barHeight = fullBarHeight * yScale . options . barPercentage ;
var barSpacing = fullBarHeight - ( fullBarHeight * yScale . options . barPercentage ) ;
return {
stackCount : stackCount ,
tickHeight : tickHeight ,
categoryHeight : categoryHeight ,
categorySpacing : categorySpacing ,
fullBarHeight : fullBarHeight ,
barHeight : barHeight ,
barSpacing : barSpacing
} ;
} ,
calculateBarHeight : function ( ruler ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
if ( yScale . options . barThickness ) {
return yScale . options . barThickness ;
}
return ruler . barHeight ;
} ,
// Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible
getStackIndex : function ( datasetIndex ) {
var me = this ;
var meta = me . chart . getDatasetMeta ( datasetIndex ) ;
var xScale = me . getScaleForId ( meta . xAxisID ) ;
var dsMeta , j ;
var stacks = [ meta . stack ] ;
for ( j = 0 ; j < datasetIndex ; ++ j ) {
dsMeta = this . chart . getDatasetMeta ( j ) ;
if ( dsMeta . bar && this . chart . isDatasetVisible ( j ) &&
( xScale . options . stacked === false ||
( xScale . options . stacked === true && stacks . indexOf ( dsMeta . stack ) === - 1 ) ||
( xScale . options . stacked === undefined && ( dsMeta . stack === undefined || stacks . indexOf ( dsMeta . stack ) === - 1 ) ) ) ) {
stacks . push ( dsMeta . stack ) ;
}
}
return stacks . length - 1 ;
} ,
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 ||
( xScale . options . stacked === undefined && meta . stack !== undefined ) ) {
var base = xScale . getBaseValue ( ) ;
var sumPos = base ,
sumNeg = base ;
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 ) &&
meta . stack === dsMeta . stack ) {
var stackedVal = Number ( ds . data [ index ] ) ;
if ( stackedVal < 0 ) {
sumNeg += stackedVal || 0 ;
} else {
sumPos += stackedVal || 0 ;
}
}
}
if ( value < 0 ) {
return xScale . getPixelForValue ( sumNeg + value ) ;
}
return xScale . getPixelForValue ( sumPos + value ) ;
}
return xScale . getPixelForValue ( value ) ;
} ,
calculateBarY : function ( index , datasetIndex , ruler ) {
var me = this ;
var meta = me . getMeta ( ) ;
var yScale = me . getScaleForId ( meta . yAxisID ) ;
var stackIndex = me . getStackIndex ( datasetIndex ) ;
var topTick = yScale . getPixelForValue ( null , index , datasetIndex , me . chart . isCombo ) ;
topTick -= me . chart . isCombo ? ( ruler . tickHeight / 2 ) : 0 ;
return topTick +
( ruler . barHeight / 2 ) +
ruler . categorySpacing +
( ruler . barHeight * stackIndex ) +
( ruler . barSpacing / 2 ) +
( ruler . barSpacing * stackIndex ) ;
}
} ) ;
} ;
} , { } ] , 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 + ': (' + tooltipItem . xLabel + ', ' + tooltipItem . yLabel + ', ' + 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
} ;
} ) ;
}
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 ) ;
// toggle visibility of index if exists
if ( meta . data [ index ] ) {
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 ) {
var dataLabel = data . labels [ tooltipItem . index ] ;
var value = ': ' + data . datasets [ tooltipItem . datasetIndex ] . data [ tooltipItem . index ] ;
if ( helpers . isArray ( dataLabel ) ) {
// show value on first line of multiline label
// need to clone because we are changing the value
dataLabel = dataLabel . slice ( ) ;
dataLabel [ 0 ] += value ;
} else {
dataLabel += value ;
}
return dataLabel ;
}
}
}
} ;
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 ) : 0 , 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 = Math . max ( me . outerRadius - chart . radiusLength , 0 ) ;
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 ) ;
}
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 ,
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 lines 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 ( ! isNaN ( custom . borderWidth ) ) {
borderWidth = custom . borderWidth ;
} else if ( ! isNaN ( dataset . pointBorderWidth ) ) {
borderWidth = helpers . getValueAtIndexOrDefault ( dataset . pointBorderWidth , index , borderWidth ) ;
} else if ( ! isNaN ( 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 ) ;
}
return yScale . getPixelForValue ( sumPos + rightValue ) ;
}
return yScale . getPixelForValue ( value ) ;
} ,
updateBezierControlPoints : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var area = me . chart . chartArea ;
var points = ( meta . data || [ ] ) ;
var i , ilen , point , model , controlPoints ;
// Only consider points that are drawn in case the spanGaps option is used
if ( meta . dataset . _model . spanGaps ) {
points = points . filter ( function ( pt ) {
return ! pt . _model . skip ;
} ) ;
}
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 ) ;
}
Chart . canvasHelpers . clipArea ( me . chart . chart . ctx , me . chart . chartArea ) ;
// Transition and Draw the line
if ( lineEnabled ( me . getDataset ( ) , me . chart . options ) ) {
meta . dataset . transition ( easingDecimal ) . draw ( ) ;
}
Chart . canvasHelpers . unclipArea ( me . chart . chart . ctx ) ;
// Draw the points
for ( i = 0 , ilen = points . length ; i < ilen ; ++ i ) {
points [ i ] . draw ( me . chart . chartArea ) ;
}
} ,
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 ] + '"></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 . 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
} ;
} ) ;
}
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 ;
}
return 0 ;
}
} ) ;
} ;
} , { } ] , 20 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . radar = {
aspectRatio : 1 ,
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 ,
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 . lineTension , 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 ,
/ * *
* @ function Chart . animationService . addAnimation
* @ param chartInstance { ChartController } the chart to animate
* @ param animationObject { IAnimation } the animation that we will animate
* @ param duration { Number } length of animation in ms
* @ param lazy { Boolean } if true , the chart is not marked as animating to enable more responsive interactions
* /
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 'rectRounded' :
var offset = radius / Math . SQRT2 ;
var leftX = x - offset ;
var topY = y - offset ;
var sideSize = Math . SQRT2 * radius ;
Chart . helpers . drawRoundedRectangle ( ctx , leftX , topY , sideSize , sideSize , radius / 2 ) ;
ctx . fill ( ) ;
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 ( ) ;
} ;
helpers . clipArea = function ( ctx , clipArea ) {
ctx . save ( ) ;
ctx . beginPath ( ) ;
ctx . rect ( clipArea . left , clipArea . top , clipArea . right - clipArea . left , clipArea . bottom - clipArea . top ) ;
ctx . clip ( ) ;
} ;
helpers . unclipArea = function ( ctx ) {
ctx . restore ( ) ;
} ;
} ;
} , { } ] , 23 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var plugins = Chart . plugins ;
var platform = Chart . platform ;
// 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 = { } ;
/ * *
* Initializes the given config with global and chart default values .
* /
function initConfig ( config ) {
config = config || { } ;
// Do NOT use configMerge() for the data object because this method merges arrays
// and so would change references to labels and datasets, preventing data updates.
var data = config . data = config . data || { } ;
data . datasets = data . datasets || [ ] ;
data . labels = data . labels || [ ] ;
config . options = helpers . configMerge (
Chart . defaults . global ,
Chart . defaults [ config . type ] ,
config . options || { } ) ;
return config ;
}
/ * *
* Updates the config of the chart
* @ param chart { Chart . Controller } chart to update the options for
* /
function updateConfig ( chart ) {
var newOptions = chart . options ;
// Update Scale(s) with options
if ( newOptions . scale ) {
chart . scale . options = newOptions . scale ;
} else if ( newOptions . scales ) {
newOptions . scales . xAxes . concat ( newOptions . scales . yAxes ) . forEach ( function ( scaleOptions ) {
chart . scales [ scaleOptions . id ] . options = scaleOptions ;
} ) ;
}
// Tooltip
chart . tooltip . _options = newOptions . tooltips ;
}
/ * *
* @ class Chart . Controller
* The main controller of a chart .
* /
Chart . Controller = function ( item , config , instance ) {
var me = this ;
config = initConfig ( config ) ;
var context = platform . acquireContext ( item , config ) ;
var canvas = context && context . canvas ;
var height = canvas && canvas . height ;
var width = canvas && canvas . width ;
instance . ctx = context ;
instance . canvas = canvas ;
instance . config = config ;
instance . width = width ;
instance . height = height ;
instance . aspectRatio = height ? width / height : null ;
me . id = helpers . uid ( ) ;
me . chart = instance ;
me . config = config ;
me . options = config . options ;
me . _bufferedRender = false ;
// Add the chart instance to the global namespace
Chart . instances [ me . id ] = me ;
Object . defineProperty ( me , 'data' , {
get : function ( ) {
return me . config . data ;
}
} ) ;
if ( ! context || ! canvas ) {
// The given item is not a compatible context2d element, let's return before finalizing
// the chart initialization but after setting basic chart / controller properties that
// can help to figure out that the chart is not valid (e.g chart.canvas !== null);
// https://github.com/chartjs/Chart.js/issues/2807
console . error ( "Failed to create chart: can't acquire context from the given item" ) ;
return me ;
}
me . initialize ( ) ;
me . update ( ) ;
return me ;
} ;
helpers . extend ( Chart . Controller . prototype , /** @lends Chart.Controller.prototype */ {
initialize : function ( ) {
var me = this ;
// Before init plugin notification
plugins . notify ( me , 'beforeInit' ) ;
helpers . retinaScale ( me . chart ) ;
me . bindEvents ( ) ;
if ( me . options . responsive ) {
// Initial resize before chart draws (must be silent to preserve initial animations).
me . resize ( true ) ;
}
// Make sure scales have IDs and are built before we build any controllers.
me . ensureScalesHaveIDs ( ) ;
me . buildScales ( ) ;
me . initToolTip ( ) ;
// After init plugin notification
plugins . notify ( me , 'afterInit' ) ;
return me ;
} ,
clear : function ( ) {
helpers . clear ( this . chart ) ;
return this ;
} ,
stop : function ( ) {
// Stops any current animation loop occurring
Chart . animationService . cancelAnimation ( this ) ;
return this ;
} ,
resize : function ( silent ) {
var me = this ;
var chart = me . chart ;
var options = me . options ;
var canvas = chart . canvas ;
var aspectRatio = ( options . maintainAspectRatio && chart . aspectRatio ) || null ;
// the canvas render width and height will be casted to integers so make sure that
// the canvas display style uses the same integer values to avoid blurring effect.
var newWidth = Math . floor ( helpers . getMaximumWidth ( canvas ) ) ;
var newHeight = Math . floor ( aspectRatio ? newWidth / aspectRatio : helpers . getMaximumHeight ( canvas ) ) ;
if ( chart . width === newWidth && chart . height === newHeight ) {
return ;
}
canvas . width = chart . width = newWidth ;
canvas . height = chart . height = newHeight ;
canvas . style . width = newWidth + 'px' ;
canvas . style . height = newHeight + 'px' ;
helpers . retinaScale ( chart ) ;
if ( ! silent ) {
// Notify any plugins about the resize
var newSize = { width : newWidth , height : newHeight } ;
plugins . notify ( me , 'resize' , [ newSize ] ) ;
// Notify of resize
if ( me . options . onResize ) {
me . options . onResize ( me , newSize ) ;
}
me . stop ( ) ;
me . update ( me . options . responsiveAnimationDuration ) ;
}
} ,
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 ) ;
} ,
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 ;
} ,
/ * *
* Reset the elements of all datasets
* @ private
* /
resetElements : function ( ) {
var me = this ;
helpers . each ( me . data . datasets , function ( dataset , datasetIndex ) {
me . getDatasetMeta ( datasetIndex ) . controller . reset ( ) ;
} , me ) ;
} ,
/ * *
* Resets the chart back to it ' s state before the initial animation
* /
reset : function ( ) {
this . resetElements ( ) ;
this . tooltip . initialize ( ) ;
} ,
update : function ( animationDuration , lazy ) {
var me = this ;
updateConfig ( me ) ;
if ( plugins . notify ( me , 'beforeUpdate' ) === false ) {
return ;
}
// 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 ) ;
me . updateLayout ( ) ;
// 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
plugins . notify ( me , 'afterUpdate' ) ;
if ( me . _bufferedRender ) {
me . _bufferedRequest = {
lazy : lazy ,
duration : animationDuration
} ;
} else {
me . render ( animationDuration , lazy ) ;
}
} ,
/ * *
* Updates the chart layout unless a plugin returns ` false ` to the ` beforeLayout `
* hook , in which case , plugins will not be called on ` afterLayout ` .
* @ private
* /
updateLayout : function ( ) {
var me = this ;
if ( plugins . notify ( me , 'beforeLayout' ) === false ) {
return ;
}
Chart . layoutService . update ( this , this . chart . width , this . chart . height ) ;
/ * *
* Provided for backward compatibility , use ` afterLayout ` instead .
* @ method IPlugin # afterScaleUpdate
* @ deprecated since version 2.5 . 0
* @ todo remove at version 3
* /
plugins . notify ( me , 'afterScaleUpdate' ) ;
plugins . notify ( me , 'afterLayout' ) ;
} ,
/ * *
* Updates all datasets unless a plugin returns ` false ` to the ` beforeDatasetsUpdate `
* hook , in which case , plugins will not be called on ` afterDatasetsUpdate ` .
* @ private
* /
updateDatasets : function ( ) {
var me = this ;
if ( plugins . notify ( me , 'beforeDatasetsUpdate' ) === false ) {
return ;
}
for ( var i = 0 , ilen = me . data . datasets . length ; i < ilen ; ++ i ) {
me . getDatasetMeta ( i ) . controller . update ( ) ;
}
plugins . notify ( me , 'afterDatasetsUpdate' ) ;
} ,
render : function ( duration , lazy ) {
var me = this ;
if ( plugins . notify ( me , 'beforeRender' ) === false ) {
return ;
}
var animationOptions = me . options . animation ;
var onComplete = function ( ) {
plugins . notify ( me , 'afterRender' ) ;
var callback = animationOptions && animationOptions . onComplete ;
if ( callback && callback . call ) {
callback . call ( me ) ;
}
} ;
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 = onComplete ;
Chart . animationService . addAnimation ( me , animation , duration , lazy ) ;
} else {
me . draw ( ) ;
onComplete ( ) ;
}
return me ;
} ,
draw : function ( easingValue ) {
var me = this ;
me . clear ( ) ;
if ( easingValue === undefined || easingValue === null ) {
easingValue = 1 ;
}
if ( plugins . notify ( me , 'beforeDraw' , [ easingValue ] ) === false ) {
return ;
}
// Draw all the scales
helpers . each ( me . boxes , function ( box ) {
box . draw ( me . chartArea ) ;
} , me ) ;
if ( me . scale ) {
me . scale . draw ( ) ;
}
me . drawDatasets ( easingValue ) ;
// Finally draw the tooltip
me . tooltip . transition ( easingValue ) . draw ( ) ;
plugins . notify ( me , 'afterDraw' , [ easingValue ] ) ;
} ,
/ * *
* Draws all datasets unless a plugin returns ` false ` to the ` beforeDatasetsDraw `
* hook , in which case , plugins will not be called on ` afterDatasetsDraw ` .
* @ private
* /
drawDatasets : function ( easingValue ) {
var me = this ;
if ( plugins . notify ( me , 'beforeDatasetsDraw' , [ easingValue ] ) === false ) {
return ;
}
// 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 ( easingValue ) ;
}
} , me , true ) ;
plugins . notify ( me , 'afterDatasetsDraw' , [ easingValue ] ) ;
} ,
// 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 ) {
return Chart . Interaction . modes . single ( this , e ) ;
} ,
getElementsAtEvent : function ( e ) {
return Chart . Interaction . modes . label ( this , e , { intersect : true } ) ;
} ,
getElementsAtXAxis : function ( e ) {
return Chart . Interaction . modes [ 'x-axis' ] ( this , e , { intersect : true } ) ;
} ,
getElementsAtEventForMode : function ( e , mode , options ) {
var method = Chart . Interaction . modes [ mode ] ;
if ( typeof method === 'function' ) {
return method ( this , e , options ) ;
}
return [ ] ;
} ,
getDatasetAtEvent : function ( e ) {
return Chart . Interaction . modes . dataset ( this , e , { intersect : true } ) ;
} ,
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 ;
var canvas = me . chart . canvas ;
var meta , i , ilen ;
me . stop ( ) ;
// dataset controllers need to cleanup associated data
for ( i = 0 , ilen = me . data . datasets . length ; i < ilen ; ++ i ) {
meta = me . getDatasetMeta ( i ) ;
if ( meta . controller ) {
meta . controller . destroy ( ) ;
meta . controller = null ;
}
}
if ( canvas ) {
me . unbindEvents ( ) ;
helpers . clear ( me . chart ) ;
platform . releaseContext ( me . chart . ctx ) ;
me . chart . canvas = null ;
me . chart . ctx = null ;
}
plugins . notify ( me , 'destroy' ) ;
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 ) ;
me . tooltip . initialize ( ) ;
} ,
/ * *
* @ private
* /
bindEvents : function ( ) {
var me = this ;
var listeners = me . _listeners = { } ;
var listener = function ( ) {
me . eventHandler . apply ( me , arguments ) ;
} ;
helpers . each ( me . options . events , function ( type ) {
platform . addEventListener ( me , type , listener ) ;
listeners [ type ] = listener ;
} ) ;
// Responsiveness is currently based on the use of an iframe, however this method causes
// performance issues and could be troublesome when used with ad blockers. So make sure
// that the user is still able to create a chart without iframe when responsive is false.
// See https://github.com/chartjs/Chart.js/issues/2210
if ( me . options . responsive ) {
listener = function ( ) {
me . resize ( ) ;
} ;
platform . addEventListener ( me , 'resize' , listener ) ;
listeners . resize = listener ;
}
} ,
/ * *
* @ private
* /
unbindEvents : function ( ) {
var me = this ;
var listeners = me . _listeners ;
if ( ! listeners ) {
return ;
}
delete me . _listeners ;
helpers . each ( listeners , function ( listener , type ) {
platform . removeEventListener ( me , type , listener ) ;
} ) ;
} ,
updateHoverStyle : function ( elements , mode , enabled ) {
var method = enabled ? 'setHoverStyle' : 'removeHoverStyle' ;
var element , i , ilen ;
for ( i = 0 , ilen = elements . length ; i < ilen ; ++ i ) {
element = elements [ i ] ;
if ( element ) {
this . getDatasetMeta ( element . _datasetIndex ) . controller [ method ] ( element ) ;
}
}
} ,
/ * *
* @ private
* /
eventHandler : function ( e ) {
var me = this ;
var tooltip = me . tooltip ;
if ( plugins . notify ( me , 'beforeEvent' , [ e ] ) === false ) {
return ;
}
// Buffer any update calls so that renders do not occur
me . _bufferedRender = true ;
me . _bufferedRequest = null ;
var changed = me . handleEvent ( e ) ;
changed |= tooltip && tooltip . handleEvent ( e ) ;
plugins . notify ( me , 'afterEvent' , [ e ] ) ;
var bufferedRequest = me . _bufferedRequest ;
if ( bufferedRequest ) {
// If we have an update that was triggered, we need to do a normal render
me . render ( bufferedRequest . duration , bufferedRequest . lazy ) ;
} else if ( changed && ! me . animating ) {
// If entering, leaving, or changing elements, animate the change via pivot
me . stop ( ) ;
// We only need to render at this point. Updating will cause scales to be
// recomputed generating flicker & using more memory than necessary.
me . render ( me . options . hover . animationDuration , true ) ;
}
me . _bufferedRender = false ;
me . _bufferedRequest = null ;
return me ;
} ,
/ * *
* Handle an event
* @ private
* @ param { IEvent } event the event to handle
* @ return { Boolean } true if the chart needs to re - render
* /
handleEvent : function ( e ) {
var me = this ;
var options = me . options || { } ;
var hoverOptions = options . hover ;
var changed = false ;
me . lastActive = me . lastActive || [ ] ;
// Find Active Elements for hover and tooltips
if ( e . type === 'mouseout' ) {
me . active = [ ] ;
} else {
me . active = me . getElementsAtEventForMode ( e , hoverOptions . mode , hoverOptions ) ;
}
// On Hover hook
if ( hoverOptions . onHover ) {
// Need to call with native event here to not break backwards compatibility
hoverOptions . onHover . call ( me , e . native , me . active ) ;
}
if ( e . type === 'mouseup' || e . type === 'click' ) {
if ( options . onClick ) {
// Use e.native here for backwards compatibility
options . onClick . call ( me , e . native , me . active ) ;
}
}
// 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 ) ;
}
changed = ! helpers . arrayEquals ( me . active , me . lastActive ) ;
// Remember Last Actives
me . lastActive = me . active ;
return changed ;
}
} ) ;
} ;
} , { } ] , 24 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var arrayEvents = [ 'push' , 'pop' , 'shift' , 'splice' , 'unshift' ] ;
/ * *
* Hooks the array methods that add or remove values ( 'push' , pop ', ' shift ', ' splice ' ,
* 'unshift' ) and notify the listener AFTER the array has been altered . Listeners are
* called on the 'onData*' callbacks ( e . g . onDataPush , etc . ) with same arguments .
* /
function listenArrayEvents ( array , listener ) {
if ( array . _chartjs ) {
array . _chartjs . listeners . push ( listener ) ;
return ;
}
Object . defineProperty ( array , '_chartjs' , {
configurable : true ,
enumerable : false ,
value : {
listeners : [ listener ]
}
} ) ;
arrayEvents . forEach ( function ( key ) {
var method = 'onData' + key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) ;
var base = array [ key ] ;
Object . defineProperty ( array , key , {
configurable : true ,
enumerable : false ,
value : function ( ) {
var args = Array . prototype . slice . call ( arguments ) ;
var res = base . apply ( this , args ) ;
helpers . each ( array . _chartjs . listeners , function ( object ) {
if ( typeof object [ method ] === 'function' ) {
object [ method ] . apply ( object , args ) ;
}
} ) ;
return res ;
}
} ) ;
} ) ;
}
/ * *
* Removes the given array event listener and cleanup extra attached properties ( such as
* the _chartjs stub and overridden methods ) if array doesn ' t have any more listeners .
* /
function unlistenArrayEvents ( array , listener ) {
var stub = array . _chartjs ;
if ( ! stub ) {
return ;
}
var listeners = stub . listeners ;
var index = listeners . indexOf ( listener ) ;
if ( index !== - 1 ) {
listeners . splice ( index , 1 ) ;
}
if ( listeners . length > 0 ) {
return ;
}
arrayEvents . forEach ( function ( key ) {
delete array [ key ] ;
} ) ;
delete array . _chartjs ;
}
// Base class for all dataset controllers (line, bar, etc)
Chart . DatasetController = function ( chart , datasetIndex ) {
this . initialize ( 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 ) ;
} ,
/ * *
* @ private
* /
destroy : function ( ) {
if ( this . _data ) {
unlistenArrayEvents ( this . _data , this ) ;
}
} ,
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 ( i ) ;
}
meta . dataset = meta . dataset || me . createMetaDataset ( ) ;
} ,
addElementAndReset : function ( index ) {
var element = this . createMetaData ( index ) ;
this . getMeta ( ) . data . splice ( index , 0 , element ) ;
this . updateElement ( element , index , true ) ;
} ,
buildOrUpdateElements : function ( ) {
var me = this ;
var dataset = me . getDataset ( ) ;
var data = dataset . data || ( dataset . data = [ ] ) ;
// In order to correctly handle data addition/deletion animation (an thus simulate
// real-time charts), we need to monitor these data modifications and synchronize
// the internal meta data accordingly.
if ( me . _data !== data ) {
if ( me . _data ) {
// This case happens when the user replaced the data array instance.
unlistenArrayEvents ( me . _data , me ) ;
}
listenArrayEvents ( data , me ) ;
me . _data = data ;
}
// Re-sync meta data in case the user replaced the data array or if we missed
// any updates and so make sure that we handle number of datapoints changing.
me . resyncElements ( ) ;
} ,
update : helpers . noop ,
draw : function ( ease ) {
var easingDecimal = ease || 1 ;
var i , len ;
var metaData = this . getMeta ( ) . data ;
for ( i = 0 , len = metaData . length ; i < len ; ++ i ) {
metaData [ i ] . 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 ) ;
} ,
/ * *
* @ private
* /
resyncElements : function ( ) {
var me = this ;
var meta = me . getMeta ( ) ;
var data = me . getDataset ( ) . data ;
var numMeta = meta . data . length ;
var numData = data . length ;
if ( numData < numMeta ) {
meta . data . splice ( numData , numMeta - numData ) ;
} else if ( numData > numMeta ) {
me . insertElements ( numMeta , numData - numMeta ) ;
}
} ,
/ * *
* @ private
* /
insertElements : function ( start , count ) {
for ( var i = 0 ; i < count ; ++ i ) {
this . addElementAndReset ( start + i ) ;
}
} ,
/ * *
* @ private
* /
onDataPush : function ( ) {
this . insertElements ( this . getDataset ( ) . data . length - 1 , arguments . length ) ;
} ,
/ * *
* @ private
* /
onDataPop : function ( ) {
this . getMeta ( ) . data . pop ( ) ;
} ,
/ * *
* @ private
* /
onDataShift : function ( ) {
this . getMeta ( ) . data . shift ( ) ;
} ,
/ * *
* @ private
* /
onDataSplice : function ( start , count ) {
this . getMeta ( ) . data . splice ( start , count ) ;
this . insertElements ( start , arguments . length - 2 ) ;
} ,
/ * *
* @ private
* /
onDataUnshift : function ( ) {
this . insertElements ( 0 , arguments . length ) ;
}
} ) ;
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 ) {
var baseHasProperty = base . hasOwnProperty ( key ) ;
var baseVal = baseHasProperty ? base [ key ] : { } ;
if ( key === 'scales' ) {
// Scale config merging is complex. Add our own function here for that
base [ key ] = helpers . scaleMerge ( baseVal , value ) ;
} else if ( key === 'scale' ) {
// Used in polar area & radar charts since there is only one scale
base [ key ] = helpers . configMerge ( baseVal , Chart . scaleService . getScaleDefaults ( value . type ) , value ) ;
} else if ( baseHasProperty
&& typeof baseVal === 'object'
&& ! helpers . isArray ( baseVal )
&& baseVal !== null
&& typeof value === 'object'
&& ! helpers . isArray ( value ) ) {
// If we are overwriting an object with an object, do a merge of the properties.
base [ key ] = helpers . configMerge ( baseVal , 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 ) ;
}
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 me = this ;
var ChartElement = ( extensions && extensions . hasOwnProperty ( 'constructor' ) ) ? extensions . constructor : function ( ) {
return me . apply ( this , arguments ) ;
} ;
var Surrogate = function ( ) {
this . constructor = ChartElement ;
} ;
Surrogate . prototype = me . prototype ;
ChartElement . prototype = new Surrogate ( ) ;
ChartElement . extend = helpers . inherits ;
if ( extensions ) {
helpers . extend ( ChartElement . prototype , extensions ) ;
}
ChartElement . _ _super _ _ = me . 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 . almostWhole = function ( x , epsilon ) {
var rounded = Math . round ( x ) ;
return ( ( ( rounded - epsilon ) < x ) && ( ( rounded + epsilon ) > x ) ) ;
} ;
helpers . max = function ( array ) {
return array . reduce ( function ( max , value ) {
if ( ! isNaN ( value ) ) {
return Math . max ( max , value ) ;
}
return max ;
} , Number . NEGATIVE _INFINITY ) ;
} ;
helpers . min = function ( array ) {
return array . reduce ( function ( min , value ) {
if ( ! isNaN ( value ) ) {
return Math . min ( min , value ) ;
}
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 . distanceBetweenPoints = function ( pt1 , pt2 ) {
return Math . sqrt ( Math . pow ( pt2 . x - pt1 . x , 2 ) + Math . pow ( pt2 . y - pt1 . y , 2 ) ) ;
} ;
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 ) {
var slopeDeltaX = ( pointAfter . model . x - pointCurrent . model . x ) ;
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
pointCurrent . deltaK = slopeDeltaX !== 0 ? ( pointAfter . model . y - pointCurrent . model . y ) / slopeDeltaX : 0 ;
}
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 ) ;
} ;
// Easing functions adapted from Robert Penner's easing equations
// http://www.robertpenner.com/easing/
var easingEffects = helpers . easingEffects = {
linear : function ( t ) {
return t ;
} ,
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 ) ;
}
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 ) ;
} ;
} ( ) ) ;
// -- 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 ;
}
} ;
// 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 paddingLeft = parseInt ( helpers . getStyle ( container , 'padding-left' ) , 10 ) ;
var paddingRight = parseInt ( helpers . getStyle ( container , 'padding-right' ) , 10 ) ;
var w = container . clientWidth - paddingLeft - paddingRight ;
var cw = helpers . getConstraintWidth ( domNode ) ;
return isNaN ( cw ) ? w : Math . min ( w , cw ) ;
} ;
helpers . getMaximumHeight = function ( domNode ) {
var container = domNode . parentNode ;
var paddingTop = parseInt ( helpers . getStyle ( container , 'padding-top' ) , 10 ) ;
var paddingBottom = parseInt ( helpers . getStyle ( container , 'padding-bottom' ) , 10 ) ;
var h = container . clientHeight - paddingTop - paddingBottom ;
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 pixelRatio = chart . currentDevicePixelRatio = window . devicePixelRatio || 1 ;
if ( pixelRatio === 1 ) {
return ;
}
var canvas = chart . canvas ;
var height = chart . height ;
var width = chart . width ;
canvas . height = height * pixelRatio ;
canvas . width = width * pixelRatio ;
chart . ctx . scale ( pixelRatio , pixelRatio ) ;
// If no style has been set on the canvas, the render size is used as display size,
// making the chart visually bigger, so let's enforce it to the "correct" values.
// See https://github.com/chartjs/Chart.js/issues/3575
canvas . style . height = height + 'px' ;
canvas . style . width = width + '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 . error ( 'Color.js not found!' ) ;
return c ;
}
/* global CanvasGradient */
if ( c instanceof CanvasGradient ) {
return color ( Chart . defaults . global . defaultColor ) ;
}
return color ( c ) ;
} ;
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 ( colorValue ) {
/* global CanvasPattern */
return ( colorValue instanceof CanvasPattern ) ?
colorValue :
helpers . color ( colorValue ) . saturate ( 0.5 ) . darken ( 0.1 ) . rgbString ( ) ;
} ;
} ;
} , { "3" : 3 } ] , 27 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
/ * *
* Helper function to get relative position for an event
* @ param { Event | IEvent } event - The event to get the position for
* @ param { Chart } chart - The chart
* @ returns { Point } the event position
* /
function getRelativePosition ( e , chart ) {
if ( e . native ) {
return {
x : e . x ,
y : e . y
} ;
}
return helpers . getRelativePosition ( e , chart ) ;
}
/ * *
* Helper function to traverse all of the visible elements in the chart
* @ param chart { chart } the chart
* @ param handler { Function } the callback to execute for each visible item
* /
function parseVisibleItems ( chart , handler ) {
var datasets = chart . data . datasets ;
var meta , i , j , ilen , jlen ;
for ( i = 0 , ilen = datasets . length ; i < ilen ; ++ i ) {
if ( ! chart . isDatasetVisible ( i ) ) {
continue ;
}
meta = chart . getDatasetMeta ( i ) ;
for ( j = 0 , jlen = meta . data . length ; j < jlen ; ++ j ) {
var element = meta . data [ j ] ;
if ( ! element . _view . skip ) {
handler ( element ) ;
}
}
}
}
/ * *
* Helper function to get the items that intersect the event position
* @ param items { ChartElement [ ] } elements to filter
* @ param position { Point } the point to be nearest to
* @ return { ChartElement [ ] } the nearest items
* /
function getIntersectItems ( chart , position ) {
var elements = [ ] ;
parseVisibleItems ( chart , function ( element ) {
if ( element . inRange ( position . x , position . y ) ) {
elements . push ( element ) ;
}
} ) ;
return elements ;
}
/ * *
* Helper function to get the items nearest to the event position considering all visible items in teh chart
* @ param chart { Chart } the chart to look at elements from
* @ param position { Point } the point to be nearest to
* @ param intersect { Boolean } if true , only consider items that intersect the position
* @ param distanceMetric { Function } Optional function to provide the distance between
* @ return { ChartElement [ ] } the nearest items
* /
function getNearestItems ( chart , position , intersect , distanceMetric ) {
var minDistance = Number . POSITIVE _INFINITY ;
var nearestItems = [ ] ;
if ( ! distanceMetric ) {
distanceMetric = helpers . distanceBetweenPoints ;
}
parseVisibleItems ( chart , function ( element ) {
if ( intersect && ! element . inRange ( position . x , position . y ) ) {
return ;
}
var center = element . getCenterPoint ( ) ;
var distance = distanceMetric ( position , center ) ;
if ( distance < minDistance ) {
nearestItems = [ element ] ;
minDistance = distance ;
} else if ( distance === minDistance ) {
// Can have multiple items at the same distance in which case we sort by size
nearestItems . push ( element ) ;
}
} ) ;
return nearestItems ;
}
function indexMode ( chart , e , options ) {
var position = getRelativePosition ( e , chart . chart ) ;
var distanceMetric = function ( pt1 , pt2 ) {
return Math . abs ( pt1 . x - pt2 . x ) ;
} ;
var items = options . intersect ? getIntersectItems ( chart , position ) : getNearestItems ( chart , position , false , distanceMetric ) ;
var elements = [ ] ;
if ( ! items . length ) {
return [ ] ;
}
chart . data . datasets . forEach ( function ( dataset , datasetIndex ) {
if ( chart . isDatasetVisible ( datasetIndex ) ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ,
element = meta . data [ items [ 0 ] . _index ] ;
// don't count items that are skipped (null data)
if ( element && ! element . _view . skip ) {
elements . push ( element ) ;
}
}
} ) ;
return elements ;
}
/ * *
* @ interface IInteractionOptions
* /
/ * *
* If true , only consider items that intersect the point
* @ name IInterfaceOptions # boolean
* @ type Boolean
* /
/ * *
* Contains interaction related functions
* @ namespace Chart . Interaction
* /
Chart . Interaction = {
// Helper function for different modes
modes : {
single : function ( chart , e ) {
var position = getRelativePosition ( e , chart . chart ) ;
var elements = [ ] ;
parseVisibleItems ( chart , function ( element ) {
if ( element . inRange ( position . x , position . y ) ) {
elements . push ( element ) ;
return elements ;
}
} ) ;
return elements . slice ( 0 , 1 ) ;
} ,
/ * *
* @ function Chart . Interaction . modes . label
* @ deprecated since version 2.4 . 0
* /
label : indexMode ,
/ * *
* Returns items at the same index . If the options . intersect parameter is true , we only return items if we intersect something
* If the options . intersect mode is false , we find the nearest item and return the items at the same index as that item
* @ function Chart . Interaction . modes . index
* @ since v2 . 4.0
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ param options { IInteractionOptions } options to use during interaction
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
index : indexMode ,
/ * *
* Returns items in the same dataset . If the options . intersect parameter is true , we only return items if we intersect something
* If the options . intersect is false , we find the nearest item and return the items in that dataset
* @ function Chart . Interaction . modes . dataset
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ param options { IInteractionOptions } options to use during interaction
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
dataset : function ( chart , e , options ) {
var position = getRelativePosition ( e , chart . chart ) ;
var items = options . intersect ? getIntersectItems ( chart , position ) : getNearestItems ( chart , position , false ) ;
if ( items . length > 0 ) {
items = chart . getDatasetMeta ( items [ 0 ] . _datasetIndex ) . data ;
}
return items ;
} ,
/ * *
* @ function Chart . Interaction . modes . x - axis
* @ deprecated since version 2.4 . 0. Use index mode and intersect == true
* /
'x-axis' : function ( chart , e ) {
return indexMode ( chart , e , true ) ;
} ,
/ * *
* Point mode returns all elements that hit test based on the event position
* of the event
* @ function Chart . Interaction . modes . intersect
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
point : function ( chart , e ) {
var position = getRelativePosition ( e , chart . chart ) ;
return getIntersectItems ( chart , position ) ;
} ,
/ * *
* nearest mode returns the element closest to the point
* @ function Chart . Interaction . modes . intersect
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ param options { IInteractionOptions } options to use
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
nearest : function ( chart , e , options ) {
var position = getRelativePosition ( e , chart . chart ) ;
var nearestItems = getNearestItems ( chart , position , options . intersect ) ;
// We have multiple items at the same distance from the event. Now sort by smallest
if ( nearestItems . length > 1 ) {
nearestItems . sort ( function ( a , b ) {
var sizeA = a . getArea ( ) ;
var sizeB = b . getArea ( ) ;
var ret = sizeA - sizeB ;
if ( ret === 0 ) {
// if equal sort by dataset index
ret = a . _datasetIndex - b . _datasetIndex ;
}
return ret ;
} ) ;
}
// Return only 1 item
return nearestItems . slice ( 0 , 1 ) ;
} ,
/ * *
* x mode returns the elements that hit - test at the current x coordinate
* @ function Chart . Interaction . modes . x
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ param options { IInteractionOptions } options to use
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
x : function ( chart , e , options ) {
var position = getRelativePosition ( e , chart . chart ) ;
var items = [ ] ;
var intersectsItem = false ;
parseVisibleItems ( chart , function ( element ) {
if ( element . inXRange ( position . x ) ) {
items . push ( element ) ;
}
if ( element . inRange ( position . x , position . y ) ) {
intersectsItem = true ;
}
} ) ;
// If we want to trigger on an intersect and we don't have any items
// that intersect the position, return nothing
if ( options . intersect && ! intersectsItem ) {
items = [ ] ;
}
return items ;
} ,
/ * *
* y mode returns the elements that hit - test at the current y coordinate
* @ function Chart . Interaction . modes . y
* @ param chart { chart } the chart we are returning items from
* @ param e { Event } the event we are find things at
* @ param options { IInteractionOptions } options to use
* @ return { Chart . Element [ ] } Array of elements that are under the point . If none are found , an empty array is returned
* /
y : function ( chart , e , options ) {
var position = getRelativePosition ( e , chart . chart ) ;
var items = [ ] ;
var intersectsItem = false ;
parseVisibleItems ( chart , function ( element ) {
if ( element . inYRange ( position . y ) ) {
items . push ( element ) ;
}
if ( element . inRange ( position . x , position . y ) ) {
intersectsItem = true ;
}
} ) ;
// If we want to trigger on an intersect and we don't have any items
// that intersect the position, return nothing
if ( options . intersect && ! intersectsItem ) {
items = [ ] ;
}
return items ;
}
}
} ;
} ;
} , { } ] , 28 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( ) {
// Occupy the global variable of Chart, and create a simple base class
var Chart = function ( item , config ) {
this . controller = new Chart . Controller ( item , config , this ) ;
return this . controller ;
} ;
// 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 : 'nearest' ,
intersect : true ,
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 ;
} ;
} , { } ] , 29 : [ 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 layoutOptions = chartInstance . options . layout ;
var padding = layoutOptions ? layoutOptions . padding : null ;
var leftPadding = 0 ;
var rightPadding = 0 ;
var topPadding = 0 ;
var bottomPadding = 0 ;
if ( ! isNaN ( padding ) ) {
// options.layout.padding is a number. assign to all
leftPadding = padding ;
rightPadding = padding ;
topPadding = padding ;
bottomPadding = padding ;
} else {
leftPadding = padding . left || 0 ;
rightPadding = padding . right || 0 ;
topPadding = padding . top || 0 ;
bottomPadding = padding . bottom || 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 - leftPadding - rightPadding ;
var chartHeight = height - topPadding - bottomPadding ;
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 = [ ] ;
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 ,
} ) ;
}
helpers . each ( leftBoxes . concat ( rightBoxes , topBoxes , bottomBoxes ) , getMinimumBoxSize ) ;
// If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
var maxHorizontalLeftPadding = 0 ;
var maxHorizontalRightPadding = 0 ;
var maxVerticalTopPadding = 0 ;
var maxVerticalBottomPadding = 0 ;
helpers . each ( topBoxes . concat ( bottomBoxes ) , function ( horizontalBox ) {
if ( horizontalBox . getPadding ) {
var boxPadding = horizontalBox . getPadding ( ) ;
maxHorizontalLeftPadding = Math . max ( maxHorizontalLeftPadding , boxPadding . left ) ;
maxHorizontalRightPadding = Math . max ( maxHorizontalRightPadding , boxPadding . right ) ;
}
} ) ;
helpers . each ( leftBoxes . concat ( rightBoxes ) , function ( verticalBox ) {
if ( verticalBox . getPadding ) {
var boxPadding = verticalBox . getPadding ( ) ;
maxVerticalTopPadding = Math . max ( maxVerticalTopPadding , boxPadding . top ) ;
maxVerticalBottomPadding = Math . max ( maxVerticalBottomPadding , boxPadding . bottom ) ;
}
} ) ;
// 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 = leftPadding ;
var totalRightBoxesWidth = rightPadding ;
var totalTopBoxesHeight = topPadding ;
var totalBottomBoxesHeight = bottomPadding ;
// Function to fit a box
function fitBox ( box ) {
var minBoxSize = helpers . findNextWhere ( minBoxSizes , function ( minBox ) {
return minBox . box === box ;
} ) ;
if ( minBoxSize ) {
if ( box . isHorizontal ( ) ) {
var scaleMargin = {
left : Math . max ( totalLeftBoxesWidth , maxHorizontalLeftPadding ) ,
right : Math . max ( totalRightBoxesWidth , maxHorizontalRightPadding ) ,
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 ) ;
}
}
}
// 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 ) ;
// 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 ;
} ) ;
function finalFitVerticalBox ( box ) {
var minBoxSize = helpers . findNextWhere ( minBoxSizes , function ( minSize ) {
return minSize . box === box ;
} ) ;
var scaleMargin = {
left : 0 ,
right : 0 ,
top : totalTopBoxesHeight ,
bottom : totalBottomBoxesHeight
} ;
if ( minBoxSize ) {
box . update ( minBoxSize . minSize . width , maxChartAreaHeight , scaleMargin ) ;
}
}
// Let the left layout know the final margin
helpers . each ( leftBoxes . concat ( rightBoxes ) , finalFitVerticalBox ) ;
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
totalLeftBoxesWidth = leftPadding ;
totalRightBoxesWidth = rightPadding ;
totalTopBoxesHeight = topPadding ;
totalBottomBoxesHeight = bottomPadding ;
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 ;
} ) ;
// We may be adding some padding to account for rotated x axis labels
var leftPaddingAddition = Math . max ( maxHorizontalLeftPadding - totalLeftBoxesWidth , 0 ) ;
totalLeftBoxesWidth += leftPaddingAddition ;
totalRightBoxesWidth += Math . max ( maxHorizontalRightPadding - totalRightBoxesWidth , 0 ) ;
var topPaddingAddition = Math . max ( maxVerticalTopPadding - totalTopBoxesHeight , 0 ) ;
totalTopBoxesHeight += topPaddingAddition ;
totalBottomBoxesHeight += Math . max ( maxVerticalBottomPadding - totalBottomBoxesHeight , 0 ) ;
// 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 = leftPadding + leftPaddingAddition ;
var top = topPadding + topPaddingAddition ;
function placeBox ( box ) {
if ( box . isHorizontal ( ) ) {
box . left = box . options . fullWidth ? leftPadding : totalLeftBoxesWidth ;
box . right = box . options . fullWidth ? width - rightPadding : 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 ;
}
}
helpers . each ( leftBoxes . concat ( topBoxes ) , placeBox ) ;
// Account for chart width and height
left += maxChartAreaWidth ;
top += maxChartAreaHeight ;
helpers . each ( rightBoxes , placeBox ) ;
helpers . each ( bottomBoxes , placeBox ) ;
// 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 ) ;
} ) ;
}
} ;
} ;
} , { } ] , 30 : [ 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 ( ) ;
} ,
onHover : null ,
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 ) : [ ] ;
}
}
} ;
/ * *
* Helper function to get the box width based on the usePointStyle option
* @ param labelopts { Object } the label options on the legend
* @ param fontSize { Number } the label font size
* @ return { Number } width of the color box area
* /
function getBoxWidth ( labelOpts , fontSize ) {
return labelOpts . usePointStyle ?
fontSize * Math . SQRT2 :
labelOpts . boxWidth ;
}
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 lifecycle. 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 ;
var labelOpts = me . options . labels ;
var legendItems = labelOpts . generateLabels . call ( me , me . chart ) ;
if ( labelOpts . filter ) {
legendItems = legendItems . filter ( function ( item ) {
return labelOpts . filter ( item , me . chart . data ) ;
} ) ;
}
if ( me . options . reverse ) {
legendItems . reverse ( ) ;
}
me . legendItems = legendItems ;
} ,
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 = getBoxWidth ( labelOpts , fontSize ) ;
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 ) {
var boxWidth = getBoxWidth ( labelOpts , fontSize ) ;
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' ;
} ,
// Actually 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 = getBoxWidth ( labelOpts , fontSize ) ,
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 ) ;
var isLineWidthZero = ( itemOrDefault ( legendItem . lineWidth , lineDefault . borderWidth ) === 0 ) ;
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 ) {
// Recalculate 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
if ( ! isLineWidthZero ) {
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 = 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 + labelOpts . padding ;
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
* @ private
* @ param { IEvent } event - The event to handle
* @ return { Boolean } true if a change occured
* /
handleEvent : function ( e ) {
var me = this ;
var opts = me . options ;
var type = e . type === 'mouseup' ? 'click' : e . type ;
var changed = false ;
if ( type === 'mousemove' ) {
if ( ! opts . onHover ) {
return ;
}
} else if ( type === 'click' ) {
if ( ! opts . onClick ) {
return ;
}
} else {
return ;
}
// Chart event already has relative position in it
var x = e . x ,
y = e . y ;
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 ( type === 'click' ) {
// use e.native for backwards compatibility
opts . onClick . call ( me , e . native , me . legendItems [ i ] ) ;
changed = true ;
break ;
} else if ( type === 'mousemove' ) {
// use e.native for backwards compatibility
opts . onHover . call ( me , e . native , me . legendItems [ i ] ) ;
changed = true ;
break ;
}
}
}
}
return changed ;
}
} ) ;
function createNewLegendAndAttach ( chartInstance , legendOpts ) {
var legend = new Chart . Legend ( {
ctx : chartInstance . chart . ctx ,
options : legendOpts ,
chart : chartInstance
} ) ;
chartInstance . legend = legend ;
Chart . layoutService . addBox ( chartInstance , legend ) ;
}
// Register the legend plugin
Chart . plugins . register ( {
beforeInit : function ( chartInstance ) {
var legendOpts = chartInstance . options . legend ;
if ( legendOpts ) {
createNewLegendAndAttach ( chartInstance , legendOpts ) ;
}
} ,
beforeUpdate : function ( chartInstance ) {
var legendOpts = chartInstance . options . legend ;
if ( legendOpts ) {
legendOpts = helpers . configMerge ( Chart . defaults . global . legend , legendOpts ) ;
if ( chartInstance . legend ) {
chartInstance . legend . options = legendOpts ;
} else {
createNewLegendAndAttach ( chartInstance , legendOpts ) ;
}
} else {
Chart . layoutService . removeBox ( chartInstance , chartInstance . legend ) ;
delete chartInstance . legend ;
}
} ,
afterEvent : function ( chartInstance , e ) {
var legend = chartInstance . legend ;
if ( legend ) {
legend . handleEvent ( e ) ;
}
}
} ) ;
} ;
} , { } ] , 31 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
Chart . defaults . global . plugins = { } ;
/ * *
* The plugin service singleton
* @ namespace Chart . plugins
* @ since 2.1 . 0
* /
Chart . plugins = {
/ * *
* Globally registered plugins .
* @ private
* /
_plugins : [ ] ,
/ * *
* This identifier is used to invalidate the descriptors cache attached to each chart
* when a global plugin is registered or unregistered . In this case , the cache ID is
* incremented and descriptors are regenerated during following API calls .
* @ private
* /
_cacheId : 0 ,
/ * *
* 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 ) ;
}
} ) ;
this . _cacheId ++ ;
} ,
/ * *
* 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 ) ;
}
} ) ;
this . _cacheId ++ ;
} ,
/ * *
* Remove all registered plugins .
* @ since 2.1 . 5
* /
clear : function ( ) {
this . _plugins = [ ] ;
this . _cacheId ++ ;
} ,
/ * *
* Returns the number of registered plugins ?
* @ returns { Number }
* @ since 2.1 . 5
* /
count : function ( ) {
return this . _plugins . length ;
} ,
/ * *
* Returns all registered plugin instances .
* @ returns { Array } array of plugin objects .
* @ since 2.1 . 5
* /
getAll : function ( ) {
return this . _plugins ;
} ,
/ * *
* Calls enabled plugins for ` chart ` on the specified hook and 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 { Object } chart - The chart instance for which plugins should be called .
* @ param { String } hook - The name of the plugin method to call ( e . g . 'beforeUpdate' ) .
* @ param { Array } [ args ] - Extra arguments to apply to the hook call .
* @ returns { Boolean } false if any of the plugins return false , else returns true .
* /
notify : function ( chart , hook , args ) {
var descriptors = this . descriptors ( chart ) ;
var ilen = descriptors . length ;
var i , descriptor , plugin , params , method ;
for ( i = 0 ; i < ilen ; ++ i ) {
descriptor = descriptors [ i ] ;
plugin = descriptor . plugin ;
method = plugin [ hook ] ;
if ( typeof method === 'function' ) {
params = [ chart ] . concat ( args || [ ] ) ;
params . push ( descriptor . options ) ;
if ( method . apply ( plugin , params ) === false ) {
return false ;
}
}
}
return true ;
} ,
/ * *
* Returns descriptors of enabled plugins for the given chart .
* @ returns { Array } [ { plugin , options } ]
* @ private
* /
descriptors : function ( chart ) {
var cache = chart . _plugins || ( chart . _plugins = { } ) ;
if ( cache . id === this . _cacheId ) {
return cache . descriptors ;
}
var plugins = [ ] ;
var descriptors = [ ] ;
var config = ( chart && chart . config ) || { } ;
var defaults = Chart . defaults . global . plugins ;
var options = ( config . options && config . options . plugins ) || { } ;
this . _plugins . concat ( config . plugins || [ ] ) . forEach ( function ( plugin ) {
var idx = plugins . indexOf ( plugin ) ;
if ( idx !== - 1 ) {
return ;
}
var id = plugin . id ;
var opts = options [ id ] ;
if ( opts === false ) {
return ;
}
if ( opts === true ) {
opts = helpers . clone ( defaults [ id ] ) ;
}
plugins . push ( plugin ) ;
descriptors . push ( {
plugin : plugin ,
options : opts || { }
} ) ;
} ) ;
cache . descriptors = descriptors ;
cache . id = this . _cacheId ;
return descriptors ;
}
} ;
/ * *
* Plugin extension hooks .
* @ interface IPlugin
* @ since 2.1 . 0
* /
/ * *
* @ method IPlugin # beforeInit
* @ desc Called before initializing ` chart ` .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # afterInit
* @ desc Called after ` chart ` has been initialized and before the first update .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeUpdate
* @ desc Called before updating ` chart ` . If any plugin returns ` false ` , the update
* is cancelled ( and thus subsequent render ( s ) ) until another ` update ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } ` false ` to cancel the chart update .
* /
/ * *
* @ method IPlugin # afterUpdate
* @ desc Called after ` chart ` has been updated and before rendering . Note that this
* hook will not be called if the chart update has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeDatasetsUpdate
* @ desc Called before updating the ` chart ` datasets . If any plugin returns ` false ` ,
* the datasets update is cancelled until another ` update ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } false to cancel the datasets update .
* @ since version 2.1 . 5
* /
/ * *
* @ method IPlugin # afterDatasetsUpdate
* @ desc Called after the ` chart ` datasets have been updated . Note that this hook
* will not be called if the datasets update has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* @ since version 2.1 . 5
* /
/ * *
* @ method IPlugin # beforeLayout
* @ desc Called before laying out ` chart ` . If any plugin returns ` false ` ,
* the layout update is cancelled until another ` update ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } ` false ` to cancel the chart layout .
* /
/ * *
* @ method IPlugin # afterLayout
* @ desc Called after the ` chart ` has been layed out . Note that this hook will not
* be called if the layout update has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeRender
* @ desc Called before rendering ` chart ` . If any plugin returns ` false ` ,
* the rendering is cancelled until another ` render ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } ` false ` to cancel the chart rendering .
* /
/ * *
* @ method IPlugin # afterRender
* @ desc Called after the ` chart ` has been fully rendered ( and animation completed ) . Note
* that this hook will not be called if the rendering has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeDraw
* @ desc Called before drawing ` chart ` at every animation frame specified by the given
* easing value . If any plugin returns ` false ` , the frame drawing is cancelled until
* another ` render ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Number } easingValue - The current animation value , between 0.0 and 1.0 .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } ` false ` to cancel the chart drawing .
* /
/ * *
* @ method IPlugin # afterDraw
* @ desc Called after the ` chart ` has been drawn for the specific easing value . Note
* that this hook will not be called if the drawing has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Number } easingValue - The current animation value , between 0.0 and 1.0 .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeDatasetsDraw
* @ desc Called before drawing the ` chart ` datasets . If any plugin returns ` false ` ,
* the datasets drawing is cancelled until another ` render ` is triggered .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Number } easingValue - The current animation value , between 0.0 and 1.0 .
* @ param { Object } options - The plugin options .
* @ returns { Boolean } ` false ` to cancel the chart datasets drawing .
* /
/ * *
* @ method IPlugin # afterDatasetsDraw
* @ desc Called after the ` chart ` datasets have been drawn . Note that this hook
* will not be called if the datasets drawing has been previously cancelled .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Number } easingValue - The current animation value , between 0.0 and 1.0 .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # beforeEvent
* @ desc Called before processing the specified ` event ` . If any plugin returns ` false ` ,
* the event will be discarded .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { IEvent } event - The event object .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # afterEvent
* @ desc Called after the ` event ` has been consumed . Note that this hook
* will not be called if the ` event ` has been previously discarded .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { IEvent } event - The event object .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # resize
* @ desc Called after the chart as been resized .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Number } size - The new canvas display size ( eq . canvas . style width & height ) .
* @ param { Object } options - The plugin options .
* /
/ * *
* @ method IPlugin # destroy
* @ desc Called after the chart as been destroyed .
* @ param { Chart . Controller } chart - The chart instance .
* @ param { Object } options - The plugin options .
* /
/ * *
* Provided for backward compatibility , use Chart . plugins instead
* @ namespace Chart . pluginService
* @ deprecated since version 2.1 . 5
* @ todo remove at version 3
* @ private
* /
Chart . pluginService = Chart . plugins ;
/ * *
* Provided for backward compatibility , inheriting from Chart . PlugingBase has no
* effect , instead simply create / register plugins via plain JavaScript objects .
* @ interface Chart . PluginBase
* @ deprecated since version 2.5 . 0
* @ todo remove at version 3
* @ private
* /
Chart . PluginBase = helpers . inherits ( { } ) ;
} ;
} , { } ] , 32 : [ 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 : 0 ,
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 : Chart . Ticks . formatters . values
}
} ;
function computeTextSize ( context , tick , font ) {
return helpers . isArray ( tick ) ?
helpers . longestText ( context , font , tick ) :
context . measureText ( tick ) . width ;
}
function parseFontOptions ( options ) {
var getValueOrDefault = helpers . getValueOrDefault ;
var globalDefaults = Chart . defaults . global ;
var size = getValueOrDefault ( options . fontSize , globalDefaults . defaultFontSize ) ;
var style = getValueOrDefault ( options . fontStyle , globalDefaults . defaultFontStyle ) ;
var family = getValueOrDefault ( options . fontFamily , globalDefaults . defaultFontFamily ) ;
return {
size : size ,
style : style ,
family : family ,
font : helpers . fontString ( size , style , family )
} ;
}
Chart . Scale = Chart . Element . extend ( {
/ * *
* Get the padding needed for the scale
* @ method getPadding
* @ private
* @ returns { Padding } the necessary padding
* /
getPadding : function ( ) {
var me = this ;
return {
left : me . paddingLeft || 0 ,
top : me . paddingTop || 0 ,
right : me . paddingRight || 0 ,
bottom : me . paddingBottom || 0
} ;
} ,
// 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 ) ;
me . longestTextCache = me . longestTextCache || { } ;
// 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
var tickOpts = me . options . ticks ;
me . ticks = me . ticks . map ( tickOpts . userCallback || tickOpts . callback ) ;
} ,
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 tickOpts = me . options . ticks ;
// Get the width of each grid by calculating the difference
// between x offsets between 0 and 1.
var tickFont = parseFontOptions ( tickOpts ) ;
context . font = tickFont . font ;
var labelRotation = tickOpts . minRotation || 0 ;
if ( me . options . display && me . isHorizontal ( ) ) {
var originalLabelWidth = helpers . longestText ( context , tickFont . font , me . ticks , me . longestTextCache ) ;
var labelWidth = originalLabelWidth ;
var cosRotation ;
var sinRotation ;
// Allow 3 pixels x2 padding either side for label readability
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 && labelRotation < tickOpts . maxRotation ) {
var angleRadians = helpers . toRadians ( labelRotation ) ;
cosRotation = Math . cos ( angleRadians ) ;
sinRotation = Math . sin ( angleRadians ) ;
if ( sinRotation * originalLabelWidth > me . maxHeight ) {
// go back one step
labelRotation -- ;
break ;
}
labelRotation ++ ;
labelWidth = cosRotation * originalLabelWidth ;
}
}
me . labelRotation = labelRotation ;
} ,
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 tickOpts = opts . ticks ;
var scaleLabelOpts = opts . scaleLabel ;
var gridLineOpts = opts . gridLines ;
var display = opts . display ;
var isHorizontal = me . isHorizontal ( ) ;
var tickFont = parseFontOptions ( tickOpts ) ;
var scaleLabelFontSize = parseFontOptions ( scaleLabelOpts ) . size * 1.5 ;
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 ;
} else {
minSize . width += scaleLabelFontSize ;
}
}
// Don't bother fitting the ticks if we are not showing them
if ( tickOpts . display && display ) {
var largestTextWidth = helpers . longestText ( me . ctx , tickFont . font , me . ticks , me . longestTextCache ) ;
var tallestLabelHeightInLines = helpers . numberOfLabelLines ( me . ticks ) ;
var lineSpace = tickFont . size * 0.5 ;
if ( isHorizontal ) {
// A horizontal axis is more constrained by the height.
me . longestLabelWidth = largestTextWidth ;
var angleRadians = helpers . toRadians ( me . labelRotation ) ;
var cosRotation = Math . cos ( angleRadians ) ;
var sinRotation = Math . sin ( angleRadians ) ;
// TODO - improve this calculation
var labelHeight = ( sinRotation * largestTextWidth )
+ ( tickFont . size * tallestLabelHeightInLines )
+ ( lineSpace * tallestLabelHeightInLines ) ;
minSize . height = Math . min ( me . maxHeight , minSize . height + labelHeight ) ;
me . ctx . font = tickFont . font ;
var firstTick = me . ticks [ 0 ] ;
var firstLabelWidth = computeTextSize ( me . ctx , firstTick , tickFont . font ) ;
var lastTick = me . ticks [ me . ticks . length - 1 ] ;
var lastLabelWidth = computeTextSize ( me . ctx , lastTick , tickFont . font ) ;
// 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
if ( me . labelRotation !== 0 ) {
me . paddingLeft = opts . position === 'bottom' ? ( cosRotation * firstLabelWidth ) + 3 : ( cosRotation * lineSpace ) + 3 ; // add 3 px to move away from canvas edges
me . paddingRight = opts . position === 'bottom' ? ( cosRotation * lineSpace ) + 3 : ( cosRotation * lastLabelWidth ) + 3 ;
} else {
me . paddingLeft = firstLabelWidth / 2 + 3 ; // add 3 px to move away from canvas edges
me . paddingRight = lastLabelWidth / 2 + 3 ;
}
} else {
// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
// Account for padding
if ( tickOpts . mirror ) {
largestTextWidth = 0 ;
} else {
largestTextWidth += me . options . ticks . padding ;
}
minSize . width += largestTextWidth ;
me . paddingTop = tickFont . size / 2 ;
me . paddingBottom = tickFont . size / 2 ;
}
}
me . handleMargins ( ) ;
me . width = minSize . width ;
me . height = minSize . height ;
} ,
/ * *
* Handle margins and padding interactions
* @ private
* /
handleMargins : function ( ) {
var me = this ;
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 ) ;
}
} ,
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; Discard Infinite values
if ( typeof ( rawValue ) === 'number' && ! isFinite ( 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 ;
}
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 ;
}
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 ;
}
return me . top + ( decimal * me . height ) ;
} ,
getBasePixel : function ( ) {
return this . getPixelForValue ( this . getBaseValue ( ) ) ;
} ,
getBaseValue : function ( ) {
var me = this ;
var min = me . min ;
var max = me . max ;
return me . beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0 ;
} ,
// Actually 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 tickFont = parseFontOptions ( optionTicks ) ;
var tl = gridLines . drawTicks ? gridLines . tickMarkLength : 0 ;
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 scaleLabelFont = parseFontOptions ( scaleLabel ) ;
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 = 'middle' ;
var textBaseline = 'middle' ;
if ( isHorizontal ) {
if ( options . position === 'bottom' ) {
// bottom
textBaseline = ! isRotated ? 'top' : 'middle' ;
textAlign = ! isRotated ? 'center' : 'right' ;
labelY = me . top + tl ;
} else {
// top
textBaseline = ! isRotated ? 'bottom' : 'middle' ;
textAlign = ! isRotated ? 'center' : 'left' ;
labelY = me . bottom - tl ;
}
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)
tx1 = tx2 = x1 = x2 = xLineValue ;
ty1 = yTickStart ;
ty2 = yTickEnd ;
y1 = chartArea . top ;
y2 = chartArea . bottom ;
} else {
var isLeft = options . position === 'left' ;
var tickPadding = optionTicks . padding ;
var labelXOffset ;
if ( optionTicks . mirror ) {
textAlign = isLeft ? 'left' : 'right' ;
labelXOffset = tickPadding ;
} else {
textAlign = isLeft ? 'right' : 'left' ;
labelXOffset = tl + tickPadding ;
}
labelX = isLeft ? me . right - labelXOffset : me . left + labelXOffset ;
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 = tickFont . font ;
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 += ( tickFont . size * 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 - ( scaleLabelFont . size / 2 ) : me . top + ( scaleLabelFont . size / 2 ) ;
} else {
var isLeft = options . position === 'left' ;
scaleLabelX = isLeft ? me . left + ( scaleLabelFont . size / 2 ) : me . right - ( scaleLabelFont . size / 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 . font ;
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 ( ) ;
}
}
} ) ;
} ;
} , { } ] , 33 : [ 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 ) ;
} ) ;
}
} ;
} ;
} , { } ] , 34 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
/ * *
* Namespace to hold static tick generation functions
* @ namespace Chart . Ticks
* /
Chart . Ticks = {
/ * *
* Namespace to hold generators for different types of ticks
* @ namespace Chart . Ticks . generators
* /
generators : {
/ * *
* Interface for the options provided to the numeric tick generator
* @ interface INumericTickGenerationOptions
* /
/ * *
* The maximum number of ticks to display
* @ name INumericTickGenerationOptions # maxTicks
* @ type Number
* /
/ * *
* The distance between each tick .
* @ name INumericTickGenerationOptions # stepSize
* @ type Number
* @ optional
* /
/ * *
* Forced minimum for the ticks . If not specified , the minimum of the data range is used to calculate the tick minimum
* @ name INumericTickGenerationOptions # min
* @ type Number
* @ optional
* /
/ * *
* The maximum value of the ticks . If not specified , the maximum of the data range is used to calculate the tick maximum
* @ name INumericTickGenerationOptions # max
* @ type Number
* @ optional
* /
/ * *
* Generate a set of linear ticks
* @ method Chart . Ticks . generators . linear
* @ param generationOptions { INumericTickGenerationOptions } the options used to generate the ticks
* @ param dataRange { IRange } the range of the data
* @ returns { Array < Number > } array of tick values
* /
linear : function ( generationOptions , dataRange ) {
var ticks = [ ] ;
// 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 ;
if ( generationOptions . stepSize && generationOptions . stepSize > 0 ) {
spacing = generationOptions . stepSize ;
} else {
var niceRange = helpers . niceNum ( dataRange . max - dataRange . min , false ) ;
spacing = helpers . niceNum ( niceRange / ( generationOptions . maxTicks - 1 ) , true ) ;
}
var niceMin = Math . floor ( dataRange . min / spacing ) * spacing ;
var niceMax = Math . ceil ( dataRange . max / spacing ) * spacing ;
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if ( generationOptions . min && generationOptions . max && generationOptions . stepSize ) {
// If very close to our whole number, use it.
if ( helpers . almostWhole ( ( generationOptions . max - generationOptions . min ) / generationOptions . stepSize , spacing / 1000 ) ) {
niceMin = generationOptions . min ;
niceMax = generationOptions . max ;
}
}
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 ( generationOptions . min !== undefined ? generationOptions . min : niceMin ) ;
for ( var j = 1 ; j < numSpaces ; ++ j ) {
ticks . push ( niceMin + ( j * spacing ) ) ;
}
ticks . push ( generationOptions . max !== undefined ? generationOptions . max : niceMax ) ;
return ticks ;
} ,
/ * *
* Generate a set of logarithmic ticks
* @ method Chart . Ticks . generators . logarithmic
* @ param generationOptions { INumericTickGenerationOptions } the options used to generate the ticks
* @ param dataRange { IRange } the range of the data
* @ returns { Array < Number > } array of tick values
* /
logarithmic : function ( generationOptions , dataRange ) {
var 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 tickVal = getValueOrDefault ( generationOptions . min , Math . pow ( 10 , Math . floor ( helpers . log10 ( dataRange . min ) ) ) ) ;
var endExp = Math . floor ( helpers . log10 ( dataRange . max ) ) ;
var endSignificand = Math . ceil ( dataRange . max / Math . pow ( 10 , endExp ) ) ;
var exp ;
var significand ;
if ( tickVal === 0 ) {
exp = Math . floor ( helpers . log10 ( dataRange . minNotZero ) ) ;
significand = Math . floor ( dataRange . minNotZero / Math . pow ( 10 , exp ) ) ;
ticks . push ( tickVal ) ;
tickVal = significand * Math . pow ( 10 , exp ) ;
} else {
exp = Math . floor ( helpers . log10 ( tickVal ) ) ;
significand = Math . floor ( tickVal / Math . pow ( 10 , exp ) ) ;
}
do {
ticks . push ( tickVal ) ;
++ significand ;
if ( significand === 10 ) {
significand = 1 ;
++ exp ;
}
tickVal = significand * Math . pow ( 10 , exp ) ;
} while ( exp < endExp || ( exp === endExp && significand < endSignificand ) ) ;
var lastTick = getValueOrDefault ( generationOptions . max , tickVal ) ;
ticks . push ( lastTick ) ;
return ticks ;
}
} ,
/ * *
* Namespace to hold formatters for different types of ticks
* @ namespace Chart . Ticks . formatters
* /
formatters : {
/ * *
* Formatter for value labels
* @ method Chart . Ticks . formatters . values
* @ param value the value to display
* @ return { String | Array } the label to display
* /
values : function ( value ) {
return helpers . isArray ( value ) ? value : '' + value ;
} ,
/ * *
* Formatter for linear numeric ticks
* @ method Chart . Ticks . formatters . linear
* @ param tickValue { Number } the value to be formatted
* @ param index { Number } the position of the tickValue parameter in the ticks array
* @ param ticks { Array < Number > } the list of ticks being converted
* @ return { String } string representation of the tickValue parameter
* /
linear : 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 ;
} ,
logarithmic : function ( tickValue , index , ticks ) {
var remain = tickValue / ( Math . pow ( 10 , Math . floor ( helpers . log10 ( tickValue ) ) ) ) ;
if ( tickValue === 0 ) {
return '0' ;
} else if ( remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks . length - 1 ) {
return tickValue . toExponential ( ) ;
}
return '' ;
}
}
} ;
} ;
} , { } ] , 35 : [ 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 ) ;
// Contains hit boxes for each dataset (in dataset order)
me . legendHitBoxes = [ ] ;
} ,
// These methods are ordered by lifecycle. Utilities then follow.
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 : 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' ;
} ,
// Actually 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 ,
maxWidth ;
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
maxWidth = right - left ;
} else {
titleX = opts . position === 'left' ? left + ( fontSize / 2 ) : right - ( fontSize / 2 ) ;
titleY = top + ( ( bottom - top ) / 2 ) ;
maxWidth = bottom - top ;
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 , maxWidth ) ;
ctx . restore ( ) ;
}
}
} ) ;
function createNewTitleBlockAndAttach ( chartInstance , titleOpts ) {
var title = new Chart . Title ( {
ctx : chartInstance . chart . ctx ,
options : titleOpts ,
chart : chartInstance
} ) ;
chartInstance . titleBlock = title ;
Chart . layoutService . addBox ( chartInstance , title ) ;
}
// Register the title plugin
Chart . plugins . register ( {
beforeInit : function ( chartInstance ) {
var titleOpts = chartInstance . options . title ;
if ( titleOpts ) {
createNewTitleBlockAndAttach ( chartInstance , titleOpts ) ;
}
} ,
beforeUpdate : function ( chartInstance ) {
var titleOpts = chartInstance . options . title ;
if ( titleOpts ) {
titleOpts = helpers . configMerge ( Chart . defaults . global . title , titleOpts ) ;
if ( chartInstance . titleBlock ) {
chartInstance . titleBlock . options = titleOpts ;
} else {
createNewTitleBlockAndAttach ( chartInstance , titleOpts ) ;
}
} else {
Chart . layoutService . removeBox ( chartInstance , chartInstance . titleBlock ) ;
delete chartInstance . titleBlock ;
}
}
} ) ;
} ;
} , { } ] , 36 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
/ * *
* Helper method to merge the opacity into a color
* /
function mergeOpacity ( colorString , opacity ) {
var color = helpers . color ( colorString ) ;
return color . alpha ( opacity * color . alpha ( ) ) . rgbaString ( ) ;
}
Chart . defaults . global . tooltips = {
enabled : true ,
custom : null ,
mode : 'nearest' ,
position : 'average' ,
intersect : true ,
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 ,
caretSize : 5 ,
cornerRadius : 6 ,
multiKeyBackground : '#fff' ,
displayColors : true ,
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 ;
}
// Private helper to create a tooltip item 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 ,
x : element . _model . x ,
y : element . _model . y
} ;
}
/ * *
* Helper to get the reset model for the tooltip
* @ param tooltipOpts { Object } the tooltip options
* /
function getBaseModel ( tooltipOpts ) {
var globalDefaults = Chart . defaults . global ;
var getValueOrDefault = helpers . getValueOrDefault ;
return {
// 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 ,
displayColors : tooltipOpts . displayColors
} ;
}
/ * *
* Get the size of the tooltip
* /
function getTooltipSize ( tooltip , model ) {
var ctx = tooltip . _chart . ctx ;
var height = model . yPadding * 2 ; // Tooltip Padding
var width = 0 ;
// Count of all lines in the body
var body = model . body ;
var combinedBodyLength = body . reduce ( function ( count , bodyItem ) {
return count + bodyItem . before . length + bodyItem . lines . length + bodyItem . after . length ;
} , 0 ) ;
combinedBodyLength += model . beforeBody . length + model . afterBody . length ;
var titleLineCount = model . title . length ;
var footerLineCount = model . footer . length ;
var titleFontSize = model . titleFontSize ,
bodyFontSize = model . bodyFontSize ,
footerFontSize = model . footerFontSize ;
height += titleLineCount * titleFontSize ; // Title Lines
height += titleLineCount ? ( titleLineCount - 1 ) * model . titleSpacing : 0 ; // Title Line Spacing
height += titleLineCount ? model . titleMarginBottom : 0 ; // Title's bottom Margin
height += combinedBodyLength * bodyFontSize ; // Body Lines
height += combinedBodyLength ? ( combinedBodyLength - 1 ) * model . bodySpacing : 0 ; // Body Line Spacing
height += footerLineCount ? model . footerMarginTop : 0 ; // Footer Margin
height += footerLineCount * ( footerFontSize ) ; // Footer Lines
height += footerLineCount ? ( footerLineCount - 1 ) * model . footerSpacing : 0 ; // Footer Line Spacing
// Title width
var widthPadding = 0 ;
var maxLineWidth = function ( line ) {
width = Math . max ( width , ctx . measureText ( line ) . width + widthPadding ) ;
} ;
ctx . font = helpers . fontString ( titleFontSize , model . _titleFontStyle , model . _titleFontFamily ) ;
helpers . each ( model . title , maxLineWidth ) ;
// Body width
ctx . font = helpers . fontString ( bodyFontSize , model . _bodyFontStyle , model . _bodyFontFamily ) ;
helpers . each ( model . beforeBody . concat ( model . afterBody ) , maxLineWidth ) ;
// Body lines may include some extra width due to the color box
widthPadding = model . displayColors ? ( 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 , model . _footerFontStyle , model . _footerFontFamily ) ;
helpers . each ( model . footer , maxLineWidth ) ;
// Add padding
width += 2 * model . xPadding ;
return {
width : width ,
height : height
} ;
}
/ * *
* Helper to get the alignment of a tooltip given the size
* /
function determineAlignment ( tooltip , size ) {
var model = tooltip . _model ;
var chart = tooltip . _chart ;
var chartArea = tooltip . _chartInstance . chartArea ;
var xAlign = 'center' ;
var yAlign = 'center' ;
if ( model . y < size . height ) {
yAlign = 'top' ;
} else if ( model . y > ( chart . height - size . height ) ) {
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 ( 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 ) ) {
xAlign = 'left' ;
// Is tooltip too wide and goes over the right side of the chart.?
if ( olf ( model . x ) ) {
xAlign = 'center' ;
yAlign = yf ( model . y ) ;
}
} else if ( rf ( model . x ) ) {
xAlign = 'right' ;
// Is tooltip too wide and goes outside left edge of canvas?
if ( orf ( model . x ) ) {
xAlign = 'center' ;
yAlign = yf ( model . y ) ;
}
}
var opts = tooltip . _options ;
return {
xAlign : opts . xAlign ? opts . xAlign : xAlign ,
yAlign : opts . yAlign ? opts . yAlign : yAlign
} ;
}
/ * *
* @ Helper to get the location a tooltip needs to be placed at given the initial position ( via the vm ) and the size and alignment
* /
function getBackgroundPoint ( vm , size , alignment ) {
// Background Position
var x = vm . x ;
var y = vm . y ;
var caretSize = vm . caretSize ,
caretPadding = vm . caretPadding ,
cornerRadius = vm . cornerRadius ,
xAlign = alignment . xAlign ,
yAlign = alignment . yAlign ,
paddingAndSize = caretSize + caretPadding ,
radiusAndPadding = cornerRadius + caretPadding ;
if ( xAlign === 'right' ) {
x -= size . width ;
} else if ( xAlign === 'center' ) {
x -= ( size . width / 2 ) ;
}
if ( yAlign === 'top' ) {
y += paddingAndSize ;
} else if ( yAlign === 'bottom' ) {
y -= size . height + paddingAndSize ;
} else {
y -= ( size . height / 2 ) ;
}
if ( yAlign === 'center' ) {
if ( xAlign === 'left' ) {
x += paddingAndSize ;
} else if ( xAlign === 'right' ) {
x -= paddingAndSize ;
}
} else if ( xAlign === 'left' ) {
x -= radiusAndPadding ;
} else if ( xAlign === 'right' ) {
x += radiusAndPadding ;
}
return {
x : x ,
y : y
} ;
}
Chart . Tooltip = Chart . Element . extend ( {
initialize : function ( ) {
this . _model = getBaseModel ( this . _options ) ;
} ,
// 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 ;
// Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
// that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
// which breaks any animations.
var existingModel = me . _model ;
var model = me . _model = getBaseModel ( opts ) ;
var active = me . _active ;
var data = me . _data ;
var chartInstance = me . _chartInstance ;
// In the case where active.length === 0 we need to keep these at existing values for good animations
var alignment = {
xAlign : existingModel . xAlign ,
yAlign : existingModel . yAlign
} ;
var backgroundPoint = {
x : existingModel . x ,
y : existingModel . y
} ;
var tooltipSize = {
width : existingModel . width ,
height : existingModel . height
} ;
var tooltipPosition = {
x : existingModel . caretX ,
y : existingModel . caretY
} ;
var i , len ;
if ( active . length ) {
model . opacity = 1 ;
var labelColors = [ ] ;
tooltipPosition = Chart . Tooltip . positioners [ opts . position ] ( active , me . _eventPosition ) ;
var tooltipItems = [ ] ;
for ( i = 0 , len = active . length ; i < len ; ++ i ) {
tooltipItems . push ( createTooltipItem ( active [ i ] ) ) ;
}
// If the user provided a filter function, use it to modify the tooltip items
if ( opts . filter ) {
tooltipItems = tooltipItems . filter ( function ( a ) {
return opts . filter ( a , data ) ;
} ) ;
}
// 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 ) ;
} ) ;
}
// Determine colors for boxes
helpers . each ( tooltipItems , function ( tooltipItem ) {
labelColors . push ( opts . callbacks . labelColor . call ( me , tooltipItem , chartInstance ) ) ;
} ) ;
// Build the Text Lines
model . title = me . getTitle ( tooltipItems , data ) ;
model . beforeBody = me . getBeforeBody ( tooltipItems , data ) ;
model . body = me . getBody ( tooltipItems , data ) ;
model . afterBody = me . getAfterBody ( tooltipItems , data ) ;
model . footer = me . getFooter ( tooltipItems , data ) ;
// Initial positioning and colors
model . x = Math . round ( tooltipPosition . x ) ;
model . y = Math . round ( tooltipPosition . y ) ;
model . caretPadding = helpers . getValueOrDefault ( tooltipPosition . padding , 2 ) ;
model . labelColors = labelColors ;
// data points
model . dataPoints = tooltipItems ;
// We need to determine alignment of the tooltip
tooltipSize = getTooltipSize ( this , model ) ;
alignment = determineAlignment ( this , tooltipSize ) ;
// Final Size and Position
backgroundPoint = getBackgroundPoint ( model , tooltipSize , alignment ) ;
} else {
model . opacity = 0 ;
}
model . xAlign = alignment . xAlign ;
model . yAlign = alignment . yAlign ;
model . x = backgroundPoint . x ;
model . y = backgroundPoint . y ;
model . width = tooltipSize . width ;
model . height = tooltipSize . height ;
// Point where the caret on the tooltip points to
model . caretX = tooltipPosition . x ;
model . caretY = tooltipPosition . y ;
me . _model = model ;
if ( changed && opts . custom ) {
opts . custom . call ( me , model ) ;
}
return me ;
} ,
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 ;
}
}
ctx . fillStyle = mergeOpacity ( vm . backgroundColor , opacity ) ;
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 ;
ctx . fillStyle = mergeOpacity ( vm . titleFontColor , opacity ) ;
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 textColor = mergeOpacity ( vm . bodyFontColor , opacity ) ;
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 = vm . displayColors ;
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 = mergeOpacity ( vm . legendColorBackground , opacity ) ;
ctx . fillRect ( pt . x , pt . y , bodyFontSize , bodyFontSize ) ;
// Border
ctx . strokeStyle = mergeOpacity ( vm . labelColors [ i ] . borderColor , opacity ) ;
ctx . strokeRect ( pt . x , pt . y , bodyFontSize , bodyFontSize ) ;
// Inner square
ctx . fillStyle = mergeOpacity ( vm . labelColors [ i ] . backgroundColor , opacity ) ;
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' ;
ctx . fillStyle = mergeOpacity ( vm . footerFontColor , opacity ) ;
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 ;
} ) ;
}
} ,
drawBackground : function ( pt , vm , ctx , tooltipSize , opacity ) {
ctx . fillStyle = mergeOpacity ( vm . backgroundColor , opacity ) ;
helpers . drawRoundedRectangle ( ctx , pt . x , pt . y , tooltipSize . width , tooltipSize . height , vm . cornerRadius ) ;
ctx . fill ( ) ;
} ,
draw : function ( ) {
var ctx = this . _chart . ctx ;
var vm = this . _view ;
if ( vm . opacity === 0 ) {
return ;
}
var tooltipSize = {
width : vm . width ,
height : vm . height
} ;
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
this . drawBackground ( pt , vm , ctx , tooltipSize , opacity ) ;
// 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 ) ;
}
} ,
/ * *
* Handle an event
* @ private
* @ param { IEvent } event - The event to handle
* @ returns { Boolean } true if the tooltip changed
* /
handleEvent : function ( e ) {
var me = this ;
var options = me . _options ;
var changed = false ;
me . _lastActive = me . _lastActive || [ ] ;
// Find Active Elements for tooltips
if ( e . type === 'mouseout' ) {
me . _active = [ ] ;
} else {
me . _active = me . _chartInstance . getElementsAtEventForMode ( e , options . mode , options ) ;
}
// Remember Last Actives
changed = ! helpers . arrayEquals ( me . _active , me . _lastActive ) ;
me . _lastActive = me . _active ;
if ( options . enabled || options . custom ) {
me . _eventPosition = {
x : e . x ,
y : e . y
} ;
var model = me . _model ;
me . update ( true ) ;
me . pivot ( ) ;
// See if our tooltip position changed
changed |= ( model . x !== me . _model . x ) || ( model . y !== me . _model . y ) ;
}
return changed ;
}
} ) ;
/ * *
* @ namespace Chart . Tooltip . positioners
* /
Chart . Tooltip . positioners = {
/ * *
* Average mode places the tooltip at the average position of the elements shown
* @ function Chart . Tooltip . positioners . average
* @ param elements { ChartElement [ ] } the elements being displayed in the tooltip
* @ returns { Point } tooltip position
* /
average : function ( elements ) {
if ( ! elements . length ) {
return false ;
}
var i , len ;
var x = 0 ;
var y = 0 ;
var count = 0 ;
for ( i = 0 , len = elements . length ; i < len ; ++ i ) {
var el = elements [ i ] ;
if ( el && el . hasValue ( ) ) {
var pos = el . tooltipPosition ( ) ;
x += pos . x ;
y += pos . y ;
++ count ;
}
}
return {
x : Math . round ( x / count ) ,
y : Math . round ( y / count )
} ;
} ,
/ * *
* Gets the tooltip position nearest of the item nearest to the event position
* @ function Chart . Tooltip . positioners . nearest
* @ param elements { Chart . Element [ ] } the tooltip elements
* @ param eventPosition { Point } the position of the event in canvas coordinates
* @ returns { Point } the tooltip position
* /
nearest : function ( elements , eventPosition ) {
var x = eventPosition . x ;
var y = eventPosition . y ;
var nearestElement ;
var minDistance = Number . POSITIVE _INFINITY ;
var i , len ;
for ( i = 0 , len = elements . length ; i < len ; ++ i ) {
var el = elements [ i ] ;
if ( el && el . hasValue ( ) ) {
var center = el . getCenterPoint ( ) ;
var d = helpers . distanceBetweenPoints ( eventPosition , center ) ;
if ( d < minDistance ) {
minDistance = d ;
nearestElement = el ;
}
}
}
if ( nearestElement ) {
var tp = nearestElement . tooltipPosition ( ) ;
x = tp . x ;
y = tp . y ;
}
return {
x : x ,
y : y
} ;
}
} ;
} ;
} , { } ] , 37 : [ 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 ) ) ;
}
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 ) ;
}
return false ;
} ,
getCenterPoint : function ( ) {
var vm = this . _view ;
var halfAngle = ( vm . startAngle + vm . endAngle ) / 2 ;
var halfRadius = ( vm . innerRadius + vm . outerRadius ) / 2 ;
return {
x : vm . x + Math . cos ( halfAngle ) * halfRadius ,
y : vm . y + Math . sin ( halfAngle ) * halfRadius
} ;
} ,
getArea : function ( ) {
var vm = this . _view ;
return Math . PI * ( ( vm . endAngle - vm . startAngle ) / ( 2 * Math . PI ) ) * ( Math . pow ( vm . outerRadius , 2 ) - Math . pow ( vm . innerRadius , 2 ) ) ;
} ,
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 ( ) ;
}
}
} ) ;
} ;
} , { } ] , 38 : [ 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 fillPoint = vm . scaleZero ;
var loop = me . _loop ;
// Handle different fill modes for cartesian lines
if ( ! loop ) {
if ( vm . fill === 'top' ) {
fillPoint = vm . scaleTop ;
} else if ( vm . fill === 'bottom' ) {
fillPoint = vm . scaleBottom ;
}
}
var ctx = me . _chart . ctx ;
ctx . save ( ) ;
// Helper function to draw a line to a point
function lineToPoint ( previousPoint , point ) {
var pointVM = point . _view ;
if ( point . _view . steppedLine === true ) {
ctx . lineTo ( pointVM . x , previousPoint . _view . y ) ;
ctx . lineTo ( pointVM . x , pointVM . y ) ;
} else if ( point . _view . tension === 0 ) {
ctx . lineTo ( pointVM . x , pointVM . y ) ;
} else {
ctx . bezierCurveTo (
previousPoint . _view . controlPointNextX ,
previousPoint . _view . controlPointNextY ,
pointVM . controlPointPreviousX ,
pointVM . controlPointPreviousY ,
pointVM . x ,
pointVM . 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 ( fillPoint . x , fillPoint . y ) ;
} else {
ctx . moveTo ( currentVM . x , fillPoint ) ;
}
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 ( fillPoint . x , fillPoint . y ) ;
} else {
ctx . lineTo ( previous . _view . x , fillPoint ) ;
}
}
} 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 , fillPoint ) ;
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 , fillPoint ) ;
}
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 ( ) ;
}
} ) ;
} ;
} , { } ] , 39 : [ 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
} ;
function xRange ( mouseX ) {
var vm = this . _view ;
return vm ? ( Math . pow ( mouseX - vm . x , 2 ) < Math . pow ( vm . radius + vm . hitRadius , 2 ) ) : false ;
}
function yRange ( mouseY ) {
var vm = this . _view ;
return vm ? ( Math . pow ( mouseY - vm . y , 2 ) < Math . pow ( vm . radius + vm . hitRadius , 2 ) ) : false ;
}
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 : xRange ,
inXRange : xRange ,
inYRange : yRange ,
getCenterPoint : function ( ) {
var vm = this . _view ;
return {
x : vm . x ,
y : vm . y
} ;
} ,
getArea : function ( ) {
return Math . PI * Math . pow ( this . _view . radius , 2 ) ;
} ,
tooltipPosition : function ( ) {
var vm = this . _view ;
return {
x : vm . x ,
y : vm . y ,
padding : vm . radius + vm . borderWidth
} ;
} ,
draw : function ( chartArea ) {
var vm = this . _view ;
var model = this . _model ;
var ctx = this . _chart . ctx ;
var pointStyle = vm . pointStyle ;
var radius = vm . radius ;
var x = vm . x ;
var y = vm . y ;
var color = Chart . helpers . color ;
var errMargin = 1.01 ; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
var ratio = 0 ;
if ( vm . skip ) {
return ;
}
ctx . strokeStyle = vm . borderColor || defaultColor ;
ctx . lineWidth = helpers . getValueOrDefault ( vm . borderWidth , globalOpts . elements . point . borderWidth ) ;
ctx . fillStyle = vm . backgroundColor || defaultColor ;
// Cliping for Points.
// going out from inner charArea?
if ( ( chartArea !== undefined ) && ( ( model . x < chartArea . left ) || ( chartArea . right * errMargin < model . x ) || ( model . y < chartArea . top ) || ( chartArea . bottom * errMargin < model . y ) ) ) {
// Point fade out
if ( model . x < chartArea . left ) {
ratio = ( x - model . x ) / ( chartArea . left - model . x ) ;
} else if ( chartArea . right * errMargin < model . x ) {
ratio = ( model . x - x ) / ( model . x - chartArea . right ) ;
} else if ( model . y < chartArea . top ) {
ratio = ( y - model . y ) / ( chartArea . top - model . y ) ;
} else if ( chartArea . bottom * errMargin < model . y ) {
ratio = ( model . y - y ) / ( model . y - chartArea . bottom ) ;
}
ratio = Math . round ( ratio * 100 ) / 100 ;
ctx . strokeStyle = color ( ctx . strokeStyle ) . alpha ( ratio ) . rgbString ( ) ;
ctx . fillStyle = color ( ctx . fillStyle ) . alpha ( ratio ) . rgbString ( ) ;
}
Chart . canvasHelpers . drawPoint ( ctx , pointStyle , radius , x , y ) ;
}
} ) ;
} ;
} , { } ] , 40 : [ 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'
} ;
function isVertical ( bar ) {
return bar . _view . width !== undefined ;
}
/ * *
* Helper function to get the bounds of the bar regardless of the orientation
* @ private
* @ param bar { Chart . Element . Rectangle } the bar
* @ return { Bounds } bounds of the bar
* /
function getBarBounds ( bar ) {
var vm = bar . _view ;
var x1 , x2 , y1 , y2 ;
if ( isVertical ( bar ) ) {
// vertical
var halfWidth = vm . width / 2 ;
x1 = vm . x - halfWidth ;
x2 = vm . x + halfWidth ;
y1 = Math . min ( vm . y , vm . base ) ;
y2 = Math . max ( vm . y , vm . base ) ;
} else {
// horizontal bar
var halfHeight = vm . height / 2 ;
x1 = Math . min ( vm . x , vm . base ) ;
x2 = Math . max ( vm . x , vm . base ) ;
y1 = vm . y - halfHeight ;
y2 = vm . y + halfHeight ;
}
return {
left : x1 ,
top : y1 ,
right : x2 ,
bottom : y2
} ;
}
Chart . elements . Rectangle = Chart . Element . extend ( {
draw : function ( ) {
var ctx = this . _chart . ctx ;
var vm = this . _view ;
var left , right , top , bottom , signX , signY , borderSkipped ;
var borderWidth = vm . borderWidth ;
if ( ! vm . horizontal ) {
// bar
left = vm . x - vm . width / 2 ;
right = vm . x + vm . width / 2 ;
top = vm . y ;
bottom = vm . base ;
signX = 1 ;
signY = bottom > top ? 1 : - 1 ;
borderSkipped = vm . borderSkipped || 'bottom' ;
} else {
// horizontal bar
left = vm . base ;
right = vm . x ;
top = vm . y - vm . height / 2 ;
bottom = vm . y + vm . height / 2 ;
signX = right > left ? 1 : - 1 ;
signY = 1 ;
borderSkipped = vm . borderSkipped || 'left' ;
}
// 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 ( borderWidth ) {
// borderWidth shold be less than bar width and bar height.
var barSize = Math . min ( Math . abs ( left - right ) , Math . abs ( top - bottom ) ) ;
borderWidth = borderWidth > barSize ? barSize : borderWidth ;
var halfStroke = borderWidth / 2 ;
// Adjust borderWidth when bar top position is near vm.base(zero).
var borderLeft = left + ( borderSkipped !== 'left' ? halfStroke * signX : 0 ) ;
var borderRight = right + ( borderSkipped !== 'right' ? - halfStroke * signX : 0 ) ;
var borderTop = top + ( borderSkipped !== 'top' ? halfStroke * signY : 0 ) ;
var borderBottom = bottom + ( borderSkipped !== 'bottom' ? - halfStroke * signY : 0 ) ;
// not become a vertical line?
if ( borderLeft !== borderRight ) {
top = borderTop ;
bottom = borderBottom ;
}
// not become a horizontal line?
if ( borderTop !== borderBottom ) {
left = borderLeft ;
right = borderRight ;
}
}
ctx . beginPath ( ) ;
ctx . fillStyle = vm . backgroundColor ;
ctx . strokeStyle = vm . borderColor ;
ctx . lineWidth = borderWidth ;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[ left , bottom ] ,
[ left , top ] ,
[ right , top ] ,
[ right , bottom ]
] ;
// Find first (starting) corner with fallback to 'bottom'
var borders = [ 'bottom' , 'left' , 'top' , 'right' ] ;
var startCorner = borders . indexOf ( borderSkipped , 0 ) ;
if ( startCorner === - 1 ) {
startCorner = 0 ;
}
function cornerAt ( index ) {
return corners [ ( startCorner + index ) % 4 ] ;
}
// Draw rectangle from 'startCorner'
var corner = cornerAt ( 0 ) ;
ctx . moveTo ( corner [ 0 ] , corner [ 1 ] ) ;
for ( var i = 1 ; i < 4 ; i ++ ) {
corner = cornerAt ( i ) ;
ctx . lineTo ( corner [ 0 ] , corner [ 1 ] ) ;
}
ctx . fill ( ) ;
if ( borderWidth ) {
ctx . stroke ( ) ;
}
} ,
height : function ( ) {
var vm = this . _view ;
return vm . base - vm . y ;
} ,
inRange : function ( mouseX , mouseY ) {
var inRange = false ;
if ( this . _view ) {
var bounds = getBarBounds ( this ) ;
inRange = mouseX >= bounds . left && mouseX <= bounds . right && mouseY >= bounds . top && mouseY <= bounds . bottom ;
}
return inRange ;
} ,
inLabelRange : function ( mouseX , mouseY ) {
var me = this ;
if ( ! me . _view ) {
return false ;
}
var inRange = false ;
var bounds = getBarBounds ( me ) ;
if ( isVertical ( me ) ) {
inRange = mouseX >= bounds . left && mouseX <= bounds . right ;
} else {
inRange = mouseY >= bounds . top && mouseY <= bounds . bottom ;
}
return inRange ;
} ,
inXRange : function ( mouseX ) {
var bounds = getBarBounds ( this ) ;
return mouseX >= bounds . left && mouseX <= bounds . right ;
} ,
inYRange : function ( mouseY ) {
var bounds = getBarBounds ( this ) ;
return mouseY >= bounds . top && mouseY <= bounds . bottom ;
} ,
getCenterPoint : function ( ) {
var vm = this . _view ;
var x , y ;
if ( isVertical ( this ) ) {
x = vm . x ;
y = ( vm . y + vm . base ) / 2 ;
} else {
x = ( vm . x + vm . base ) / 2 ;
y = vm . y ;
}
return { x : x , y : y } ;
} ,
getArea : function ( ) {
var vm = this . _view ;
return vm . width * Math . abs ( vm . y - vm . base ) ;
} ,
tooltipPosition : function ( ) {
var vm = this . _view ;
return {
x : vm . x ,
y : vm . y
} ;
}
} ) ;
} ;
} , { } ] , 41 : [ function ( require , module , exports ) {
'use strict' ;
// Chart.Platform implementation for targeting a web browser
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
// DOM event types -> Chart.js event types.
// Note: only events with different types are mapped.
// https://developer.mozilla.org/en-US/docs/Web/Events
var eventTypeMap = {
// Touch events
touchstart : 'mousedown' ,
touchmove : 'mousemove' ,
touchend : 'mouseup' ,
// Pointer events
pointerenter : 'mouseenter' ,
pointerdown : 'mousedown' ,
pointermove : 'mousemove' ,
pointerup : 'mouseup' ,
pointerleave : 'mouseout' ,
pointerout : 'mouseout'
} ;
/ * *
* The "used" size is the final value of a dimension property after all calculations have
* been performed . This method uses the computed style of ` element ` but returns undefined
* if the computed style is not expressed in pixels . That can happen in some cases where
* ` element ` has a size relative to its parent and this last one is not yet displayed ,
* for example because of ` display: none ` on a parent node .
* @ see https : //developer.mozilla.org/en-US/docs/Web/CSS/used_value
* @ returns { Number } Size in pixels or undefined if unknown .
* /
function readUsedSize ( element , property ) {
var value = helpers . getStyle ( element , property ) ;
var matches = value && value . match ( /(\d+)px/ ) ;
return matches ? Number ( matches [ 1 ] ) : undefined ;
}
/ * *
* Initializes the canvas style and render size without modifying the canvas display size ,
* since responsiveness is handled by the controller . resize ( ) method . The config is used
* to determine the aspect ratio to apply in case no explicit height has been specified .
* /
function initCanvas ( canvas , config ) {
var style = canvas . style ;
// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
// returns null or '' if no explicit value has been set to the canvas attribute.
var renderHeight = canvas . getAttribute ( 'height' ) ;
var renderWidth = canvas . getAttribute ( 'width' ) ;
// Chart.js modifies some canvas values that we want to restore on destroy
canvas . _chartjs = {
initial : {
height : renderHeight ,
width : renderWidth ,
style : {
display : style . display ,
height : style . height ,
width : style . width
}
}
} ;
// Force canvas to display as block to avoid extra space caused by inline
// elements, which would interfere with the responsive resize process.
// https://github.com/chartjs/Chart.js/issues/2538
style . display = style . display || 'block' ;
if ( renderWidth === null || renderWidth === '' ) {
var displayWidth = readUsedSize ( canvas , 'width' ) ;
if ( displayWidth !== undefined ) {
canvas . width = displayWidth ;
}
}
if ( renderHeight === null || renderHeight === '' ) {
if ( canvas . style . height === '' ) {
// If no explicit render height and style height, let's apply the aspect ratio,
// which one can be specified by the user but also by charts as default option
// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
canvas . height = canvas . width / ( config . options . aspectRatio || 2 ) ;
} else {
var displayHeight = readUsedSize ( canvas , 'height' ) ;
if ( displayWidth !== undefined ) {
canvas . height = displayHeight ;
}
}
}
return canvas ;
}
function createEvent ( type , chart , x , y , native ) {
return {
type : type ,
chart : chart ,
native : native || null ,
x : x !== undefined ? x : null ,
y : y !== undefined ? y : null ,
} ;
}
function fromNativeEvent ( event , chart ) {
var type = eventTypeMap [ event . type ] || event . type ;
var pos = helpers . getRelativePosition ( event , chart ) ;
return createEvent ( type , chart , pos . x , pos . y , event ) ;
}
function createResizer ( handler ) {
var iframe = document . createElement ( 'iframe' ) ;
iframe . className = 'chartjs-hidden-iframe' ;
iframe . style . cssText =
'display:block;' +
'overflow:hidden;' +
'border:0;' +
'margin:0;' +
'top:0;' +
'left:0;' +
'bottom:0;' +
'right:0;' +
'height:100%;' +
'width:100%;' +
'position:absolute;' +
'pointer-events:none;' +
'z-index:-1;' ;
// Prevent the iframe to gain focus on tab.
// https://github.com/chartjs/Chart.js/issues/3090
iframe . tabIndex = - 1 ;
// If the iframe is re-attached to the DOM, the resize listener is removed because the
// content is reloaded, so make sure to install the handler after the iframe is loaded.
// https://github.com/chartjs/Chart.js/issues/3521
helpers . addEvent ( iframe , 'load' , function ( ) {
helpers . addEvent ( iframe . contentWindow || iframe , 'resize' , handler ) ;
// The iframe size might have changed while loading, which can also
// happen if the size has been changed while detached from the DOM.
handler ( ) ;
} ) ;
return iframe ;
}
function addResizeListener ( node , listener , chart ) {
var stub = node . _chartjs = {
ticking : false
} ;
// Throttle the callback notification until the next animation frame.
var notify = function ( ) {
if ( ! stub . ticking ) {
stub . ticking = true ;
helpers . requestAnimFrame . call ( window , function ( ) {
if ( stub . resizer ) {
stub . ticking = false ;
return listener ( createEvent ( 'resize' , chart ) ) ;
}
} ) ;
}
} ;
// Let's keep track of this added iframe and thus avoid DOM query when removing it.
stub . resizer = createResizer ( notify ) ;
node . insertBefore ( stub . resizer , node . firstChild ) ;
}
function removeResizeListener ( node ) {
if ( ! node || ! node . _chartjs ) {
return ;
}
var resizer = node . _chartjs . resizer ;
if ( resizer ) {
resizer . parentNode . removeChild ( resizer ) ;
node . _chartjs . resizer = null ;
}
delete node . _chartjs ;
}
return {
acquireContext : function ( item , config ) {
if ( typeof item === 'string' ) {
item = document . getElementById ( item ) ;
} else if ( item . length ) {
// Support for array based queries (such as jQuery)
item = item [ 0 ] ;
}
if ( item && item . canvas ) {
// Support for any object associated to a canvas (including a context2d)
item = item . canvas ;
}
if ( item instanceof HTMLCanvasElement ) {
// To prevent canvas fingerprinting, some add-ons undefine the getContext
// method, for example: https://github.com/kkapsner/CanvasBlocker
// https://github.com/chartjs/Chart.js/issues/2807
var context = item . getContext && item . getContext ( '2d' ) ;
if ( context instanceof CanvasRenderingContext2D ) {
initCanvas ( item , config ) ;
return context ;
}
}
return null ;
} ,
releaseContext : function ( context ) {
var canvas = context . canvas ;
if ( ! canvas . _chartjs ) {
return ;
}
var initial = canvas . _chartjs . initial ;
[ 'height' , 'width' ] . forEach ( function ( prop ) {
var value = initial [ prop ] ;
if ( value === undefined || value === null ) {
canvas . removeAttribute ( prop ) ;
} else {
canvas . setAttribute ( prop , value ) ;
}
} ) ;
helpers . each ( initial . style || { } , function ( value , key ) {
canvas . style [ key ] = value ;
} ) ;
// The canvas render size might have been changed (and thus the state stack discarded),
// we can't use save() and restore() to restore the initial state. So make sure that at
// least the canvas context is reset to the default state by setting the canvas width.
// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
canvas . width = canvas . width ;
delete canvas . _chartjs ;
} ,
addEventListener : function ( chart , type , listener ) {
var canvas = chart . chart . canvas ;
if ( type === 'resize' ) {
// Note: the resize event is not supported on all browsers.
addResizeListener ( canvas . parentNode , listener , chart . chart ) ;
return ;
}
var stub = listener . _chartjs || ( listener . _chartjs = { } ) ;
var proxies = stub . proxies || ( stub . proxies = { } ) ;
var proxy = proxies [ chart . id + '_' + type ] = function ( event ) {
listener ( fromNativeEvent ( event , chart . chart ) ) ;
} ;
helpers . addEvent ( canvas , type , proxy ) ;
} ,
removeEventListener : function ( chart , type , listener ) {
var canvas = chart . chart . canvas ;
if ( type === 'resize' ) {
// Note: the resize event is not supported on all browsers.
removeResizeListener ( canvas . parentNode , listener ) ;
return ;
}
var stub = listener . _chartjs || { } ;
var proxies = stub . proxies || { } ;
var proxy = proxies [ chart . id + '_' + type ] ;
if ( ! proxy ) {
return ;
}
helpers . removeEvent ( canvas , type , proxy ) ;
}
} ;
} ;
} , { } ] , 42 : [ function ( require , module , exports ) {
'use strict' ;
// By default, select the browser (DOM) platform.
// @TODO Make possible to select another platform at build time.
var implementation = require ( 41 ) ;
module . exports = function ( Chart ) {
/ * *
* @ namespace Chart . platform
* @ see https : //chartjs.gitbooks.io/proposals/content/Platform.html
* @ since 2.4 . 0
* /
Chart . platform = {
/ * *
* Called at chart construction time , returns a context2d instance implementing
* the [ W3C Canvas 2 D Context API standard ] { @ link https : //www.w3.org/TR/2dcontext/}.
* @ param { * } item - The native item from which to acquire context ( platform specific )
* @ param { Object } options - The chart options
* @ returns { CanvasRenderingContext2D } context2d instance
* /
acquireContext : function ( ) { } ,
/ * *
* Called at chart destruction time , releases any resources associated to the context
* previously returned by the acquireContext ( ) method .
* @ param { CanvasRenderingContext2D } context - The context2d instance
* @ returns { Boolean } true if the method succeeded , else false
* /
releaseContext : function ( ) { } ,
/ * *
* Registers the specified listener on the given chart .
* @ param { Chart } chart - Chart from which to listen for event
* @ param { String } type - The ( { @ link IEvent } ) type to listen for
* @ param { Function } listener - Receives a notification ( an object that implements
* the { @ link IEvent } interface ) when an event of the specified type occurs .
* /
addEventListener : function ( ) { } ,
/ * *
* Removes the specified listener previously registered with addEventListener .
* @ param { Chart } chart - Chart from which to remove the listener
* @ param { String } type - The ( { @ link IEvent } ) type to remove
* @ param { Function } listener - The listener function to remove from the event target .
* /
removeEventListener : function ( ) { }
} ;
/ * *
* @ interface IPlatform
* Allows abstracting platform dependencies away from the chart
* @ borrows Chart . platform . acquireContext as acquireContext
* @ borrows Chart . platform . releaseContext as releaseContext
* @ borrows Chart . platform . addEventListener as addEventListener
* @ borrows Chart . platform . removeEventListener as removeEventListener
* /
/ * *
* @ interface IEvent
* @ prop { String } type - The event type name , possible values are :
* 'contextmenu' , 'mouseenter' , 'mousedown' , 'mousemove' , 'mouseup' , 'mouseout' ,
* 'click' , 'dblclick' , 'keydown' , 'keypress' , 'keyup' and 'resize'
* @ prop { * } native - The original native event ( null for emulated events , e . g . 'resize' )
* @ prop { Number } x - The mouse x position , relative to the canvas ( null for incompatible events )
* @ prop { Number } y - The mouse y position , relative to the canvas ( null for incompatible events )
* /
Chart . helpers . extend ( Chart . platform , implementation ( Chart ) ) ;
} ;
} , { "41" : 41 } ] , 43 : [ 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 those
* 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 , datasetIndex ) {
var me = this ;
var data = me . chart . data ;
var isHorizontal = me . isHorizontal ( ) ;
if ( data . yLabels && ! isHorizontal ) {
return me . getRightValue ( data . datasets [ datasetIndex ] . data [ index ] ) ;
}
return me . ticks [ index - me . minIndex ] ;
} ,
// 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 valueWidth = me . width / offsetAmt ;
var widthOffset = ( valueWidth * ( index - me . minIndex ) ) ;
if ( me . options . gridLines . offsetGridLines && includeOffset || me . maxIndex === me . minIndex && includeOffset ) {
widthOffset += ( valueWidth / 2 ) ;
}
return me . left + Math . round ( widthOffset ) ;
}
var valueHeight = me . height / offsetAmt ;
var heightOffset = ( valueHeight * ( index - me . minIndex ) ) ;
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 valueDimension = ( horz ? me . width : me . height ) / offsetAmt ;
pixel -= horz ? me . left : me . top ;
if ( me . options . gridLines . offsetGridLines ) {
pixel -= ( valueDimension / 2 ) ;
}
if ( pixel <= 0 ) {
value = 0 ;
} else {
value = Math . round ( pixel / valueDimension ) ;
}
return value ;
} ,
getBasePixel : function ( ) {
return this . bottom ;
}
} ) ;
Chart . scaleService . registerScaleType ( 'category' , DatasetScale , defaultConfig ) ;
} ;
} , { } ] , 44 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var defaultConfig = {
position : 'left' ,
ticks : {
callback : Chart . Ticks . formatters . linear
}
} ;
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 ;
var hasStacks = opts . stacked ;
if ( hasStacks === undefined ) {
helpers . each ( datasets , function ( dataset , datasetIndex ) {
if ( hasStacks ) {
return ;
}
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) &&
meta . stack !== undefined ) {
hasStacks = true ;
}
} ) ;
}
if ( opts . stacked || hasStacks ) {
var valuesPerStack = { } ;
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
var key = [
meta . type ,
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
( ( opts . stacked === undefined && meta . stack === undefined ) ? datasetIndex : '' ) ,
meta . stack
] . join ( '.' ) ;
if ( valuesPerStack [ key ] === undefined ) {
valuesPerStack [ key ] = {
positiveValues : [ ] ,
negativeValues : [ ]
} ;
}
// Store these per type
var positiveValues = valuesPerStack [ key ] . positiveValues ;
var negativeValues = valuesPerStack [ key ] . 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 ) {
negativeValues [ index ] += value ;
} else {
positiveValues [ index ] += value ;
}
} ) ;
}
} ) ;
helpers . each ( valuesPerStack , 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 start = me . start ;
var rightValue = + me . getRightValue ( value ) ;
var pixel ;
var range = me . end - start ;
if ( me . isHorizontal ( ) ) {
pixel = me . left + ( me . width / range * ( rightValue - start ) ) ;
return Math . round ( pixel ) ;
}
pixel = me . bottom - ( me . height / range * ( rightValue - start ) ) ;
return Math . round ( pixel ) ;
} ,
getValueForPixel : function ( pixel ) {
var me = this ;
var isHorizontal = me . isHorizontal ( ) ;
var innerDimension = isHorizontal ? me . width : me . height ;
var offset = ( isHorizontal ? pixel - me . left : me . bottom - 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 ) ;
} ;
} , { } ] , 45 : [ 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 bottom 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 tickOpts = opts . 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. Make sure we always have at least 2 ticks
var maxTicks = me . getTickLimit ( ) ;
maxTicks = Math . max ( 2 , maxTicks ) ;
var numericGeneratorOptions = {
maxTicks : maxTicks ,
min : tickOpts . min ,
max : tickOpts . max ,
stepSize : helpers . getValueOrDefault ( tickOpts . fixedStepSize , tickOpts . stepSize )
} ;
var ticks = me . ticks = Chart . Ticks . generators . linear ( numericGeneratorOptions , me ) ;
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 ;
}
} ,
convertTicksToLabels : function ( ) {
var me = this ;
me . ticksAsNumbers = me . ticks . slice ( ) ;
me . zeroLineIndex = me . ticks . indexOf ( 0 ) ;
Chart . Scale . prototype . convertTicksToLabels . call ( me ) ;
}
} ) ;
} ;
} , { } ] , 46 : [ function ( require , module , exports ) {
'use strict' ;
module . exports = function ( Chart ) {
var helpers = Chart . helpers ;
var defaultConfig = {
position : 'left' ,
// label settings
ticks : {
callback : Chart . Ticks . formatters . logarithmic
}
} ;
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 ;
var hasStacks = opts . stacked ;
if ( hasStacks === undefined ) {
helpers . each ( datasets , function ( dataset , datasetIndex ) {
if ( hasStacks ) {
return ;
}
var meta = chart . getDatasetMeta ( datasetIndex ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) &&
meta . stack !== undefined ) {
hasStacks = true ;
}
} ) ;
}
if ( opts . stacked || hasStacks ) {
var valuesPerStack = { } ;
helpers . each ( datasets , function ( dataset , datasetIndex ) {
var meta = chart . getDatasetMeta ( datasetIndex ) ;
var key = [
meta . type ,
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
( ( opts . stacked === undefined && meta . stack === undefined ) ? datasetIndex : '' ) ,
meta . stack
] . join ( '.' ) ;
if ( chart . isDatasetVisible ( datasetIndex ) && IDMatches ( meta ) ) {
if ( valuesPerStack [ key ] === undefined ) {
valuesPerStack [ key ] = [ ] ;
}
helpers . each ( dataset . data , function ( rawValue , index ) {
var values = valuesPerStack [ key ] ;
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 ( valuesPerStack , 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 ;
}
}
} ,
buildTicks : function ( ) {
var me = this ;
var opts = me . options ;
var tickOpts = opts . ticks ;
var generationOptions = {
min : tickOpts . min ,
max : tickOpts . max
} ;
var ticks = me . ticks = Chart . Ticks . generators . logarithmic ( generationOptions , me ) ;
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 ;
}
} ,
convertTicksToLabels : function ( ) {
this . tickValues = this . ticks . slice ( ) ;
Chart . Scale . prototype . convertTicksToLabels . call ( this ) ;
} ,
// Get the correct tooltip label
getLabelForIndex : function ( index , datasetIndex ) {
return + this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
} ,
getPixelForTick : function ( index ) {
return this . getPixelForValue ( this . tickValues [ index ] ) ;
} ,
getPixelForValue : function ( value ) {
var me = this ;
var innerDimension ;
var pixel ;
var start = me . start ;
var newVal = + me . getRightValue ( value ) ;
var range ;
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 ;
} else {
innerDimension = me . width ;
pixel = me . left + ( innerDimension / range * ( helpers . log10 ( newVal ) - helpers . log10 ( start ) ) ) ;
}
} else {
// Bottom - top since pixels increase downward on a screen
innerDimension = me . height ;
if ( start === 0 && ! tickOpts . reverse ) {
range = helpers . log10 ( me . end ) - helpers . log10 ( me . minNotZero ) ;
if ( newVal === start ) {
pixel = me . bottom ;
} else if ( newVal === me . minNotZero ) {
pixel = me . bottom - innerDimension * 0.02 ;
} else {
pixel = me . bottom - 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 ;
} else if ( newVal === me . minNotZero ) {
pixel = me . top + innerDimension * 0.02 ;
} else {
pixel = me . top + 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 ;
pixel = me . bottom - ( innerDimension / range * ( helpers . log10 ( newVal ) - helpers . log10 ( start ) ) ) ;
}
}
return pixel ;
} ,
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 ;
value = me . start * Math . pow ( 10 , ( pixel - me . left ) * range / innerDimension ) ;
} else { // todo: if start === 0
innerDimension = me . height ;
value = Math . pow ( 10 , ( me . bottom - pixel ) * range / innerDimension ) / me . start ;
}
return value ;
}
} ) ;
Chart . scaleService . registerScaleType ( 'logarithmic' , LogarithmicScale , defaultConfig ) ;
} ;
} , { } ] , 47 : [ 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
} ,
// 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 ,
callback : Chart . Ticks . formatters . linear
} ,
pointLabels : {
// Number - Point label font size in pixels
fontSize : 10 ,
// Function - Used to convert point labels
callback : function ( label ) {
return label ;
}
}
} ;
function getValueCount ( scale ) {
return ! scale . options . lineArc ? scale . chart . data . labels . length : 0 ;
}
function getPointLabelFontOptions ( scale ) {
var pointLabelOptions = scale . options . pointLabels ;
var fontSize = helpers . getValueOrDefault ( pointLabelOptions . fontSize , globalDefaults . defaultFontSize ) ;
var fontStyle = helpers . getValueOrDefault ( pointLabelOptions . fontStyle , globalDefaults . defaultFontStyle ) ;
var fontFamily = helpers . getValueOrDefault ( pointLabelOptions . fontFamily , globalDefaults . defaultFontFamily ) ;
var font = helpers . fontString ( fontSize , fontStyle , fontFamily ) ;
return {
size : fontSize ,
style : fontStyle ,
family : fontFamily ,
font : font
} ;
}
function measureLabelSize ( ctx , fontSize , label ) {
if ( helpers . isArray ( label ) ) {
return {
w : helpers . longestText ( ctx , ctx . font , label ) ,
h : ( label . length * fontSize ) + ( ( label . length - 1 ) * 1.5 * fontSize )
} ;
}
return {
w : ctx . measureText ( label ) . width ,
h : fontSize
} ;
}
function determineLimits ( angle , pos , size , min , max ) {
if ( angle === min || angle === max ) {
return {
start : pos - ( size / 2 ) ,
end : pos + ( size / 2 )
} ;
} else if ( angle < min || angle > max ) {
return {
start : pos - size - 5 ,
end : pos
} ;
}
return {
start : pos ,
end : pos + size + 5
} ;
}
/ * *
* Helper function to fit a radial linear scale with point labels
* /
function fitWithPointLabels ( scale ) {
/ *
* 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 plFont = getPointLabelFontOptions ( scale ) ;
// 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 = Math . min ( scale . height / 2 , scale . width / 2 ) ;
var furthestLimits = {
l : scale . width ,
r : 0 ,
t : scale . height ,
b : 0
} ;
var furthestAngles = { } ;
var i ;
var textSize ;
var pointPosition ;
scale . ctx . font = plFont . font ;
scale . _pointLabelSizes = [ ] ;
var valueCount = getValueCount ( scale ) ;
for ( i = 0 ; i < valueCount ; i ++ ) {
pointPosition = scale . getPointPosition ( i , largestPossibleRadius ) ;
textSize = measureLabelSize ( scale . ctx , plFont . size , scale . pointLabels [ i ] || '' ) ;
scale . _pointLabelSizes [ i ] = textSize ;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = scale . getIndexAngle ( i ) ;
var angle = helpers . toDegrees ( angleRadians ) % 360 ;
var hLimits = determineLimits ( angle , pointPosition . x , textSize . w , 0 , 180 ) ;
var vLimits = determineLimits ( angle , pointPosition . y , textSize . h , 90 , 270 ) ;
if ( hLimits . start < furthestLimits . l ) {
furthestLimits . l = hLimits . start ;
furthestAngles . l = angleRadians ;
}
if ( hLimits . end > furthestLimits . r ) {
furthestLimits . r = hLimits . end ;
furthestAngles . r = angleRadians ;
}
if ( vLimits . start < furthestLimits . t ) {
furthestLimits . t = vLimits . start ;
furthestAngles . t = angleRadians ;
}
if ( vLimits . end > furthestLimits . b ) {
furthestLimits . b = vLimits . end ;
furthestAngles . b = angleRadians ;
}
}
scale . setReductions ( largestPossibleRadius , furthestLimits , furthestAngles ) ;
}
/ * *
* Helper function to fit a radial linear scale with no point labels
* /
function fit ( scale ) {
var largestPossibleRadius = Math . min ( scale . height / 2 , scale . width / 2 ) ;
scale . drawingArea = Math . round ( largestPossibleRadius ) ;
scale . setCenterPoint ( 0 , 0 , 0 , 0 ) ;
}
function getTextAlignForAngle ( angle ) {
if ( angle === 0 || angle === 180 ) {
return 'center' ;
} else if ( angle < 180 ) {
return 'left' ;
}
return 'right' ;
}
function fillText ( ctx , text , position , fontSize ) {
if ( helpers . isArray ( text ) ) {
var y = position . y ;
var spacing = 1.5 * fontSize ;
for ( var i = 0 ; i < text . length ; ++ i ) {
ctx . fillText ( text [ i ] , position . x , y ) ;
y += spacing ;
}
} else {
ctx . fillText ( text , position . x , position . y ) ;
}
}
function adjustPointPositionForLabelHeight ( angle , textSize , position ) {
if ( angle === 90 || angle === 270 ) {
position . y -= ( textSize . h / 2 ) ;
} else if ( angle > 270 || angle < 90 ) {
position . y -= textSize . h ;
}
}
function drawPointLabels ( scale ) {
var ctx = scale . ctx ;
var getValueOrDefault = helpers . getValueOrDefault ;
var opts = scale . options ;
var angleLineOpts = opts . angleLines ;
var pointLabelOpts = opts . pointLabels ;
ctx . lineWidth = angleLineOpts . lineWidth ;
ctx . strokeStyle = angleLineOpts . color ;
var outerDistance = scale . getDistanceFromCenterForValue ( opts . reverse ? scale . min : scale . max ) ;
// Point Label Font
var plFont = getPointLabelFontOptions ( scale ) ;
ctx . textBaseline = 'top' ;
for ( var i = getValueCount ( scale ) - 1 ; i >= 0 ; i -- ) {
if ( angleLineOpts . display ) {
var outerPosition = scale . getPointPosition ( i , outerDistance ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( scale . xCenter , scale . yCenter ) ;
ctx . lineTo ( outerPosition . x , outerPosition . y ) ;
ctx . stroke ( ) ;
ctx . closePath ( ) ;
}
// Extra 3px out for some label spacing
var pointLabelPosition = scale . getPointPosition ( i , outerDistance + 5 ) ;
// Keep this in loop since we may support array properties here
var pointLabelFontColor = getValueOrDefault ( pointLabelOpts . fontColor , globalDefaults . defaultFontColor ) ;
ctx . font = plFont . font ;
ctx . fillStyle = pointLabelFontColor ;
var angleRadians = scale . getIndexAngle ( i ) ;
var angle = helpers . toDegrees ( angleRadians ) ;
ctx . textAlign = getTextAlignForAngle ( angle ) ;
adjustPointPositionForLabelHeight ( angle , scale . _pointLabelSizes [ i ] , pointLabelPosition ) ;
fillText ( ctx , scale . pointLabels [ i ] || '' , pointLabelPosition , plFont . size ) ;
}
}
function drawRadiusLine ( scale , gridLineOpts , radius , index ) {
var ctx = scale . ctx ;
ctx . strokeStyle = helpers . getValueAtIndexOrDefault ( gridLineOpts . color , index - 1 ) ;
ctx . lineWidth = helpers . getValueAtIndexOrDefault ( gridLineOpts . lineWidth , index - 1 ) ;
if ( scale . options . lineArc ) {
// Draw circular arcs between the points
ctx . beginPath ( ) ;
ctx . arc ( scale . xCenter , scale . yCenter , radius , 0 , Math . PI * 2 ) ;
ctx . closePath ( ) ;
ctx . stroke ( ) ;
} else {
// Draw straight lines connecting each index
var valueCount = getValueCount ( scale ) ;
if ( valueCount === 0 ) {
return ;
}
ctx . beginPath ( ) ;
var pointPosition = scale . getPointPosition ( 0 , radius ) ;
ctx . moveTo ( pointPosition . x , pointPosition . y ) ;
for ( var i = 1 ; i < valueCount ; i ++ ) {
pointPosition = scale . getPointPosition ( i , radius ) ;
ctx . lineTo ( pointPosition . x , pointPosition . y ) ;
}
ctx . closePath ( ) ;
ctx . stroke ( ) ;
}
}
function numberOrZero ( param ) {
return helpers . isNumber ( param ) ? param : 0 ;
}
var LinearRadialScale = Chart . LinearScaleBase . extend ( {
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 ) ;
} ,
determineDataLimits : function ( ) {
var me = this ;
var chart = me . chart ;
var min = Number . POSITIVE _INFINITY ;
var max = Number . NEGATIVE _INFINITY ;
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 ;
}
min = Math . min ( value , min ) ;
max = Math . max ( value , max ) ;
} ) ;
}
} ) ;
me . min = ( min === Number . POSITIVE _INFINITY ? 0 : min ) ;
me . max = ( max === Number . NEGATIVE _INFINITY ? 0 : max ) ;
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
me . handleTickRangeOptions ( ) ;
} ,
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 ) ) ) ;
} ,
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 ) ;
} ,
getLabelForIndex : function ( index , datasetIndex ) {
return + this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
} ,
fit : function ( ) {
if ( this . options . lineArc ) {
fit ( this ) ;
} else {
fitWithPointLabels ( this ) ;
}
} ,
/ * *
* Set radius reductions and determine new radius and center point
* @ private
* /
setReductions : function ( largestPossibleRadius , furthestLimits , furthestAngles ) {
var me = this ;
var radiusReductionLeft = furthestLimits . l / Math . sin ( furthestAngles . l ) ;
var radiusReductionRight = Math . max ( furthestLimits . r - me . width , 0 ) / Math . sin ( furthestAngles . r ) ;
var radiusReductionTop = - furthestLimits . t / Math . cos ( furthestAngles . t ) ;
var radiusReductionBottom = - Math . max ( furthestLimits . b - me . height , 0 ) / Math . cos ( furthestAngles . b ) ;
radiusReductionLeft = numberOrZero ( radiusReductionLeft ) ;
radiusReductionRight = numberOrZero ( radiusReductionRight ) ;
radiusReductionTop = numberOrZero ( radiusReductionTop ) ;
radiusReductionBottom = numberOrZero ( radiusReductionBottom ) ;
me . drawingArea = Math . min (
Math . round ( largestPossibleRadius - ( radiusReductionLeft + radiusReductionRight ) / 2 ) ,
Math . round ( largestPossibleRadius - ( radiusReductionTop + radiusReductionBottom ) / 2 ) ) ;
me . setCenterPoint ( radiusReductionLeft , radiusReductionRight , radiusReductionTop , radiusReductionBottom ) ;
} ,
setCenterPoint : function ( leftMovement , rightMovement , topMovement , bottomMovement ) {
var me = this ;
var maxRight = me . width - rightMovement - me . drawingArea ,
maxLeft = leftMovement + me . drawingArea ,
maxTop = topMovement + me . drawingArea ,
maxBottom = me . height - bottomMovement - me . drawingArea ;
me . xCenter = Math . round ( ( ( maxLeft + maxRight ) / 2 ) + me . left ) ;
me . yCenter = Math . round ( ( ( maxTop + maxBottom ) / 2 ) + me . top ) ;
} ,
getIndexAngle : function ( index ) {
var angleMultiplier = ( Math . PI * 2 ) / getValueCount ( this ) ;
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 + startAngleRadians ;
} ,
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 ;
}
return ( value - me . min ) * scalingFactor ;
} ,
getPointPosition : function ( index , distanceFromCenter ) {
var me = this ;
var thisAngle = me . getIndexAngle ( index ) - ( Math . PI / 2 ) ;
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 ) ) ;
} ,
getBasePosition : function ( ) {
var me = this ;
var min = me . min ;
var max = me . max ;
return me . getPointPositionForValue ( 0 ,
me . beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0 ) ;
} ,
draw : function ( ) {
var me = this ;
var opts = me . options ;
var gridLineOpts = opts . gridLines ;
var tickOpts = opts . ticks ;
var getValueOrDefault = helpers . getValueOrDefault ;
if ( opts . display ) {
var ctx = me . ctx ;
// 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 ) ;
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 ;
// Draw circular lines around the scale
if ( gridLineOpts . display && index !== 0 ) {
drawRadiusLine ( me , gridLineOpts , yCenterOffset , index ) ;
}
if ( tickOpts . display ) {
var tickFontColor = getValueOrDefault ( tickOpts . fontColor , globalDefaults . defaultFontColor ) ;
ctx . font = tickLabelFont ;
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 ) {
drawPointLabels ( me ) ;
}
}
}
} ) ;
Chart . scaleService . registerScaleType ( 'radialLinear' , LinearRadialScale , defaultConfig ) ;
} ;
} , { } ] , 48 : [ 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/
minUnit : 'millisecond' ,
// 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 ( datasetIndex === null || index === null ) {
return null ;
}
if ( typeof this . labelMoments [ datasetIndex ] !== 'undefined' ) {
return this . labelMoments [ datasetIndex ] [ index ] ;
}
return null ;
} ,
getLabelDiff : function ( datasetIndex , index ) {
var me = this ;
if ( datasetIndex === null || index === null ) {
return null ;
}
if ( me . labelDiffs === undefined ) {
me . buildLabelDiffs ( ) ;
}
if ( typeof me . labelDiffs [ datasetIndex ] !== 'undefined' ) {
return me . labelDiffs [ 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 ) ;
}
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 ;
}
helpers . each ( me . chart . data . datasets , function ( dataset , datasetIndex ) {
var momentsForDataset = [ ] ;
var datasetVisible = me . chart . isDatasetVisible ( datasetIndex ) ;
if ( typeof dataset . data [ 0 ] === 'object' && dataset . data [ 0 ] !== null ) {
helpers . each ( dataset . data , function ( value ) {
var labelMoment = me . parseTime ( me . getRightValue ( value ) ) ;
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 ;
}
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 ) ;
}
if ( me . options . time . max ) {
me . lastTick = me . parseTime ( me . options . time . max ) ;
}
// We will modify these, so clone for later
me . firstTick = ( me . firstTick || moment ( ) ) . clone ( ) ;
me . lastTick = ( me . lastTick || moment ( ) ) . clone ( ) ;
} ,
buildLabelDiffs : function ( ) {
var me = this ;
me . labelDiffs = [ ] ;
var scaleLabelDiffs = [ ] ;
// Parse common labels once
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 ) ;
}
scaleLabelDiffs . push ( labelMoment . diff ( me . firstTick , me . tickUnit , true ) ) ;
}
} , me ) ;
}
helpers . each ( me . chart . data . datasets , function ( dataset ) {
var diffsForDataset = [ ] ;
if ( typeof dataset . data [ 0 ] === 'object' && dataset . data [ 0 ] !== null ) {
helpers . each ( dataset . data , function ( value ) {
var labelMoment = me . parseTime ( me . getRightValue ( value ) ) ;
if ( labelMoment . isValid ( ) ) {
if ( me . options . time . round ) {
labelMoment . startOf ( me . options . time . round ) ;
}
diffsForDataset . push ( labelMoment . diff ( me . firstTick , me . tickUnit , true ) ) ;
}
} , me ) ;
} else {
// We have no labels. Use common ones
diffsForDataset = scaleLabelDiffs ;
}
me . labelDiffs . push ( diffsForDataset ) ;
} , me ) ;
} ,
buildTicks : function ( ) {
var me = this ;
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 ;
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)
// 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 . height ;
// 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 ) ;
// Start as small as possible
me . tickUnit = me . options . time . minUnit ;
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
me . displayFormat = me . options . time . displayFormats [ me . tickUnit ] ;
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 predefined 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 ] ;
}
}
}
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 ) ;
}
// 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 ;
}
me . scaleSizeInUnits = me . lastTick . diff ( me . firstTick , me . tickUnit , true ) ;
}
// Tick displayFormat override
if ( me . options . time . displayFormat ) {
me . displayFormat = me . options . time . displayFormat ;
}
// first tick. will have been rounded correctly if options.time.min is not specified
me . ticks . push ( me . firstTick . clone ( ) ) ;
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
for ( var i = me . unitScale ; i <= me . scaleSizeInUnits ; i += me . unitScale ) {
var newTick = roundedStart . clone ( ) . add ( i , me . tickUnit ) ;
// Are we greater than the max time
if ( me . options . time . max && newTick . diff ( me . lastTick , me . tickUnit , true ) >= 0 ) {
break ;
}
me . ticks . push ( newTick ) ;
}
// 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 ) ;
}
}
me . ctx . restore ( ) ;
// Invalidate label diffs cache
me . labelDiffs = undefined ;
} ,
// 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 ] : '' ;
var value = me . chart . data . datasets [ datasetIndex ] . data [ index ] ;
if ( value !== null && typeof value === 'object' ) {
label = me . getRightValue ( value ) ;
}
// Format nicely
if ( me . options . time . tooltipFormat ) {
label = me . parseTime ( label ) . format ( me . options . time . tooltipFormat ) ;
}
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 ) ;
if ( callback ) {
return callback ( formattedTick , index , ticks ) ;
}
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 ;
var offset = null ;
if ( index !== undefined && datasetIndex !== undefined ) {
offset = me . getLabelDiff ( datasetIndex , index ) ;
}
if ( offset === null ) {
if ( ! value || ! value . isValid ) {
// not already a moment object
value = me . parseTime ( me . getRightValue ( value ) ) ;
}
if ( value && value . isValid && value . isValid ( ) ) {
offset = value . diff ( me . firstTick , me . tickUnit , true ) ;
}
}
if ( offset !== null ) {
var decimal = offset !== 0 ? offset / me . scaleSizeInUnits : offset ;
if ( me . isHorizontal ( ) ) {
var valueOffset = ( me . width * decimal ) ;
return me . left + Math . round ( valueOffset ) ;
}
var heightOffset = ( me . height * decimal ) ;
return me . top + Math . round ( heightOffset ) ;
}
} ,
getPixelForTick : function ( index ) {
return this . getPixelForValue ( this . tickMoments [ index ] , null , null ) ;
} ,
getValueForPixel : function ( pixel ) {
var me = this ;
var innerDimension = me . isHorizontal ( ) ? me . width : me . height ;
var offset = ( pixel - ( me . isHorizontal ( ) ? me . left : me . top ) ) / 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 ) ;
}
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 ) ;
}
} ) ;
Chart . scaleService . registerScaleType ( 'time' , TimeScale , defaultConfig ) ;
} ;
} , { "1" : 1 } ] } , { } , [ 7 ] ) ( 7 )
} ) ;