2015-09-11 06:48:48 +02:00
( function ( ) {
"use strict" ;
2015-09-28 19:27:47 +02:00
if ( ! window . moment ) {
console . warn ( 'Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at http://momentjs.com/' ) ;
return ;
}
2015-09-11 06:48:48 +02:00
var root = this ,
Chart = root . Chart ,
helpers = Chart . helpers ;
2015-09-15 19:40:01 +02:00
var time = {
units : [
'millisecond' ,
'second' ,
'minute' ,
'hour' ,
'day' ,
'week' ,
'month' ,
'quarter' ,
'year' ,
] ,
unit : {
'millisecond' : {
display : 'SSS [ms]' , // 002 ms
maxStep : 1000 ,
} ,
'second' : {
display : 'h:mm:ss a' , // 11:20:01 AM
maxStep : 60 ,
} ,
'minute' : {
display : 'h:mm:ss a' , // 11:20:01 AM
maxStep : 60 ,
} ,
'hour' : {
display : 'MMM D, hA' , // Sept 4, 5PM
maxStep : 24 ,
} ,
'day' : {
display : 'll' , // Sep 4 2015
maxStep : 7 ,
} ,
'week' : {
display : 'll' , // Week 46, or maybe "[W]WW - YYYY" ?
maxStep : 4.3333 ,
} ,
'month' : {
display : 'MMM YYYY' , // Sept 2015
maxStep : 12 ,
} ,
'quarter' : {
display : '[Q]Q - YYYY' , // Q3
maxStep : 4 ,
} ,
'year' : {
display : 'YYYY' , // 2015
maxStep : false ,
} ,
}
} ;
2015-09-11 06:48:48 +02:00
var defaultConfig = {
position : "bottom" ,
2015-09-24 05:52:31 +02:00
time : {
2015-09-15 19:40:01 +02:00
format : false , // false == date objects or use 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 , // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
2015-09-11 06:48:48 +02:00
} ,
} ;
2015-09-23 03:31:26 +02:00
var TimeScale = Chart . Scale . extend ( {
2015-10-19 00:00:46 +02:00
getLabelMoment : function ( datasetIndex , index ) {
return this . labelMoments [ datasetIndex ] [ index ] ;
} ,
2015-09-11 06:48:48 +02:00
2015-10-19 00:00:46 +02:00
buildLabelMoments : function ( ) {
// Only parse these once. If the dataset does not have data as x,y pairs, we will use
// these
var scaleLabelMoments = [ ] ;
2015-10-20 01:20:08 +02:00
if ( this . data . labels && this . data . labels . length > 0 ) {
helpers . each ( this . data . labels , function ( label , index ) {
var labelMoment = this . parseTime ( label ) ;
if ( this . options . time . round ) {
labelMoment . startOf ( this . options . time . round ) ;
}
scaleLabelMoments . push ( labelMoment ) ;
} , this ) ;
2015-10-19 00:00:46 +02:00
2015-10-20 05:23:01 +02:00
if ( this . options . time . min ) {
this . firstTick = this . parseTime ( this . options . time . min ) ;
} else {
this . firstTick = moment . min . call ( this , scaleLabelMoments ) ;
}
if ( this . options . time . max ) {
this . lastTick = this . parseTime ( this . options . time . max ) ;
} else {
this . lastTick = moment . max . call ( this , scaleLabelMoments ) ;
}
2015-10-20 01:20:08 +02:00
} else {
this . firstTick = null ;
this . lastTick = null ;
}
2015-10-19 00:00:46 +02:00
helpers . each ( this . data . datasets , function ( dataset , datasetIndex ) {
var momentsForDataset = [ ] ;
if ( typeof dataset . data [ 0 ] === 'object' ) {
helpers . each ( dataset . data , function ( value , index ) {
var labelMoment = this . parseTime ( this . getRightValue ( value ) ) ;
if ( this . options . time . round ) {
labelMoment . startOf ( this . options . time . round ) ;
}
momentsForDataset . push ( labelMoment ) ;
// May have gone outside the scale ranges, make sure we keep the first and last ticks updated
2015-10-20 01:20:08 +02:00
this . firstTick = this . firstTick !== null ? moment . min ( this . firstTick , labelMoment ) : labelMoment ;
this . lastTick = this . lastTick !== null ? moment . max ( this . lastTick , labelMoment ) : labelMoment ;
2015-10-19 00:00:46 +02:00
} , this ) ;
} else {
// We have no labels. Use the ones from the scale
momentsForDataset = scaleLabelMoments ;
}
this . labelMoments . push ( momentsForDataset ) ;
2015-09-11 06:48:48 +02:00
} , this ) ;
2015-10-19 00:00:46 +02:00
// We will modify these, so clone for later
this . firstTick = this . firstTick . clone ( ) ;
this . lastTick = this . lastTick . clone ( ) ;
} ,
buildTicks : function ( index ) {
this . ticks = [ ] ;
this . labelMoments = [ ] ;
this . buildLabelMoments ( ) ;
2015-09-11 06:48:48 +02:00
2015-09-15 19:40:01 +02:00
// Set unit override if applicable
2015-09-24 05:52:31 +02:00
if ( this . options . time . unit ) {
this . tickUnit = this . options . time . unit || 'day' ;
2015-09-27 15:38:11 +02:00
this . displayFormat = time . unit [ this . tickUnit ] . display ;
2015-09-15 19:40:01 +02:00
this . tickRange = Math . ceil ( this . lastTick . diff ( this . firstTick , this . tickUnit , true ) ) ;
} else {
2015-09-11 06:48:48 +02:00
// Determine the smallest needed unit of the time
2015-09-15 19:40:01 +02:00
var innerWidth = this . width - ( this . paddingLeft + this . paddingRight ) ;
2015-09-23 03:31:26 +02:00
var labelCapacity = innerWidth / this . options . ticks . fontSize + 4 ;
2015-09-24 05:52:31 +02:00
var buffer = this . options . time . round ? 0 : 2 ;
2015-09-15 19:40:01 +02:00
2015-09-29 18:54:42 +02:00
// Start as small as possible
this . tickUnit = 'millisecond' ;
this . tickRange = Math . ceil ( this . lastTick . diff ( this . firstTick , this . tickUnit , true ) + buffer ) ;
this . displayFormat = time . unit [ this . tickUnit ] . display ;
2015-09-15 19:40:01 +02:00
2015-09-29 18:54:42 +02:00
// Work our way up to comfort
2015-09-15 19:40:01 +02:00
helpers . each ( time . units , function ( format ) {
if ( this . tickRange <= labelCapacity ) {
2015-09-11 06:48:48 +02:00
return ;
}
2015-09-15 19:40:01 +02:00
this . tickUnit = format ;
this . tickRange = Math . ceil ( this . lastTick . diff ( this . firstTick , this . tickUnit ) + buffer ) ;
this . displayFormat = time . unit [ format ] . display ;
2015-09-11 06:48:48 +02:00
} , this ) ;
}
2015-09-15 19:40:01 +02:00
this . firstTick . startOf ( this . tickUnit ) ;
this . lastTick . endOf ( this . tickUnit ) ;
2015-09-18 19:31:25 +02:00
this . smallestLabelSeparation = this . width ;
2015-10-19 00:00:46 +02:00
helpers . each ( this . data . datasets , function ( dataset , datasetIndex ) {
for ( var i = 1 ; i < this . labelMoments [ datasetIndex ] . length ; i ++ ) {
this . smallestLabelSeparation = Math . min ( this . smallestLabelSeparation , this . labelMoments [ datasetIndex ] [ i ] . diff ( this . labelMoments [ datasetIndex ] [ i - 1 ] , this . tickUnit , true ) ) ;
}
} , this ) ;
2015-09-11 06:48:48 +02:00
2015-09-15 19:40:01 +02:00
// Tick displayFormat override
2015-09-24 05:52:31 +02:00
if ( this . options . time . displayFormat ) {
this . displayFormat = this . options . time . displayFormat ;
2015-09-11 06:48:48 +02:00
}
2015-09-23 03:31:26 +02:00
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
2015-10-19 00:00:46 +02:00
for ( var i = 0 ; i <= this . tickRange ; ++ i ) {
2015-09-27 15:38:11 +02:00
this . ticks . push ( this . firstTick . clone ( ) . add ( i , this . tickUnit ) ) ;
2015-09-11 06:48:48 +02:00
}
} ,
2015-10-18 22:13:57 +02:00
// Get tooltip label
getLabelForIndex : function ( index , datasetIndex ) {
2015-10-20 01:20:08 +02:00
var label = this . data . labels && index < this . data . labels . length ? this . data . labels [ index ] : '' ;
2015-10-19 00:00:46 +02:00
if ( typeof this . data . datasets [ datasetIndex ] . data [ 0 ] === 'object' ) {
label = this . getRightValue ( this . data . datasets [ datasetIndex ] . data [ index ] ) ;
}
return label ;
2015-10-18 22:13:57 +02:00
} ,
2015-09-27 15:38:11 +02:00
convertTicksToLabels : function ( ) {
this . ticks = this . ticks . map ( function ( tick , index , ticks ) {
var formattedTick = tick . format ( this . options . time . displayFormat ? this . options . time . displayFormat : time . unit [ this . tickUnit ] . display ) ;
if ( this . options . ticks . userCallback ) {
return this . options . ticks . userCallback ( formattedTick , index , ticks ) ;
} else {
return formattedTick ;
}
} , this ) ;
} ,
2015-09-24 20:07:40 +02:00
getPixelForValue : function ( value , index , datasetIndex , includeOffset ) {
2015-10-19 00:00:46 +02:00
var labelMoment = this . getLabelMoment ( datasetIndex , index ) ;
var offset = labelMoment . diff ( this . firstTick , this . tickUnit , true ) ;
2015-09-24 05:52:31 +02:00
2015-09-24 20:07:40 +02:00
var decimal = offset / this . tickRange ;
2015-09-24 05:52:31 +02:00
2015-09-11 06:48:48 +02:00
if ( this . isHorizontal ( ) ) {
var innerWidth = this . width - ( this . paddingLeft + this . paddingRight ) ;
2015-09-18 19:31:25 +02:00
var valueWidth = innerWidth / Math . max ( this . ticks . length - 1 , 1 ) ;
2015-09-15 19:40:01 +02:00
var valueOffset = ( innerWidth * decimal ) + this . paddingLeft ;
2015-09-11 06:48:48 +02:00
return this . left + Math . round ( valueOffset ) ;
} else {
2015-09-27 15:38:11 +02:00
//return this.top + (decimal * (this.height / this.ticks.length));
var innerHeight = this . height - ( this . paddingTop + this . paddingBottom ) ;
var valueHeight = innerHeight / Math . max ( this . ticks . length - 1 , 1 ) ;
var heightOffset = ( innerHeight * decimal ) + this . paddingTop ;
return this . top + Math . round ( heightOffset ) ;
2015-09-11 06:48:48 +02:00
}
} ,
2015-09-24 20:07:40 +02:00
parseTime : function ( label ) {
// Date objects
if ( typeof label . getMonth === 'function' || typeof label == 'number' ) {
return moment ( label ) ;
2015-09-11 06:48:48 +02:00
}
2015-09-24 20:07:40 +02:00
// Moment support
if ( label . isValid && label . isValid ( ) ) {
return label ;
2015-09-11 06:48:48 +02:00
}
2015-09-24 20:07:40 +02:00
// Custom parsing (return an instance of moment)
if ( typeof this . options . time . format !== 'string' && this . options . time . format . call ) {
return this . options . time . format ( label ) ;
2015-09-11 06:48:48 +02:00
}
2015-09-24 20:07:40 +02:00
// Moment format parsing
return moment ( label , this . options . time . format ) ;
2015-09-11 06:48:48 +02:00
} ,
} ) ;
Chart . scaleService . registerScaleType ( "time" , TimeScale , defaultConfig ) ;
} ) . call ( this ) ;