2016-02-12 07:16:43 +01:00
/*global window: false */
2016-02-12 04:30:53 +01:00
"use strict" ;
2016-02-14 02:12:26 +01:00
var moment = require ( 'moment' ) ;
moment = typeof ( moment ) === 'function' ? moment : window . moment ;
2016-02-12 05:29:26 +01:00
module . exports = function ( Chart ) {
2016-02-12 04:30:53 +01:00
2016-02-14 23:06:00 +01:00
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 : {
2016-03-02 13:53:35 +01:00
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/
2016-02-14 23:06:00 +01:00
unit : false , // false == automatic or override with week, month, year, etc.
round : false , // none, or override with week, month, year, etc.
displayFormat : false , // DEPRECATED
// 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
}
2016-02-15 15:41:35 +01:00
} ,
ticks : {
autoSkip : false ,
2016-02-14 23:06:00 +01:00
}
} ;
var TimeScale = Chart . Scale . extend ( {
2016-03-17 00:59:20 +01:00
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 ) ;
} ,
2016-02-14 23:06:00 +01:00
getLabelMoment : function ( datasetIndex , index ) {
return this . labelMoments [ datasetIndex ] [ index ] ;
} ,
determineDataLimits : function ( ) {
this . labelMoments = [ ] ;
// Only parse these once. If the dataset does not have data as x,y pairs, we will use
// these
var scaleLabelMoments = [ ] ;
if ( this . chart . data . labels && this . chart . data . labels . length > 0 ) {
helpers . each ( this . chart . 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 ) ;
this . firstTick = moment . min . call ( this , scaleLabelMoments ) ;
this . lastTick = moment . max . call ( this , scaleLabelMoments ) ;
} else {
this . firstTick = null ;
this . lastTick = null ;
}
helpers . each ( this . chart . 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
this . firstTick = this . firstTick !== null ? moment . min ( this . firstTick , labelMoment ) : labelMoment ;
this . lastTick = this . lastTick !== null ? moment . max ( this . lastTick , labelMoment ) : labelMoment ;
} , this ) ;
} else {
// We have no labels. Use the ones from the scale
momentsForDataset = scaleLabelMoments ;
}
this . labelMoments . push ( momentsForDataset ) ;
} , this ) ;
// Set these after we've done all the data
if ( this . options . time . min ) {
this . firstTick = this . parseTime ( this . options . time . min ) ;
}
if ( this . options . time . max ) {
this . lastTick = this . parseTime ( this . options . time . max ) ;
}
// We will modify these, so clone for later
this . firstTick = ( this . firstTick || moment ( ) ) . clone ( ) ;
this . lastTick = ( this . lastTick || moment ( ) ) . clone ( ) ;
} ,
buildTicks : function ( index ) {
this . ticks = [ ] ;
this . unitScale = 1 ; // How much we scale the unit by, ie 2 means 2x unit per step
2016-03-06 04:58:34 +01:00
this . scaleSizeInUnits = 0 ; // How large the scale is in the base unit (seconds, minutes, etc)
2016-02-14 23:06:00 +01:00
// Set unit override if applicable
if ( this . options . time . unit ) {
this . tickUnit = this . options . time . unit || 'day' ;
this . displayFormat = this . options . time . displayFormats [ this . tickUnit ] ;
2016-03-06 04:58:34 +01:00
this . scaleSizeInUnits = this . lastTick . diff ( this . firstTick , this . tickUnit , true ) ;
this . unitScale = helpers . getValueOrDefault ( this . options . time . unitStepSize , 1 ) ;
2016-02-14 23:06:00 +01:00
} else {
// Determine the smallest needed unit of the time
2016-02-28 19:41:17 +01:00
var tickFontSize = helpers . getValueOrDefault ( this . options . ticks . fontSize , Chart . defaults . global . defaultFontSize ) ;
2016-02-14 23:06:00 +01:00
var innerWidth = this . isHorizontal ( ) ? this . width - ( this . paddingLeft + this . paddingRight ) : this . height - ( this . paddingTop + this . paddingBottom ) ;
2016-03-06 04:58:34 +01:00
// Crude approximation of what the label length might be
var tempFirstLabel = this . tickFormatFunction ( this . firstTick , 0 , [ ] ) ;
var tickLabelWidth = tempFirstLabel . length * tickFontSize ;
var cosRotation = Math . cos ( helpers . toRadians ( this . options . ticks . maxRotation ) ) ;
var sinRotation = Math . sin ( helpers . toRadians ( this . options . ticks . maxRotation ) ) ;
tickLabelWidth = ( tickLabelWidth * cosRotation ) + ( tickFontSize * sinRotation ) ;
var labelCapacity = innerWidth / ( tickLabelWidth + 10 ) ;
2016-02-14 23:06:00 +01:00
// Start as small as possible
this . tickUnit = 'millisecond' ;
2016-03-06 04:58:34 +01:00
this . scaleSizeInUnits = this . lastTick . diff ( this . firstTick , this . tickUnit , true ) ;
2016-02-14 23:06:00 +01:00
this . displayFormat = this . options . time . displayFormats [ this . 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
this . unitScale = 1 ;
2016-03-06 04:58:34 +01:00
if ( helpers . isArray ( unitDefinition . steps ) && Math . ceil ( this . scaleSizeInUnits / labelCapacity ) < helpers . max ( unitDefinition . steps ) ) {
2016-02-14 23:06:00 +01:00
// Use one of the prefedined steps
for ( var idx = 0 ; idx < unitDefinition . steps . length ; ++ idx ) {
2016-03-06 04:58:34 +01:00
if ( unitDefinition . steps [ idx ] > Math . ceil ( this . scaleSizeInUnits / labelCapacity ) ) {
this . unitScale = helpers . getValueOrDefault ( this . options . time . unitStepSize , unitDefinition . steps [ idx ] ) ;
2016-02-14 23:06:00 +01:00
break ;
}
}
break ;
2016-03-06 04:58:34 +01:00
} else if ( ( unitDefinition . maxStep === false ) || ( Math . ceil ( this . scaleSizeInUnits / labelCapacity ) < unitDefinition . maxStep ) ) {
2016-02-14 23:06:00 +01:00
// We have a max step. Scale this unit
2016-03-06 04:58:34 +01:00
this . unitScale = helpers . getValueOrDefault ( this . options . time . unitStepSize , Math . ceil ( this . scaleSizeInUnits / labelCapacity ) ) ;
2016-02-14 23:06:00 +01:00
break ;
} else {
// Move to the next unit up
++ unitDefinitionIndex ;
unitDefinition = time . units [ unitDefinitionIndex ] ;
this . tickUnit = unitDefinition . name ;
2016-03-06 04:58:34 +01:00
this . scaleSizeInUnits = this . lastTick . diff ( this . firstTick , this . tickUnit , true ) ;
2016-02-14 23:06:00 +01:00
this . displayFormat = this . options . time . displayFormats [ unitDefinition . name ] ;
}
}
}
var roundedStart ;
// Only round the first tick if we have no hard minimum
if ( ! this . options . time . min ) {
this . firstTick . startOf ( this . tickUnit ) ;
roundedStart = this . firstTick ;
} else {
roundedStart = this . firstTick . clone ( ) . startOf ( this . tickUnit ) ;
}
// Only round the last tick if we have no hard maximum
if ( ! this . options . time . max ) {
this . lastTick . endOf ( this . tickUnit ) ;
}
this . smallestLabelSeparation = this . width ;
helpers . each ( this . chart . 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 ) ;
// Tick displayFormat override
if ( this . options . time . displayFormat ) {
this . displayFormat = this . options . time . displayFormat ;
}
// first tick. will have been rounded correctly if options.time.min is not specified
this . ticks . push ( this . firstTick . clone ( ) ) ;
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
2016-03-06 04:58:34 +01:00
for ( var i = 1 ; i < this . scaleSizeInUnits ; ++ i ) {
2016-02-14 23:06:00 +01:00
var newTick = roundedStart . clone ( ) . add ( i , this . tickUnit ) ;
// Are we greater than the max time
if ( this . options . time . max && newTick . diff ( this . lastTick , this . tickUnit , true ) >= 0 ) {
break ;
}
if ( i % this . unitScale === 0 ) {
this . ticks . push ( newTick ) ;
}
}
// Always show the right tick
2016-03-06 04:58:34 +01:00
if ( this . ticks [ this . ticks . length - 1 ] . diff ( this . lastTick , this . tickUnit ) !== 0 || this . 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 ( this . options . time . max ) {
this . ticks . push ( this . lastTick . clone ( ) ) ;
this . scaleSizeInUnits = this . lastTick . diff ( this . ticks [ 0 ] , this . tickUnit , true ) ;
} else {
this . scaleSizeInUnits = Math . ceil ( this . scaleSizeInUnits / this . unitScale ) * this . unitScale ;
this . ticks . push ( this . firstTick . clone ( ) . add ( this . scaleSizeInUnits , this . tickUnit ) ) ;
this . lastTick = this . ticks [ this . ticks . length - 1 ] . clone ( ) ;
}
2016-02-14 23:06:00 +01:00
}
} ,
// Get tooltip label
getLabelForIndex : function ( index , datasetIndex ) {
var label = this . chart . data . labels && index < this . chart . data . labels . length ? this . chart . data . labels [ index ] : '' ;
if ( typeof this . chart . data . datasets [ datasetIndex ] . data [ 0 ] === 'object' ) {
label = this . getRightValue ( this . chart . data . datasets [ datasetIndex ] . data [ index ] ) ;
}
// Format nicely
if ( this . options . time . tooltipFormat ) {
label = this . parseTime ( label ) . format ( this . options . time . tooltipFormat ) ;
}
return label ;
} ,
2016-03-06 04:58:34 +01:00
// Function to format an individual tick mark
tickFormatFunction : function tickFormatFunction ( tick , index , ticks ) {
var formattedTick = tick . format ( this . displayFormat ) ;
2016-02-14 23:06:00 +01:00
2016-03-06 04:58:34 +01:00
if ( this . options . ticks . userCallback ) {
return this . options . ticks . userCallback ( formattedTick , index , ticks ) ;
} else {
return formattedTick ;
}
} ,
convertTicksToLabels : function ( ) {
this . ticks = this . ticks . map ( this . tickFormatFunction , this ) ;
2016-02-14 23:06:00 +01:00
} ,
getPixelForValue : function ( value , index , datasetIndex , includeOffset ) {
var labelMoment = this . getLabelMoment ( datasetIndex , index ) ;
2016-02-27 14:58:27 +01:00
if ( labelMoment ) {
var offset = labelMoment . diff ( this . firstTick , this . tickUnit , true ) ;
2016-02-14 23:06:00 +01:00
2016-03-06 04:58:34 +01:00
var decimal = offset / this . scaleSizeInUnits ;
2016-02-14 23:06:00 +01:00
2016-02-27 14:58:27 +01:00
if ( this . isHorizontal ( ) ) {
var innerWidth = this . width - ( this . paddingLeft + this . paddingRight ) ;
var valueWidth = innerWidth / Math . max ( this . ticks . length - 1 , 1 ) ;
var valueOffset = ( innerWidth * decimal ) + this . paddingLeft ;
return this . left + Math . round ( valueOffset ) ;
} else {
var innerHeight = this . height - ( this . paddingTop + this . paddingBottom ) ;
var valueHeight = innerHeight / Math . max ( this . ticks . length - 1 , 1 ) ;
var heightOffset = ( innerHeight * decimal ) + this . paddingTop ;
2016-02-14 23:06:00 +01:00
2016-02-27 14:58:27 +01:00
return this . top + Math . round ( heightOffset ) ;
}
2016-02-14 23:06:00 +01:00
}
} ,
parseTime : function ( label ) {
2016-03-02 13:53:35 +01:00
if ( typeof this . options . time . parser === 'string' ) {
2016-03-02 14:16:52 +01:00
return moment ( label , this . options . time . parser ) ;
2016-03-02 13:53:35 +01:00
}
if ( typeof this . options . time . parser === 'function' ) {
return this . options . time . parser ( label ) ;
}
2016-02-14 23:06:00 +01:00
// 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 this . options . time . format !== 'string' && this . options . time . format . call ) {
2016-03-02 13:53:35 +01:00
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" ) ;
2016-02-14 23:06:00 +01:00
return this . options . time . format ( label ) ;
}
// Moment format parsing
return moment ( label , this . options . time . format ) ;
}
} ) ;
Chart . scaleService . registerScaleType ( "time" , TimeScale , defaultConfig ) ;
} ;