import d3tip from 'd3-tip' ; import { getContrastingColor } from '@superset-ui/core' ; import './d3tip.css' ; var d3 = typeof require === 'function' ? require ( 'd3' ) : window . d3 ; var d3 = typeof require === 'function' ? require ( 'd3' ) : window . d3 ; var CalHeatMap = function ( ) { 'use strict' ; var self = this ; self . tip = d3tip ( ) . attr ( 'class' , 'd3-tip' ) . direction ( 'n' ) . offset ( [ - 5 , 0 ] ) . html ( d => `
      ${ self . options . timeFormatter ( d . t ) }: <strong>${ self . options . valueFormatter ( d . v ) }</strong>
    ` ) ; self . legendTip = d3tip ( ) . attr ( 'class' , 'd3-tip' ) . direction ( 'n' ) . offset ( [ - 5 , 0 ] ) . html ( d => self . options . valueFormatter ( d ) ) ; this . allowedDataType = [ 'json' , 'csv' , 'tsv' , 'txt' ] ; this . options = { itemSelector : '#cal-heatmap' , paintOnLoad : true , range : 12 , cellSize : 10 , cellPadding : 2 , cellRadius : 0 , domainGutter : 2 , domainMargin : [ 0 , 0 , 0 , 0 ] , valueFormatter : d => d , timeFormatter : d => d , domain : 'hour' , subDomain : 'min' , colLimit : null , rowLimit : null , weekStartOnMonday : true , start : new Date ( ) , minDate : null , maxDate : null , data : '' , dataType : this . allowedDataType [ 0 ] , dataPostPayload : null , dataRequestHeaders : null , considerMissingDataAsZero : false , loadOnInit : true , verticalOrientation : false , domainDynamicDimension : true , label : { position : 'bottom' , align : 'center' , offset : { x : 0 , y : 0 , } , rotate : null , width : 100 , height : null , } , legend : [ 10 , 20 , 30 , 40 ] , displayLegend : true , legendCellSize : 10 , legendCellPadding : 2 , legendMargin : [ 0 , 0 , 0 , 0 ] , legendVerticalPosition : 'bottom' , legendHorizontalPosition : 'left' , legendOrientation : 'horizontal' , legendColors : null , highlight : [ ] , itemName : [ 'item' , 'items' ] , domainLabelFormat : null , subDomainTitleFormat : { empty : '{date}' , filled : '{count} {name} {connector} {date}' , } , subDomainDateFormat : null , subDomainTextFormat : null , legendTitleFormat : { lower : 'less than {min} {name}' , inner : 'between {down} and {up} {name}' , upper : 'more than {max} {name}' , } , animationDuration : 500 , nextSelector : false , previousSelector : false , itemNamespace : 'cal-heatmap' , tooltip : false , onClick : null , afterLoad : null , afterLoadNextDomain : null , afterLoadPreviousDomain : null , onComplete : null , afterLoadData : function ( data ) { return data ; } , afterUpdate : null , onMaxDomainReached : null , onMinDomainReached : null , } ; this . _domainType = { min : { name : 'minute' , level : 10 , maxItemNumber : 60 , defaultRowNumber : 10 , defaultColumnNumber : 6 , row : function ( d ) { return self . getSubDomainRowNumber ( d ) ; } , column : function ( d ) { return self . getSubDomainColumnNumber ( d ) ; } , position : { x : function ( d ) { return Math . floor ( d . getMinutes ( ) / self . _domainType . min . row ( d ) ) ; } , y : function ( d ) { return d . getMinutes ( ) % self . _domainType . min . row ( d ) ; } , } , format : { date : '%H:%M, %A %B %-e, %Y' , legend : '' , connector : 'at' , } , extractUnit : function ( d ) { return new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) , d . getHours ( ) , d . getMinutes ( ) ) . getTime ( ) ; } , } , hour : { name : 'hour' , level : 20 , maxItemNumber : function ( d ) { switch ( self . options . domain ) { case 'day' : return 24 ; case 'week' : return 24 * 7 ; case 'month' : return ( 24 * ( self . options . domainDynamicDimension ? self . getDayCountInMonth ( d ) : 31 ) ) ; } } , defaultRowNumber : 6 , defaultColumnNumber : function ( d ) { switch ( self . options . domain ) { case 'day' : return 4 ; case 'week' : return 28 ; case 'month' : return self . options . domainDynamicDimension ? self . getDayCountInMonth ( d ) : 31 ; } } , row : function ( d ) { return self . getSubDomainRowNumber ( d ) ; } , column : function ( d ) { return self . getSubDomainColumnNumber ( d ) ; } , position : { x : function ( d ) { if ( self . options . domain === 'month' ) { if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { return Math . floor ( ( d . getHours ( ) + ( d . getDate ( ) - 1 ) * 24 ) / self . _domainType . hour . row ( d ) ) ; } return ( Math . floor ( d . getHours ( ) / self . _domainType . hour . row ( d ) ) + ( d . getDate ( ) - 1 ) * 4 ) ; } else if ( self . options . domain === 'week' ) { if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { return Math . floor ( ( d . getHours ( ) + self . getWeekDay ( d ) * 24 ) / self . _domainType . hour . row ( d ) ) ; } return ( Math . floor ( d . getHours ( ) / self . _domainType . hour . row ( d ) ) + self . getWeekDay ( d ) * 4 ) ; } return Math . floor ( d . getHours ( ) / self . _domainType . hour . row ( d ) ) ; } , y : function ( d ) { var p = d . getHours ( ) ; if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { switch ( self . options . domain ) { case 'month' : p += ( d . getDate ( ) - 1 ) * 24 ; break ; case 'week' : p += self . getWeekDay ( d ) * 24 ; break ; } } return Math . floor ( p % self . _domainType . hour . row ( d ) ) ; } , } , format : { date : '%Hh, %A %B %-e, %Y' , legend : '%H:00' , connector : 'at' , } , extractUnit : function ( d ) { return new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) , d . getHours ( ) ) . getTime ( ) ; } , } , day : { name : 'day' , level : 30 , maxItemNumber : function ( d ) { switch ( self . options . domain ) { case 'week' : return 7 ; case 'month' : return self . options . domainDynamicDimension ? self . getDayCountInMonth ( d ) : 31 ; case 'year' : return self . options . domainDynamicDimension ? self . getDayCountInYear ( d ) : 366 ; } } , defaultColumnNumber : function ( d ) { d = new Date ( d ) ; switch ( self . options . domain ) { case 'week' : return 1 ; case 'month' : return self . options . domainDynamicDimension && ! self . options . verticalOrientation ? self . getWeekNumber ( new Date ( d . getFullYear ( ) , d . getMonth ( ) + 1 , 0 ) ) - self . getWeekNumber ( d ) + 1 : 6 ; case 'year' : return self . options . domainDynamicDimension ? self . getWeekNumber ( new Date ( d . getFullYear ( ) , 11 , 31 ) ) - self . getWeekNumber ( new Date ( d . getFullYear ( ) , 0 ) ) + 1 : 54 ; } } , defaultRowNumber : 7 , row : function ( d ) { return self . getSubDomainRowNumber ( d ) ; } , column : function ( d ) { return self . getSubDomainColumnNumber ( d ) ; } , position : { x : function ( d ) { switch ( self . options . domain ) { case 'week' : return Math . floor ( self . getWeekDay ( d ) / self . _domainType . day . row ( d ) ) ; case 'month' : if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { return Math . floor ( ( d . getDate ( ) - 1 ) / self . _domainType . day . row ( d ) ) ; } return ( self . getWeekNumber ( d ) - self . getWeekNumber ( new Date ( d . getFullYear ( ) , d . getMonth ( ) ) ) ) ; case 'year' : if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { return Math . floor ( ( self . getDayOfYear ( d ) - 1 ) / self . _domainType . day . row ( d ) ) ; } return self . getWeekNumber ( d ) ; } } , y : function ( d ) { var p = self . getWeekDay ( d ) ; if ( self . options . colLimit > 0 || self . options . rowLimit > 0 ) { switch ( self . options . domain ) { case 'year' : p = self . getDayOfYear ( d ) - 1 ; break ; case 'week' : p = self . getWeekDay ( d ) ; break ; case 'month' : p = d . getDate ( ) - 1 ; break ; } } return Math . floor ( p % self . _domainType . day . row ( d ) ) ; } , } , format : { date : '%A %B %-e, %Y' , legend : '%e %b' , connector : 'on' , } , extractUnit : function ( d ) { return new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) . getTime ( ) ; } , } , week : { name : 'week' , level : 40 , maxItemNumber : 54 , defaultColumnNumber : function ( d ) { d = new Date ( d ) ; switch ( self . options . domain ) { case 'year' : return self . _domainType . week . maxItemNumber ; case 'month' : return self . options . domainDynamicDimension ? self . getWeekNumber ( new Date ( d . getFullYear ( ) , d . getMonth ( ) + 1 , 0 ) ) - self . getWeekNumber ( d ) : 5 ; } } , defaultRowNumber : 1 , row : function ( d ) { return self . getSubDomainRowNumber ( d ) ; } , column : function ( d ) { return self . getSubDomainColumnNumber ( d ) ; } , position : { x : function ( d ) { switch ( self . options . domain ) { case 'year' : return Math . floor ( self . getWeekNumber ( d ) / self . _domainType . week . row ( d ) ) ; case 'month' : return Math . floor ( self . getMonthWeekNumber ( d ) / self . _domainType . week . row ( d ) ) ; } } , y : function ( d ) { return self . getWeekNumber ( d ) % self . _domainType . week . row ( d ) ; } , } , format : { date : '%B Week #%W' , legend : '%B Week #%W' , connector : 'in' , } , extractUnit : function ( d ) { var dt = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) ; var weekDay = dt . getDay ( ) - ( self . options . weekStartOnMonday ? 1 : 0 ) ; if ( weekDay < 0 ) { weekDay = 6 ; } dt . setDate ( dt . getDate ( ) - weekDay ) ; return dt . getTime ( ) ; } , } , month : { name : 'month' , level : 50 , maxItemNumber : 12 , defaultColumnNumber : 12 , defaultRowNumber : 1 , row : function ( ) { return self . getSubDomainRowNumber ( ) ; } , column : function ( ) { return self . getSubDomainColumnNumber ( ) ; } , position : { x : function ( d ) { return Math . floor ( d . getMonth ( ) / self . _domainType . month . row ( d ) ) ; } , y : function ( d ) { return d . getMonth ( ) % self . _domainType . month . row ( d ) ; } , } , format : { date : '%B %Y' , legend : '%B' , connector : 'in' , } , extractUnit : function ( d ) { return new Date ( d . getFullYear ( ) , d . getMonth ( ) ) . getTime ( ) ; } , } , year : { name : 'year' , level : 60 , row : function ( ) { return self . options . rowLimit || 1 ; } , column : function ( ) { return self . options . colLimit || 1 ; } , position : { x : function ( ) { return 1 ; } , y : function ( ) { return 1 ; } , } , format : { date : '%Y' , legend : '%Y' , connector : 'in' , } , extractUnit : function ( d ) { return new Date ( d . getFullYear ( ) ) . getTime ( ) ; } , } , } ; for ( var type in this . _domainType ) { if ( this . _domainType . hasOwnProperty ( type ) ) { var d = this . _domainType [ type ] ; this . _domainType [ 'x_' + type ] = { name : 'x_' + type , level : d . type , maxItemNumber : d . maxItemNumber , defaultRowNumber : d . defaultRowNumber , defaultColumnNumber : d . defaultColumnNumber , row : d . column , column : d . row , position : { x : d . position . y , y : d . position . x , } , format : d . format , extractUnit : d . extractUnit , } ; } } this . lastInsertedSvg = null ; this . _completed = false ; this . _domains = d3 . map ( ) ; this . graphDim = { width : 0 , height : 0 , } ; this . legendDim = { width : 0 , height : 0 , } ; this . NAVIGATE_LEFT = 1 ; this . NAVIGATE_RIGHT = 2 ; this . RESET_ALL_ON_UPDATE = 0 ; this . RESET_SINGLE_ON_UPDATE = 1 ; this . APPEND_ON_UPDATE = 2 ; this . DEFAULT_LEGEND_MARGIN = 10 ; this . root = null ; this . tooltip = null ; this . _maxDomainReached = false ; this . _minDomainReached = false ; this . domainPosition = new DomainPosition ( ) ; this . Legend = null ; this . legendScale = null ; this . DSTDomain = [ ] ; return this . _init = function ( ) { self . getDomain ( self . options . start ) . map ( function ( d ) { return d . getTime ( ) ; } ) . map ( function ( d ) { self . _domains . set ( d , self . getSubDomain ( d ) . map ( function ( d ) { return { t : self . _domainType [ self . options . subDomain ] . extractUnit ( d ) , v : null , } ; } ) ) ; } ) ; self . root = d3 . select ( self . options . itemSelector ) . append ( 'svg' ) . attr ( 'class' , 'cal-heatmap-container' ) ; self . root . attr ( 'x' , 0 ) . attr ( 'y' , 0 ) . append ( 'svg' ) . attr ( 'class' , 'graph' ) ; self . Legend = new Legend ( self ) ; if ( self . options . paintOnLoad ) { _initCalendar ( ) ; } self . root . call ( self . tip ) ; self . root . call ( self . legendTip ) ; return true ; } ; function _initCalendar ( ) { self . verticalDomainLabel = self . options . label . position === 'top' || self . options . label . position === 'bottom' ; self . domainVerticalLabelHeight = self . options . label . height === null ? Math . max ( 25 , self . options . cellSize * 2 ) : self . options . label . height ; self . domainHorizontalLabelWidth = 0 ; if ( self . options . domainLabelFormat === '' && self . options . label . height === null ) { self . domainVerticalLabelHeight = 0 ; } if ( ! self . verticalDomainLabel ) { self . domainVerticalLabelHeight = 0 ; self . domainHorizontalLabelWidth = self . options . label . width ; } self . paint ( ) ; if ( self . options . nextSelector !== false ) { d3 . select ( self . options . nextSelector ) . on ( 'click.' + self . options . itemNamespace , function ( ) { d3 . event . preventDefault ( ) ; return self . loadNextDomain ( 1 ) ; } ) ; } if ( self . options . previousSelector !== false ) { d3 . select ( self . options . previousSelector ) . on ( 'click.' + self . options . itemNamespace , function ( ) { d3 . event . preventDefault ( ) ; return self . loadPreviousDomain ( 1 ) ; } ) ; } self . Legend . redraw ( self . graphDim . width - self . options . domainGutter - self . options . cellPadding ) ; self . afterLoad ( ) ; var domains = self . getDomainKeys ( ) ; if ( self . options . loadOnInit ) { self . getDatas ( self . options . data , new Date ( domains [ 0 ] ) , self . getSubDomain ( domains [ domains . length - 1 ] ) . pop ( ) , function ( ) { self . fill ( ) ; self . onComplete ( ) ; } ) ; } else { self . onComplete ( ) ; } self . checkIfMinDomainIsReached ( domains [ 0 ] ) ; self . checkIfMaxDomainIsReached ( self . getNextDomain ( ) . getTime ( ) ) ; } function w ( d , outer ) { var width = self . options . cellSize * self . _domainType [ self . options . subDomain ] . column ( d ) + self . options . cellPadding * self . _domainType [ self . options . subDomain ] . column ( d ) ; if ( arguments . length === 2 && outer === true ) { return ( width += self . domainHorizontalLabelWidth + self . options . domainGutter + self . options . domainMargin [ 1 ] + self . options . domainMargin [ 3 ] ) ; } return width ; } function h ( d , outer ) { var height = self . options . cellSize * self . _domainType [ self . options . subDomain ] . row ( d ) + self . options . cellPadding * self . _domainType [ self . options . subDomain ] . row ( d ) ; if ( arguments . length === 2 && outer === true ) { height += self . options . domainGutter + self . domainVerticalLabelHeight + self . options . domainMargin [ 0 ] + self . options . domainMargin [ 2 ] ; } return height ; } param int this . paint = function ( navigationDir ) { var options = self . options ; if ( arguments . length === 0 ) { navigationDir = false ; } var domainSvg = self . root . select ( '.graph' ) . selectAll ( '.graph-domain' ) . data ( function ( ) { var data = self . getDomainKeys ( ) ; return navigationDir === self . NAVIGATE_LEFT ? data . reverse ( ) : data ; } , function ( d ) { return d ; } ) ; var enteringDomainDim = 0 ; var exitingDomainDim = 0 ; var svg = domainSvg . enter ( ) . append ( 'svg' ) . attr ( 'width' , function ( d ) { return w ( d , true ) ; } ) . attr ( 'height' , function ( d ) { return h ( d , true ) ; } ) . attr ( 'x' , function ( d ) { if ( options . verticalOrientation ) { self . graphDim . width = Math . max ( self . graphDim . width , w ( d , true ) ) ; return 0 ; } else { return getDomainPosition ( d , self . graphDim , 'width' , w ( d , true ) ) ; } } ) . attr ( 'y' , function ( d ) { if ( options . verticalOrientation ) { return getDomainPosition ( d , self . graphDim , 'height' , h ( d , true ) ) ; } else { self . graphDim . height = Math . max ( self . graphDim . height , h ( d , true ) ) ; return 0 ; } } ) . attr ( 'class' , function ( d ) { var classname = 'graph-domain' ; var date = new Date ( d ) ; switch ( options . domain ) { case 'hour' : classname += ' h_' + date . getHours ( ) ; case 'day' : classname += ' d_' + date . getDate ( ) + ' dy_' + date . getDay ( ) ; case 'week' : classname += ' w_' + self . getWeekNumber ( date ) ; case 'month' : classname += ' m_' + ( date . getMonth ( ) + 1 ) ; case 'year' : classname += ' y_' + date . getFullYear ( ) ; } return classname ; } ) ; self . lastInsertedSvg = svg ; function getDomainPosition ( domainIndex , graphDim , axis , domainDim ) { var tmp = 0 ; switch ( navigationDir ) { case false : tmp = graphDim [ axis ] ; graphDim [ axis ] += domainDim ; self . domainPosition . setPosition ( domainIndex , tmp ) ; return tmp ; case self . NAVIGATE_RIGHT : self . domainPosition . setPosition ( domainIndex , graphDim [ axis ] ) ; enteringDomainDim = domainDim ; exitingDomainDim = self . domainPosition . getPositionFromIndex ( 1 ) ; self . domainPosition . shiftRightBy ( exitingDomainDim ) ; return graphDim [ axis ] ; case self . NAVIGATE_LEFT : tmp = - domainDim ; enteringDomainDim = - tmp ; exitingDomainDim = graphDim [ axis ] - self . domainPosition . getLast ( ) ; self . domainPosition . setPosition ( domainIndex , tmp ) ; self . domainPosition . shiftLeftBy ( enteringDomainDim ) ; return tmp ; } } svg . append ( 'rect' ) . attr ( 'width' , function ( d ) { return w ( d , true ) - options . domainGutter - options . cellPadding ; } ) . attr ( 'height' , function ( d ) { return h ( d , true ) - options . domainGutter - options . cellPadding ; } ) . attr ( 'class' , 'domain-background' ) ; var subDomainSvgGroup = svg . append ( 'svg' ) . attr ( 'x' , function ( ) { if ( options . label . position === 'left' ) { return self . domainHorizontalLabelWidth + options . domainMargin [ 3 ] ; } else { return options . domainMargin [ 3 ] ; } } ) . attr ( 'y' , function ( ) { if ( options . label . position === 'top' ) { return self . domainVerticalLabelHeight + options . domainMargin [ 0 ] ; } else { return options . domainMargin [ 0 ] ; } } ) . attr ( 'class' , 'graph-subdomain-group' ) ; var rect = subDomainSvgGroup . selectAll ( 'g' ) . data ( function ( d ) { return self . _domains . get ( d ) ; } ) . enter ( ) . append ( 'g' ) ; rect . append ( 'rect' ) . attr ( 'class' , function ( d ) { return ( 'graph-rect' + self . getHighlightClassName ( d . t ) + ( options . onClick !== null ? ' hover_cursor' : '' ) ) ; } ) . attr ( 'width' , options . cellSize ) . attr ( 'height' , options . cellSize ) . attr ( 'x' , function ( d ) { return self . positionSubDomainX ( d . t ) ; } ) . attr ( 'y' , function ( d ) { return self . positionSubDomainY ( d . t ) ; } ) . on ( 'click' , function ( d ) { if ( options . onClick !== null ) { return self . onClick ( new Date ( d . t ) , d . v ) ; } } ) . call ( function ( selection ) { if ( options . cellRadius > 0 ) { selection . attr ( 'rx' , options . cellRadius ) . attr ( 'ry' , options . cellRadius ) ; } if ( self . legendScale !== null && options . legendColors !== null && options . legendColors . hasOwnProperty ( 'base' ) ) { selection . attr ( 'fill' , options . legendColors . base ) ; } if ( options . tooltip ) { selection . on ( 'mouseover' , function ( d ) { self . tip . show ( d , this ) ; } ) . on ( 'mouseout' , function ( ) { self . tip . hide ( d ) ; } ) ; } } ) ; if ( ! options . tooltip ) { rect . append ( 'title' ) . text ( function ( d ) { return self . formatDate ( new Date ( d . t ) , options . subDomainDateFormat ) ; } ) ; } if ( options . domainLabelFormat !== '' ) { svg . append ( 'text' ) . attr ( 'class' , 'graph-label' ) . attr ( 'y' , function ( d ) { var y = options . domainMargin [ 0 ] ; switch ( options . label . position ) { case 'top' : y += self . domainVerticalLabelHeight / 2 ; break ; case 'bottom' : y += h ( d ) + self . domainVerticalLabelHeight / 2 ; } return ( y + options . label . offset . y * ( ( options . label . rotate === 'right' && options . label . position === 'right' ) || ( options . label . rotate === 'left' && options . label . position === 'left' ) ? - 1 : 1 ) ) ; } ) . attr ( 'x' , function ( d ) { var x = options . domainMargin [ 3 ] ; switch ( options . label . position ) { case 'right' : x += w ( d ) ; break ; case 'bottom' : case 'top' : x += w ( d ) / 2 ; } if ( options . label . align === 'right' ) { return ( x + self . domainHorizontalLabelWidth - options . label . offset . x * ( options . label . rotate === 'right' ? - 1 : 1 ) ) ; } return x + options . label . offset . x ; } ) . attr ( 'text-anchor' , function ( ) { switch ( options . label . align ) { case 'start' : case 'left' : return 'start' ; case 'end' : case 'right' : return 'end' ; default : return 'middle' ; } } ) . attr ( 'dominant-baseline' , function ( ) { return self . verticalDomainLabel ? 'middle' : 'top' ; } ) . text ( function ( d ) { return self . formatDate ( new Date ( d ) , options . domainLabelFormat ) ; } ) . call ( domainRotate ) ; } function domainRotate ( selection ) { switch ( options . label . rotate ) { case 'right' : selection . attr ( 'transform' , function ( d ) { var s = 'rotate(90), ' ; switch ( options . label . position ) { case 'right' : s += 'translate(-' + w ( d ) + ' , -' + w ( d ) + ')' ; break ; case 'left' : s += 'translate(0, -' + self . domainHorizontalLabelWidth + ')' ; break ; } return s ; } ) ; break ; case 'left' : selection . attr ( 'transform' , function ( d ) { var s = 'rotate(270), ' ; switch ( options . label . position ) { case 'right' : s += 'translate(-' + ( w ( d ) + self . domainHorizontalLabelWidth ) + ' , ' + w ( d ) + ')' ; break ; case 'left' : s += 'translate(-' + self . domainHorizontalLabelWidth + ' , ' + self . domainHorizontalLabelWidth + ')' ; break ; } return s ; } ) ; break ; } } if ( options . subDomainTextFormat !== null ) { rect . append ( 'text' ) . attr ( 'class' , function ( d ) { return 'subdomain-text' + self . getHighlightClassName ( d . t ) ; } ) . attr ( 'x' , function ( d ) { return self . positionSubDomainX ( d . t ) + options . cellSize / 2 ; } ) . attr ( 'y' , function ( d ) { return self . positionSubDomainY ( d . t ) + options . cellSize / 2 ; } ) . attr ( 'text-anchor' , 'middle' ) . attr ( 'dominant-baseline' , 'central' ) . text ( function ( d ) { return self . formatDate ( new Date ( d . t ) , options . subDomainTextFormat ) ; } ) ; } if ( navigationDir !== false ) { domainSvg . transition ( ) . duration ( options . animationDuration ) . attr ( 'x' , function ( d ) { return options . verticalOrientation ? 0 : self . domainPosition . getPosition ( d ) ; } ) . attr ( 'y' , function ( d ) { return options . verticalOrientation ? self . domainPosition . getPosition ( d ) : 0 ; } ) ; } var tempWidth = self . graphDim . width ; var tempHeight = self . graphDim . height ; if ( options . verticalOrientation ) { self . graphDim . height += enteringDomainDim - exitingDomainDim ; } else { self . graphDim . width += enteringDomainDim - exitingDomainDim ; } domainSvg . exit ( ) . transition ( ) . duration ( options . animationDuration ) . attr ( 'x' , function ( d ) { if ( options . verticalOrientation ) { return 0 ; } else { switch ( navigationDir ) { case self . NAVIGATE_LEFT : return Math . min ( self . graphDim . width , tempWidth ) ; case self . NAVIGATE_RIGHT : return - w ( d , true ) ; } } } ) . attr ( 'y' , function ( d ) { if ( options . verticalOrientation ) { switch ( navigationDir ) { case self . NAVIGATE_LEFT : return Math . min ( self . graphDim . height , tempHeight ) ; case self . NAVIGATE_RIGHT : return - h ( d , true ) ; } } else { return 0 ; } } ) . remove ( ) ; self . resize ( ) ; } ; } ; CalHeatMap . prototype = { param { object } settings return { bool } init : function ( settings ) { 'use strict' ; var parent = this ; var options = ( parent . options = mergeRecursive ( parent . options , settings ) ) ; validateDomainType ( ) ; validateSelector ( options . itemSelector , false , 'itemSelector' ) ; if ( parent . allowedDataType . indexOf ( options . dataType ) === - 1 ) { throw new Error ( "The data type '" + options . dataType + "' is not valid data type" ) ; } if ( d3 . select ( options . itemSelector ) [ 0 ] [ 0 ] === null ) { throw new Error ( "The node '" + options . itemSelector + "' specified in itemSelector does not exists" ) ; } try { validateSelector ( options . nextSelector , true , 'nextSelector' ) ; validateSelector ( options . previousSelector , true , 'previousSelector' ) ; } catch ( error ) { console . log ( error . message ) ; return false ; } if ( ! settings . hasOwnProperty ( 'subDomain' ) ) { this . options . subDomain = getOptimalSubDomain ( settings . domain ) ; } if ( typeof options . itemNamespace !== 'string' || options . itemNamespace === '' ) { console . log ( 'itemNamespace can not be empty, falling back to cal-heatmap' ) ; options . itemNamespace = 'cal-heatmap' ; } var s = [ 'data' , 'onComplete' , 'onClick' , 'afterLoad' , 'afterLoadData' , 'afterLoadPreviousDomain' , 'afterLoadNextDomain' , 'afterUpdate' , ] ; for ( var k in s ) { if ( settings . hasOwnProperty ( s [ k ] ) ) { options [ s [ k ] ] = settings [ s [ k ] ] ; } } options . subDomainDateFormat = typeof options . subDomainDateFormat === 'string' || typeof options . subDomainDateFormat === 'function' ? options . subDomainDateFormat : this . _domainType [ options . subDomain ] . format . date ; options . domainLabelFormat = typeof options . domainLabelFormat === 'string' || typeof options . domainLabelFormat === 'function' ? options . domainLabelFormat : this . _domainType [ options . domain ] . format . legend ; options . subDomainTextFormat = ( typeof options . subDomainTextFormat === 'string' && options . subDomainTextFormat !== '' ) || typeof options . subDomainTextFormat === 'function' ? options . subDomainTextFormat : null ; options . domainMargin = expandMarginSetting ( options . domainMargin ) ; options . legendMargin = expandMarginSetting ( options . legendMargin ) ; options . highlight = parent . expandDateSetting ( options . highlight ) ; options . itemName = expandItemName ( options . itemName ) ; options . colLimit = parseColLimit ( options . colLimit ) ; options . rowLimit = parseRowLimit ( options . rowLimit ) ; if ( ! settings . hasOwnProperty ( 'legendMargin' ) ) { autoAddLegendMargin ( ) ; } autoAlignLabel ( ) ; param { Element | string | bool } selector param { bool } canBeFalse param { string } name throws return { bool } function validateSelector ( selector , canBeFalse , name ) { if ( ( ( canBeFalse && selector === false ) || selector instanceof Element || typeof selector === 'string' ) && selector !== '' ) { return true ; } throw new Error ( 'The ' + name + ' is not valid' ) ; } param { string } domain return { string } function getOptimalSubDomain ( domain ) { switch ( domain ) { case 'year' : return 'month' ; case 'month' : return 'day' ; case 'week' : return 'day' ; case 'day' : return 'hour' ; default : return 'min' ; } } throw return { bool } function validateDomainType ( ) { if ( ! parent . _domainType . hasOwnProperty ( options . domain ) || options . domain === 'min' || options . domain . substring ( 0 , 2 ) === 'x_' ) { throw new Error ( "The domain '" + options . domain + "' is not valid" ) ; } if ( ! parent . _domainType . hasOwnProperty ( options . subDomain ) || options . subDomain === 'year' ) { throw new Error ( "The subDomain '" + options . subDomain + "' is not valid" ) ; } if ( parent . _domainType [ options . domain ] . level <= parent . _domainType [ options . subDomain ] . level ) { throw new Error ( "'" + options . subDomain + "' is not a valid subDomain to '" + options . domain + "'" ) ; } return true ; } return function autoAlignLabel ( ) { if ( ! settings . hasOwnProperty ( 'label' ) || ( settings . hasOwnProperty ( 'label' ) && ! settings . label . hasOwnProperty ( 'align' ) ) ) { switch ( options . label . position ) { case 'left' : options . label . align = 'right' ; break ; case 'right' : options . label . align = 'left' ; break ; default : options . label . align = 'center' ; } if ( options . label . rotate === 'left' ) { options . label . align = 'right' ; } else if ( options . label . rotate === 'right' ) { options . label . align = 'left' ; } } if ( ! settings . hasOwnProperty ( 'label' ) || ( settings . hasOwnProperty ( 'label' ) && ! settings . label . hasOwnProperty ( 'offset' ) ) ) { if ( options . label . position === 'left' || options . label . position === 'right' ) { options . label . offset = { x : 10 , y : 15 , } ; } } } return function autoAddLegendMargin ( ) { switch ( options . legendVerticalPosition ) { case 'top' : options . legendMargin [ 2 ] = parent . DEFAULT_LEGEND_MARGIN ; break ; case 'bottom' : options . legendMargin [ 0 ] = parent . DEFAULT_LEGEND_MARGIN ; break ; case 'middle' : case 'center' : options . legendMargin [ options . legendHorizontalPosition === 'right' ? 3 : 1 ] = parent . DEFAULT_LEGEND_MARGIN ; } } param { integer | array } value return { array } function expandMarginSetting ( value ) { if ( typeof value === 'number' ) { value = [ value ] ; } if ( ! Array . isArray ( value ) ) { console . log ( 'Margin only takes an integer or an array of integers' ) ; value = [ 0 ] ; } switch ( value . length ) { case 1 : return [ value [ 0 ] , value [ 0 ] , value [ 0 ] , value [ 0 ] ] ; case 2 : return [ value [ 0 ] , value [ 1 ] , value [ 0 ] , value [ 1 ] ] ; case 3 : return [ value [ 0 ] , value [ 1 ] , value [ 2 ] , value [ 1 ] ] ; case 4 : return value ; default : return value . slice ( 0 , 4 ) ; } } param { string | array } value return { array } function expandItemName ( value ) { if ( typeof value === 'string' ) { return [ value , value + ( value !== '' ? 's' : '' ) ] ; } if ( Array . isArray ( value ) ) { if ( value . length === 1 ) { return [ value [ 0 ] , value [ 0 ] + 's' ] ; } else if ( value . length > 2 ) { return value . slice ( 0 , 2 ) ; } return value ; } return [ 'item' , 'items' ] ; } function parseColLimit ( value ) { return value > 0 ? value : null ; } function parseRowLimit ( value ) { if ( value > 0 && options . colLimit > 0 ) { console . log ( 'colLimit and rowLimit are mutually exclusive, rowLimit will be ignored' ) ; return null ; } return value > 0 ? value : null ; } return this . _init ( ) ; } , param { string | array | Date } value return { array } expandDateSetting : function ( value ) { 'use strict' ; if ( ! Array . isArray ( value ) ) { value = [ value ] ; } return value . map ( function ( data ) { if ( data === 'now' ) { return new Date ( ) ; } if ( data instanceof Date ) { return data ; } return false ; } ) . filter ( function ( d ) { return d !== false ; } ) ; } , param array return fill : function ( svg ) { 'use strict' ; var parent = this ; var options = parent . options ; if ( arguments . length === 0 ) { svg = parent . root . selectAll ( '.graph-domain' ) ; } var rect = svg . selectAll ( 'svg' ) . selectAll ( 'g' ) . data ( function ( d ) { return parent . _domains . get ( d ) ; } ) ; /**
         * Colorize the cell via a style attribute if enabled
         */ function addStyle ( element ) { if ( parent . legendScale === null ) { return false ; } element . attr ( 'fill' , function ( d ) { if ( d . v === null && options . hasOwnProperty ( 'considerMissingDataAsZero' ) && ! options . considerMissingDataAsZero ) { if ( options . legendColors . hasOwnProperty ( 'base' ) ) { return options . legendColors . base ; } } if ( options . legendColors !== null && options . legendColors . hasOwnProperty ( 'empty' ) && ( d . v === 0 || ( d . v === null && options . hasOwnProperty ( 'considerMissingDataAsZero' ) && options . considerMissingDataAsZero ) ) ) { return options . legendColors . empty ; } if ( d . v < 0 && options . legend [ 0 ] > 0 && options . legendColors !== null && options . legendColors . hasOwnProperty ( 'overflow' ) ) { return options . legendColors . overflow ; } return parent . legendScale ( Math . min ( d . v , options . legend [ options . legend . length - 1 ] ) ) ; } ) ; } rect . transition ( ) . duration ( options . animationDuration ) . select ( 'rect' ) . attr ( 'class' , function ( d ) { var htmlClass = parent . getHighlightClassName ( d . t ) . trim ( ) . split ( ' ' ) ; var pastDate = parent . dateIsLessThan ( d . t , new Date ( ) ) ; var sameDate = parent . dateIsEqual ( d . t , new Date ( ) ) ; if ( parent . legendScale === null || ( d . v === null && options . hasOwnProperty ( 'considerMissingDataAsZero' ) && ! options . considerMissingDataAsZero && ! options . legendColors . hasOwnProperty ( 'base' ) ) ) { htmlClass . push ( 'graph-rect' ) ; } if ( sameDate ) { htmlClass . push ( 'now' ) ; } else if ( ! pastDate ) { htmlClass . push ( 'future' ) ; } if ( d . v !== null ) { htmlClass . push ( parent . Legend . getClass ( d . v , parent . legendScale === null ) ) ; } else if ( options . considerMissingDataAsZero && pastDate ) { htmlClass . push ( parent . Legend . getClass ( 0 , parent . legendScale === null ) ) ; } if ( options . onClick !== null ) { htmlClass . push ( 'hover_cursor' ) ; } return htmlClass . join ( ' ' ) ; } ) . call ( addStyle ) ; rect . transition ( ) . duration ( options . animationDuration ) . select ( 'title' ) . text ( function ( d ) { return parent . getSubDomainTitle ( d ) ; } ) ; function formatSubDomainText ( element ) { if ( typeof options . subDomainTextFormat === 'function' ) { element . text ( function ( d ) { return options . subDomainTextFormat ( d . t , d . v ) ; } ) ; } } /**
         * Change the subDomainText class if necessary
         * Also change the text, e.g when text is representing the value
         * instead of the date
         */ rect . transition ( ) . duration ( options . animationDuration ) . select ( 'text' ) . attr ( 'class' , function ( d ) { return 'subdomain-text' + parent . getHighlightClassName ( d . t ) ; } ) . call ( formatSubDomainText ) . attr ( 'fill' , d => { if ( ! d . v ) return '#000' ; const rgb = parent . legendScale ( Math . min ( d . v , options . legend [ options . legend . length - 1 ] ) ) ; return getContrastingColor ( rgb , 135 ) ; } ) ; } , param string param object return formatStringWithObject : function ( formatted , args ) { 'use strict' ; for ( var prop in args ) { if ( args . hasOwnProperty ( prop ) ) { var regexp = new RegExp ( '\\{' + prop + '\\}' , 'gi' ) ; formatted = formatted . replace ( regexp , args [ prop ] ) ; } } return formatted ; } , param string param array param boolean return triggerEvent : function ( eventName , successArgs , skip ) { 'use strict' ; if ( ( arguments . length === 3 && skip ) || this . options [ eventName ] === null ) { return true ; } if ( typeof this . options [ eventName ] === 'function' ) { if ( typeof successArgs === 'function' ) { successArgs = successArgs ( ) ; } return this . options [ eventName ] . apply ( this , successArgs ) ; } else { console . log ( 'Provided callback for ' + eventName + ' is not a function.' ) ; return false ; } } , param Date param int onClick : function ( d , itemNb ) { 'use strict' ; return this . triggerEvent ( 'onClick' , [ d , itemNb ] ) ; } , /**
     * Event triggered after drawing the calendar, byt before filling it with data
     */ afterLoad : function ( ) { 'use strict' ; return this . triggerEvent ( 'afterLoad' ) ; } , /**
     * Event triggered after completing drawing and filling the calendar
     */ onComplete : function ( ) { 'use strict' ; var response = this . triggerEvent ( 'onComplete' , [ ] , this . _completed ) ; this . _completed = true ; return response ; } , param Date param Date afterLoadPreviousDomain : function ( start ) { 'use strict' ; var parent = this ; return this . triggerEvent ( 'afterLoadPreviousDomain' , function ( ) { var subDomain = parent . getSubDomain ( start ) ; return [ subDomain . shift ( ) , subDomain . pop ( ) ] ; } ) ; } , param Date param Date afterLoadNextDomain : function ( start ) { 'use strict' ; var parent = this ; return this . triggerEvent ( 'afterLoadNextDomain' , function ( ) { var subDomain = parent . getSubDomain ( start ) ; return [ subDomain . shift ( ) , subDomain . pop ( ) ] ; } ) ; } , param boolean onMinDomainReached : function ( reached ) { 'use strict' ; this . _minDomainReached = reached ; return this . triggerEvent ( 'onMinDomainReached' , [ reached ] ) ; } , param boolean onMaxDomainReached : function ( reached ) { 'use strict' ; this . _maxDomainReached = reached ; return this . triggerEvent ( 'onMaxDomainReached' , [ reached ] ) ; } , checkIfMinDomainIsReached : function ( date , upperBound ) { 'use strict' ; if ( this . minDomainIsReached ( date ) ) { this . onMinDomainReached ( true ) ; } if ( arguments . length === 2 ) { if ( this . _maxDomainReached && ! this . maxDomainIsReached ( upperBound ) ) { this . onMaxDomainReached ( false ) ; } } } , checkIfMaxDomainIsReached : function ( date , lowerBound ) { 'use strict' ; if ( this . maxDomainIsReached ( date ) ) { this . onMaxDomainReached ( true ) ; } if ( arguments . length === 2 ) { if ( this . _minDomainReached && ! this . minDomainIsReached ( lowerBound ) ) { this . onMinDomainReached ( false ) ; } } } , afterUpdate : function ( ) { 'use strict' ; return this . triggerEvent ( 'afterUpdate' ) ; } , formatNumber : d3 . format ( ',g' ) , formatDate : function ( d , format ) { 'use strict' ; if ( arguments . length < 2 ) { format = 'title' ; } if ( typeof format === 'function' ) { return format ( d ) ; } else { var f = d3 . time . format ( format ) ; return f ( d ) ; } } , getSubDomainTitle : function ( d ) { 'use strict' ; if ( d . v === null && ! this . options . considerMissingDataAsZero ) { return this . formatStringWithObject ( this . options . subDomainTitleFormat . empty , { date : this . formatDate ( new Date ( d . t ) , this . options . subDomainDateFormat ) , } ) ; } else { var value = d . v ; if ( value === null && this . options . considerMissingDataAsZero ) { value = 0 ; } return this . formatStringWithObject ( this . options . subDomainTitleFormat . filled , { count : this . formatNumber ( value ) , name : this . options . itemName [ value !== 1 ? 1 : 0 ] , connector : this . _domainType [ this . options . subDomain ] . format . connector , date : this . formatDate ( new Date ( d . t ) , this . options . subDomainDateFormat ) , } ) ; } } , param int return loadNextDomain : function ( n ) { 'use strict' ; if ( this . _maxDomainReached || n === 0 ) { return false ; } var bound = this . loadNewDomains ( this . NAVIGATE_RIGHT , this . getDomain ( this . getNextDomain ( ) , n ) ) ; this . afterLoadNextDomain ( bound . end ) ; this . checkIfMaxDomainIsReached ( this . getNextDomain ( ) . getTime ( ) , bound . start ) ; return true ; } , param int return loadPreviousDomain : function ( n ) { 'use strict' ; if ( this . _minDomainReached || n === 0 ) { return false ; } var bound = this . loadNewDomains ( this . NAVIGATE_LEFT , this . getDomain ( this . getDomainKeys ( ) [ 0 ] , - n ) . reverse ( ) ) ; this . afterLoadPreviousDomain ( bound . start ) ; this . checkIfMinDomainIsReached ( bound . start , bound . end ) ; return true ; } , loadNewDomains : function ( direction , newDomains ) { 'use strict' ; var parent = this ; var backward = direction === this . NAVIGATE_LEFT ; var i = - 1 ; var total = newDomains . length ; var domains = this . getDomainKeys ( ) ; function buildSubDomain ( d ) { return { t : parent . _domainType [ parent . options . subDomain ] . extractUnit ( d ) , v : null , } ; } while ( ++ i < total ) { if ( backward && this . minDomainIsReached ( newDomains [ i ] ) ) { newDomains = newDomains . slice ( 0 , i + 1 ) ; break ; } if ( ! backward && this . maxDomainIsReached ( newDomains [ i ] ) ) { newDomains = newDomains . slice ( 0 , i ) ; break ; } } newDomains = newDomains . slice ( - this . options . range ) ; for ( i = 0 , total = newDomains . length ; i < total ; i += 1 ) { this . _domains . set ( newDomains [ i ] . getTime ( ) , this . getSubDomain ( newDomains [ i ] ) . map ( buildSubDomain ) ) ; this . _domains . remove ( backward ? domains . pop ( ) : domains . shift ( ) ) ; } domains = this . getDomainKeys ( ) ; if ( backward ) { newDomains = newDomains . reverse ( ) ; } this . paint ( direction ) ; this . getDatas ( this . options . data , newDomains [ 0 ] , this . getSubDomain ( newDomains [ newDomains . length - 1 ] ) . pop ( ) , function ( ) { parent . fill ( parent . lastInsertedSvg ) ; } ) ; return { start : newDomains [ backward ? 0 : 1 ] , end : domains [ domains . length - 1 ] , } ; } , param int return maxDomainIsReached : function ( datetimestamp ) { 'use strict' ; return ( this . options . maxDate !== null && this . options . maxDate . getTime ( ) < datetimestamp ) ; } , param int return minDomainIsReached : function ( datetimestamp ) { 'use strict' ; return ( this . options . minDate !== null && this . options . minDate . getTime ( ) >= datetimestamp ) ; } , return getDomainKeys : function ( ) { 'use strict' ; return this . _domains . keys ( ) . map ( function ( d ) { return parseInt ( d , 10 ) ; } ) . sort ( function ( a , b ) { return a - b ; } ) ; } , positionSubDomainX : function ( d ) { 'use strict' ; var index = this . _domainType [ this . options . subDomain ] . position . x ( new Date ( d ) ) ; return index * this . options . cellSize + index * this . options . cellPadding ; } , positionSubDomainY : function ( d ) { 'use strict' ; var index = this . _domainType [ this . options . subDomain ] . position . y ( new Date ( d ) ) ; return index * this . options . cellSize + index * this . options . cellPadding ; } , getSubDomainColumnNumber : function ( d ) { 'use strict' ; if ( this . options . rowLimit > 0 ) { var i = this . _domainType [ this . options . subDomain ] . maxItemNumber ; if ( typeof i === 'function' ) { i = i ( d ) ; } return Math . ceil ( i / this . options . rowLimit ) ; } var j = this . _domainType [ this . options . subDomain ] . defaultColumnNumber ; if ( typeof j === 'function' ) { j = j ( d ) ; } return this . options . colLimit || j ; } , getSubDomainRowNumber : function ( d ) { 'use strict' ; if ( this . options . colLimit > 0 ) { var i = this . _domainType [ this . options . subDomain ] . maxItemNumber ; if ( typeof i === 'function' ) { i = i ( d ) ; } return Math . ceil ( i / this . options . colLimit ) ; } var j = this . _domainType [ this . options . subDomain ] . defaultRowNumber ; if ( typeof j === 'function' ) { j = j ( d ) ; } return this . options . rowLimit || j ; } , param timestamp return getHighlightClassName : function ( d ) { 'use strict' ; d = new Date ( d ) ; if ( this . options . highlight . length > 0 ) { for ( var i in this . options . highlight ) { if ( this . dateIsEqual ( this . options . highlight [ i ] , d ) ) { return this . isNow ( this . options . highlight [ i ] ) ? ' highlight-now' : ' highlight' ; } } } return '' ; } , param Date return isNow : function ( d ) { 'use strict' ; return this . dateIsEqual ( d , new Date ( ) ) ; } , param Date param Date return dateIsEqual : function ( dateA , dateB ) { 'use strict' ; if ( ! ( dateA instanceof Date ) ) { dateA = new Date ( dateA ) ; } if ( ! ( dateB instanceof Date ) ) { dateB = new Date ( dateB ) ; } switch ( this . options . subDomain ) { case 'x_min' : case 'min' : return ( dateA . getFullYear ( ) === dateB . getFullYear ( ) && dateA . getMonth ( ) === dateB . getMonth ( ) && dateA . getDate ( ) === dateB . getDate ( ) && dateA . getHours ( ) === dateB . getHours ( ) && dateA . getMinutes ( ) === dateB . getMinutes ( ) ) ; case 'x_hour' : case 'hour' : return ( dateA . getFullYear ( ) === dateB . getFullYear ( ) && dateA . getMonth ( ) === dateB . getMonth ( ) && dateA . getDate ( ) === dateB . getDate ( ) && dateA . getHours ( ) === dateB . getHours ( ) ) ; case 'x_day' : case 'day' : return ( dateA . getFullYear ( ) === dateB . getFullYear ( ) && dateA . getMonth ( ) === dateB . getMonth ( ) && dateA . getDate ( ) === dateB . getDate ( ) ) ; case 'x_week' : case 'week' : return ( dateA . getFullYear ( ) === dateB . getFullYear ( ) && this . getWeekNumber ( dateA ) === this . getWeekNumber ( dateB ) ) ; case 'x_month' : case 'month' : return ( dateA . getFullYear ( ) === dateB . getFullYear ( ) && dateA . getMonth ( ) === dateB . getMonth ( ) ) ; default : return false ; } } , param dateA param dateB returns { boolean } dateIsLessThan : function ( dateA , dateB ) { 'use strict' ; if ( ! ( dateA instanceof Date ) ) { dateA = new Date ( dateA ) ; } if ( ! ( dateB instanceof Date ) ) { dateB = new Date ( dateB ) ; } function normalizedMillis ( date , subdomain ) { switch ( subdomain ) { case 'x_min' : case 'min' : return new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) , date . getHours ( ) , date . getMinutes ( ) ) . getTime ( ) ; case 'x_hour' : case 'hour' : return new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) , date . getHours ( ) ) . getTime ( ) ; case 'x_day' : case 'day' : return new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) ) . getTime ( ) ; case 'x_week' : case 'week' : case 'x_month' : case 'month' : return new Date ( date . getFullYear ( ) , date . getMonth ( ) ) . getTime ( ) ; default : return date . getTime ( ) ; } } return ( normalizedMillis ( dateA , this . options . subDomain ) < normalizedMillis ( dateB , this . options . subDomain ) ) ; } , param Date return getDayOfYear : d3 . time . format ( '%j' ) , return getWeekNumber : function ( d ) { 'use strict' ; var f = this . options . weekStartOnMonday === true ? d3 . time . format ( '%W' ) : d3 . time . format ( '%U' ) ; return f ( d ) ; } , param int return getMonthWeekNumber : function ( d ) { 'use strict' ; if ( typeof d === 'number' ) { d = new Date ( d ) ; } var monthFirstWeekNumber = this . getWeekNumber ( new Date ( d . getFullYear ( ) , d . getMonth ( ) ) ) ; return this . getWeekNumber ( d ) - monthFirstWeekNumber - 1 ; } , param int return getWeekNumberInYear : function ( d ) { 'use strict' ; if ( typeof d === 'number' ) { d = new Date ( d ) ; } } , param int return getDayCountInMonth : function ( d ) { 'use strict' ; return this . getEndOfMonth ( d ) . getDate ( ) ; } , param int return getDayCountInYear : function ( d ) { 'use strict' ; if ( typeof d === 'number' ) { d = new Date ( d ) ; } return new Date ( d . getFullYear ( ) , 1 , 29 ) . getMonth ( ) === 1 ? 366 : 365 ; } , param Date return getWeekDay : function ( d ) { 'use strict' ; if ( this . options . weekStartOnMonday === false ) { return d . getDay ( ) ; } return d . getDay ( ) === 0 ? 6 : d . getDay ( ) - 1 ; } , param Date return getEndOfMonth : function ( d ) { 'use strict' ; if ( typeof d === 'number' ) { d = new Date ( d ) ; } return new Date ( d . getFullYear ( ) , d . getMonth ( ) + 1 , 0 ) ; } , param Date param int param string return jumpDate : function ( date , count , step ) { 'use strict' ; var d = new Date ( date ) ; switch ( step ) { case 'hour' : d . setHours ( d . getHours ( ) + count ) ; break ; case 'day' : d . setHours ( d . getHours ( ) + count * 24 ) ; break ; case 'week' : d . setHours ( d . getHours ( ) + count * 24 * 7 ) ; break ; case 'month' : d . setMonth ( d . getMonth ( ) + count ) ; break ; case 'year' : d . setFullYear ( d . getFullYear ( ) + count ) ; } return new Date ( d ) ; } , param Date param int return getMinuteDomain : function ( d , range ) { 'use strict' ; var start = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) , d . getHours ( ) ) ; var stop = null ; if ( range instanceof Date ) { stop = new Date ( range . getFullYear ( ) , range . getMonth ( ) , range . getDate ( ) , range . getHours ( ) ) ; } else { stop = new Date ( + start + range * 1000 * 60 ) ; } return d3 . time . minutes ( Math . min ( start , stop ) , Math . max ( start , stop ) ) ; } , param Date param int return getHourDomain : function ( d , range ) { 'use strict' ; var start = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) , d . getHours ( ) ) ; var stop = null ; if ( range instanceof Date ) { stop = new Date ( range . getFullYear ( ) , range . getMonth ( ) , range . getDate ( ) , range . getHours ( ) ) ; } else { stop = new Date ( start ) ; stop . setHours ( stop . getHours ( ) + range ) ; } var domains = d3 . time . hours ( Math . min ( start , stop ) , Math . max ( start , stop ) ) ; var i = 0 ; var total = domains . length ; for ( i = 0 ; i < total ; i += 1 ) { if ( i > 0 && domains [ i ] . getHours ( ) === domains [ i - 1 ] . getHours ( ) ) { this . DSTDomain . push ( domains [ i ] . getTime ( ) ) ; domains . splice ( i , 1 ) ; break ; } } if ( typeof range === 'number' && domains . length > Math . abs ( range ) ) { domains . splice ( domains . length - 1 , 1 ) ; } return domains ; } , param Date param int return getDayDomain : function ( d , range ) { 'use strict' ; var start = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) ; var stop = null ; if ( range instanceof Date ) { stop = new Date ( range . getFullYear ( ) , range . getMonth ( ) , range . getDate ( ) ) ; } else { stop = new Date ( start ) ; stop = new Date ( stop . setDate ( stop . getDate ( ) + parseInt ( range , 10 ) ) ) ; } return d3 . time . days ( Math . min ( start , stop ) , Math . max ( start , stop ) ) ; } , param Date param int return getWeekDomain : function ( d , range ) { 'use strict' ; var weekStart ; if ( this . options . weekStartOnMonday === false ) { weekStart = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) - d . getDay ( ) ) ; } else { if ( d . getDay ( ) === 1 ) { weekStart = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) ; } else if ( d . getDay ( ) === 0 ) { weekStart = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ) ; weekStart . setDate ( weekStart . getDate ( ) - 6 ) ; } else { weekStart = new Date ( d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) - d . getDay ( ) + 1 ) ; } } var endDate = new Date ( weekStart ) ; var stop = range ; if ( typeof range !== 'object' ) { stop = new Date ( endDate . setDate ( endDate . getDate ( ) + range * 7 ) ) ; } return this . options . weekStartOnMonday === true ? d3 . time . mondays ( Math . min ( weekStart , stop ) , Math . max ( weekStart , stop ) ) : d3 . time . sundays ( Math . min ( weekStart , stop ) , Math . max ( weekStart , stop ) ) ; } , param Date param int return getMonthDomain : function ( d , range ) { 'use strict' ; var start = new Date ( d . getFullYear ( ) , d . getMonth ( ) ) ; var stop = null ; if ( range instanceof Date ) { stop = new Date ( range . getFullYear ( ) , range . getMonth ( ) ) ; } else { stop = new Date ( start ) ; stop = stop . setMonth ( stop . getMonth ( ) + range ) ; } return d3 . time . months ( Math . min ( start , stop ) , Math . max ( start , stop ) ) ; } , param Date param int return getYearDomain : function ( d , range ) { 'use strict' ; var start = new Date ( d . getFullYear ( ) , 0 ) ; var stop = null ; if ( range instanceof Date ) { stop = new Date ( range . getFullYear ( ) , 0 ) ; } else { stop = new Date ( d . getFullYear ( ) + range , 0 ) ; } return d3 . time . years ( Math . min ( start , stop ) , Math . max ( start , stop ) ) ; } , param int param int return getDomain : function ( date , range ) { 'use strict' ; if ( typeof date === 'number' ) { date = new Date ( date ) ; } if ( arguments . length < 2 ) { range = this . options . range ; } switch ( this . options . domain ) { case 'hour' : var domains = this . getHourDomain ( date , range ) ; if ( typeof range === 'number' && domains . length < range ) { if ( range > 0 ) { domains . push ( this . getHourDomain ( domains [ domains . length - 1 ] , 2 ) [ 1 ] ) ; } else { domains . shift ( this . getHourDomain ( domains [ 0 ] , - 2 ) [ 0 ] ) ; } } return domains ; case 'day' : return this . getDayDomain ( date , range ) ; case 'week' : return this . getWeekDomain ( date , range ) ; case 'month' : return this . getMonthDomain ( date , range ) ; case 'year' : return this . getYearDomain ( date , range ) ; } } , getSubDomain : function ( date ) { 'use strict' ; if ( typeof date === 'number' ) { date = new Date ( date ) ; } var parent = this ; return var computeDaySubDomainSize = function ( date , domain ) { switch ( domain ) { case 'year' : return parent . getDayCountInYear ( date ) ; case 'month' : return parent . getDayCountInMonth ( date ) ; case 'week' : return 7 ; } } ; return var computeMinSubDomainSize = function ( date , domain ) { switch ( domain ) { case 'hour' : return 60 ; case 'day' : return 60 * 24 ; case 'week' : return 60 * 24 * 7 ; } } ; return var computeHourSubDomainSize = function ( date , domain ) { switch ( domain ) { case 'day' : return 24 ; case 'week' : return 168 ; case 'month' : return parent . getDayCountInMonth ( date ) * 24 ; } } ; return var computeWeekSubDomainSize = function ( date , domain ) { if ( domain === 'month' ) { var endOfMonth = new Date ( date . getFullYear ( ) , date . getMonth ( ) + 1 , 0 ) ; var endWeekNb = parent . getWeekNumber ( endOfMonth ) ; var startWeekNb = parent . getWeekNumber ( new Date ( date . getFullYear ( ) , date . getMonth ( ) ) ) ; if ( startWeekNb > endWeekNb ) { startWeekNb = 0 ; endWeekNb += 1 ; } return endWeekNb - startWeekNb + 1 ; } else if ( domain === 'year' ) { return parent . getWeekNumber ( new Date ( date . getFullYear ( ) , 11 , 31 ) ) ; } } ; switch ( this . options . subDomain ) { case 'x_min' : case 'min' : return this . getMinuteDomain ( date , computeMinSubDomainSize ( date , this . options . domain ) ) ; case 'x_hour' : case 'hour' : return this . getHourDomain ( date , computeHourSubDomainSize ( date , this . options . domain ) ) ; case 'x_day' : case 'day' : return this . getDayDomain ( date , computeDaySubDomainSize ( date , this . options . domain ) ) ; case 'x_week' : case 'week' : return this . getWeekDomain ( date , computeWeekSubDomainSize ( date , this . options . domain ) ) ; case 'x_month' : case 'month' : return this . getMonthDomain ( date , 12 ) ; } } , param int return getNextDomain : function ( n ) { 'use strict' ; if ( arguments . length === 0 ) { n = 1 ; } return this . getDomain ( this . jumpDate ( this . getDomainKeys ( ) . pop ( ) , n , this . options . domain ) , 1 ) [ 0 ] ; } , param int return getPreviousDomain : function ( n ) { 'use strict' ; if ( arguments . length === 0 ) { n = 1 ; } return this . getDomain ( this . jumpDate ( this . getDomainKeys ( ) . shift ( ) , - n , this . options . domain ) , 1 ) [ 0 ] ; } , param string param Date param Date param function param function param updateMode return getDatas : function ( source , startDate , endDate , callback , afterLoad , updateMode ) { 'use strict' ; var self = this ; if ( arguments . length < 5 ) { afterLoad = true ; } if ( arguments . length < 6 ) { updateMode = this . APPEND_ON_UPDATE ; } var _callback = function ( error , data ) { if ( afterLoad !== false ) { if ( typeof afterLoad === 'function' ) { data = afterLoad ( data ) ; } else if ( typeof self . options . afterLoadData === 'function' ) { data = self . options . afterLoadData ( data ) ; } else { console . log ( 'Provided callback for afterLoadData is not a function.' ) ; } } else if ( self . options . dataType === 'csv' || self . options . dataType === 'tsv' ) { data = this . interpretCSV ( data ) ; } self . parseDatas ( data , updateMode , startDate , endDate ) ; if ( typeof callback === 'function' ) { callback ( ) ; } } ; switch ( typeof source ) { case 'string' : if ( source === '' ) { _callback ( null , { } ) ; return true ; } else { var url = this . parseURI ( source , startDate , endDate ) ; var requestType = 'GET' ; if ( self . options . dataPostPayload !== null ) { requestType = 'POST' ; } var payload = null ; if ( self . options . dataPostPayload !== null ) { payload = this . parseURI ( self . options . dataPostPayload , startDate , endDate ) ; } var xhr = null ; switch ( this . options . dataType ) { case 'json' : xhr = d3 . json ( url ) ; break ; case 'csv' : xhr = d3 . csv ( url ) ; break ; case 'tsv' : xhr = d3 . tsv ( url ) ; break ; case 'txt' : xhr = d3 . text ( url , 'text/plain' ) ; break ; } if ( self . options . dataRequestHeaders !== null ) { for ( var header in self . options . dataRequestHeaders ) { if ( self . options . dataRequestHeaders . hasOwnProperty ( header ) ) { xhr . header ( header , self . options . dataRequestHeaders [ header ] ) ; } } } xhr . send ( requestType , payload , _callback ) ; } return false ; case 'object' : if ( source === Object ( source ) ) { _callback ( null , source ) ; return false ; } default : _callback ( null , { } ) ; return true ; } } , param object param constant param Date param Date return parseDatas : function ( data , updateMode , startDate , endDate ) { 'use strict' ; if ( updateMode === this . RESET_ALL_ON_UPDATE ) { this . _domains . forEach ( function ( key , value ) { value . forEach ( function ( element , index , array ) { array [ index ] . v = null ; } ) ; } ) ; } var temp = { } ; var extractTime = function ( d ) { return d . t ; } ; for ( var d in data ) { var date = new Date ( d * 1000 ) ; var domainUnit = this . getDomain ( date ) [ 0 ] . getTime ( ) ; if ( this . DSTDomain . indexOf ( domainUnit ) >= 0 ) { if ( this . _domains . has ( domainUnit - 3600 * 1000 ) ) { domainUnit -= 3600 * 1000 ; } } if ( isNaN ( d ) || ! data . hasOwnProperty ( d ) || ! this . _domains . has ( domainUnit ) || ! ( domainUnit >= + startDate && domainUnit < + endDate ) ) { continue ; } var subDomainsData = this . _domains . get ( domainUnit ) ; if ( ! temp . hasOwnProperty ( domainUnit ) ) { temp [ domainUnit ] = subDomainsData . map ( extractTime ) ; } var index = temp [ domainUnit ] . indexOf ( this . _domainType [ this . options . subDomain ] . extractUnit ( date ) ) ; if ( updateMode === this . RESET_SINGLE_ON_UPDATE ) { subDomainsData [ index ] . v = data [ d ] ; } else { if ( ! isNaN ( subDomainsData [ index ] . v ) ) { subDomainsData [ index ] . v += data [ d ] ; } else { subDomainsData [ index ] . v = data [ d ] ; } } } } , parseURI : function ( str , startDate , endDate ) { 'use strict' ; str = str . replace ( /\{\{t:start\}\}/g , startDate . getTime ( ) / 1000 ) ; str = str . replace ( /\{\{t:end\}\}/g , endDate . getTime ( ) / 1000 ) ; str = str . replace ( /\{\{d:start\}\}/g , startDate . toISOString ( ) ) ; str = str . replace ( /\{\{d:end\}\}/g , endDate . toISOString ( ) ) ; return str ; } , interpretCSV : function ( data ) { 'use strict' ; var d = { } ; var keys = Object . keys ( data [ 0 ] ) ; var i , total ; for ( i = 0 , total = data . length ; i < total ; i += 1 ) { d [ data [ i ] [ keys [ 0 ] ] ] = + data [ i ] [ keys [ 1 ] ] ; } return d ; } , return resize : function ( ) { 'use strict' ; var parent = this ; var options = parent . options ; var legendWidth = options . displayLegend ? parent . Legend . getDim ( 'width' ) + options . legendMargin [ 1 ] + options . legendMargin [ 3 ] : 0 ; var legendHeight = options . displayLegend ? parent . Legend . getDim ( 'height' ) + options . legendMargin [ 0 ] + options . legendMargin [ 2 ] : 0 ; var graphWidth = parent . graphDim . width - options . domainGutter - options . cellPadding ; var graphHeight = parent . graphDim . height - options . domainGutter - options . cellPadding ; this . root . transition ( ) . duration ( options . animationDuration ) . attr ( 'width' , function ( ) { if ( options . legendVerticalPosition === 'middle' || options . legendVerticalPosition === 'center' ) { return graphWidth + legendWidth ; } return Math . max ( graphWidth , legendWidth ) ; } ) . attr ( 'height' , function ( ) { if ( options . legendVerticalPosition === 'middle' || options . legendVerticalPosition === 'center' ) { return Math . max ( graphHeight , legendHeight ) ; } return graphHeight + legendHeight ; } ) ; this . root . select ( '.graph' ) . transition ( ) . duration ( options . animationDuration ) . attr ( 'y' , function ( ) { if ( options . legendVerticalPosition === 'top' ) { return legendHeight ; } return 0 ; } ) . attr ( 'x' , function ( ) { if ( ( options . legendVerticalPosition === 'middle' || options . legendVerticalPosition === 'center' ) && options . legendHorizontalPosition === 'left' ) { return legendWidth ; } return 0 ; } ) ; } , /**
     * Shift the calendar forward
     */ next : function ( n ) { 'use strict' ; if ( arguments . length === 0 ) { n = 1 ; } return this . loadNextDomain ( n ) ; } , /**
     * Shift the calendar backward
     */ previous : function ( n ) { 'use strict' ; if ( arguments . length === 0 ) { n = 1 ; } return this . loadPreviousDomain ( n ) ; } , param Date param bool param bool jumpTo : function ( date , reset ) { 'use strict' ; if ( arguments . length < 2 ) { reset = false ; } var domains = this . getDomainKeys ( ) ; var firstDomain = domains [ 0 ] ; var lastDomain = domains [ domains . length - 1 ] ; if ( date < firstDomain ) { return this . loadPreviousDomain ( this . getDomain ( firstDomain , date ) . length ) ; } else { if ( reset ) { return this . loadNextDomain ( this . getDomain ( firstDomain , date ) . length ) ; } if ( date > lastDomain ) { return this . loadNextDomain ( this . getDomain ( lastDomain , date ) . length ) ; } } return false ; } , since return rewind : function ( ) { 'use strict' ; this . jumpTo ( this . options . start , true ) ; } , param object param boolean update : function ( dataSource , afterLoad , updateMode ) { 'use strict' ; if ( arguments . length === 0 ) { dataSource = this . options . data ; } if ( arguments . length < 2 ) { afterLoad = true ; } if ( arguments . length < 3 ) { updateMode = this . RESET_ALL_ON_UPDATE ; } var domains = this . getDomainKeys ( ) ; var self = this ; this . getDatas ( dataSource , new Date ( domains [ 0 ] ) , this . getSubDomain ( domains [ domains . length - 1 ] ) . pop ( ) , function ( ) { self . fill ( ) ; self . afterUpdate ( ) ; } , afterLoad , updateMode ) ; } , param array param array setLegend : function ( ) { 'use strict' ; var oldLegend = this . options . legend . slice ( 0 ) ; if ( arguments . length >= 1 && Array . isArray ( arguments [ 0 ] ) ) { this . options . legend = arguments [ 0 ] ; } if ( arguments . length >= 2 ) { if ( Array . isArray ( arguments [ 1 ] ) && arguments [ 1 ] . length >= 2 ) { this . options . legendColors = [ arguments [ 1 ] [ 0 ] , arguments [ 1 ] [ 1 ] ] ; } else { this . options . legendColors = arguments [ 1 ] ; } } if ( ( arguments . length > 0 && ! arrayEquals ( oldLegend , this . options . legend ) ) || arguments . length >= 2 ) { this . Legend . buildColors ( ) ; this . fill ( ) ; } this . Legend . redraw ( this . graphDim . width - this . options . domainGutter - this . options . cellPadding ) ; } , return removeLegend : function ( ) { 'use strict' ; if ( ! this . options . displayLegend ) { return false ; } this . options . displayLegend = false ; this . Legend . remove ( ) ; return true ; } , return showLegend : function ( ) { 'use strict' ; if ( this . options . displayLegend ) { return false ; } this . options . displayLegend = true ; this . Legend . redraw ( this . graphDim . width - this . options . domainGutter - this . options . cellPadding ) ; return true ; } , since param array return highlight : function ( args ) { 'use strict' ; if ( ( this . options . highlight = this . expandDateSetting ( args ) ) . length > 0 ) { this . fill ( ) ; return true ; } return false ; } , since param function return destroy : function ( callback ) { 'use strict' ; this . root . transition ( ) . duration ( this . options . animationDuration ) . attr ( 'width' , 0 ) . attr ( 'height' , 0 ) . remove ( ) . each ( 'end' , function ( ) { if ( typeof callback === 'function' ) { callback ( ) ; } else if ( typeof callback !== 'undefined' ) { console . log ( 'Provided callback for destroy() is not a function.' ) ; } } ) ; return null ; } , getSVG : function ( ) { 'use strict' ; var styles = { '.cal-heatmap-container' : { } , '.graph' : { } , '.graph-rect' : { } , 'rect.highlight' : { } , 'rect.now' : { } , 'rect.highlight-now' : { } , 'text.highlight' : { } , 'text.now' : { } , 'text.highlight-now' : { } , '.domain-background' : { } , '.graph-label' : { } , '.subdomain-text' : { } , '.q0' : { } , '.qi' : { } , } ; for ( var j = 1 , total = this . options . legend . length + 1 ; j <= total ; j += 1 ) { styles [ '.q' + j ] = { } ; } var root = this . root ; var whitelistStyles = [ 'stroke' , 'stroke-width' , 'stroke-opacity' , 'stroke-dasharray' , 'stroke-dashoffset' , 'stroke-linecap' , 'stroke-miterlimit' , 'fill' , 'fill-opacity' , 'fill-rule' , 'marker' , 'marker-start' , 'marker-mid' , 'marker-end' , 'alignement-baseline' , 'baseline-shift' , 'dominant-baseline' , 'glyph-orientation-horizontal' , 'glyph-orientation-vertical' , 'kerning' , 'text-anchor' , 'shape-rendering' , 'text-transform' , 'font-family' , 'font' , 'font-size' , 'font-weight' , ] ; var filterStyles = function ( attribute , property , value ) { if ( whitelistStyles . indexOf ( property ) !== - 1 ) { styles [ attribute ] [ property ] = value ; } } ; var getElement = function ( e ) { return root . select ( e ) [ 0 ] [ 0 ] ; } ; for ( var element in styles ) { if ( ! styles . hasOwnProperty ( element ) ) { continue ; } var dom = getElement ( element ) ; if ( dom === null ) { continue ; } if ( 'getComputedStyle' in window ) { var cs = getComputedStyle ( dom , null ) ; if ( cs . length !== 0 ) { for ( var i = 0 ; i < cs . length ; i += 1 ) { filterStyles ( element , cs . item ( i ) , cs . getPropertyValue ( cs . item ( i ) ) ) ; } } else { for ( var k in cs ) { if ( cs . hasOwnProperty ( k ) ) { filterStyles ( element , k , cs [ k ] ) ; } } } } else if ( 'currentStyle' in dom ) { var css = dom . currentStyle ; for ( var p in css ) { filterStyles ( element , p , css [ p ] ) ; } } } var string = '<svg xmlns="http://www.w3.org/2000/svg" ' + 'xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css"><![CDATA[ ' ; for ( var style in styles ) { string += style + ' {\n' ; for ( var l in styles [ style ] ) { string += '\t' + l + ':' + styles [ style ] [ l ] + ';\n' ; } string += '}\n' ; } string += ']]></style>' ; string += new XMLSerializer ( ) . serializeToString ( this . root [ 0 ] [ 0 ] ) ; string += '</svg>' ; return string ; } , } ; /**
 * Compute the position of a domain, relative to the calendar
 */ var DomainPosition = function ( ) { 'use strict' ; this . positions = d3 . map ( ) ; } ; DomainPosition . prototype . getPosition = function ( d ) { 'use strict' ; return this . positions . get ( d ) ; } ; DomainPosition . prototype . getPositionFromIndex = function ( i ) { 'use strict' ; var domains = this . getKeys ( ) ; return this . positions . get ( domains [ i ] ) ; } ; DomainPosition . prototype . getLast = function ( ) { 'use strict' ; var domains = this . getKeys ( ) ; return this . positions . get ( domains [ domains . length - 1 ] ) ; } ; DomainPosition . prototype . setPosition = function ( d , dim ) { 'use strict' ; this . positions . set ( d , dim ) ; } ; DomainPosition . prototype . shiftRightBy = function ( exitingDomainDim ) { 'use strict' ; this . positions . forEach ( function ( key , value ) { this . set ( key , value - exitingDomainDim ) ; } ) ; var domains = this . getKeys ( ) ; this . positions . remove ( domains [ 0 ] ) ; } ; DomainPosition . prototype . shiftLeftBy = function ( enteringDomainDim ) { 'use strict' ; this . positions . forEach ( function ( key , value ) { this . set ( key , value + enteringDomainDim ) ; } ) ; var domains = this . getKeys ( ) ; this . positions . remove ( domains [ domains . length - 1 ] ) ; } ; DomainPosition . prototype . getKeys = function ( ) { 'use strict' ; return this . positions . keys ( ) . sort ( function ( a , b ) { return parseInt ( a , 10 ) - parseInt ( b , 10 ) ; } ) ; } ; var Legend = function ( calendar ) { 'use strict' ; this . calendar = calendar ; this . computeDim ( ) ; if ( calendar . options . legendColors !== null ) { this . buildColors ( ) ; } } ; Legend . prototype . computeDim = function ( ) { 'use strict' ; var options = this . calendar . options ; this . dim = { width : options . legendCellSize * ( options . legend . length + 1 ) + options . legendCellPadding * options . legend . length , height : options . legendCellSize , } ; } ; Legend . prototype . remove = function ( ) { 'use strict' ; this . calendar . root . select ( '.graph-legend' ) . remove ( ) ; this . calendar . resize ( ) ; } ; Legend . prototype . redraw = function ( width ) { 'use strict' ; if ( ! this . calendar . options . displayLegend ) { return false ; } var parent = this ; var calendar = this . calendar ; var legend = calendar . root ; var legendItem ; var options = calendar . options ; this . computeDim ( ) ; var _legend = options . legend . slice ( 0 ) ; _legend . push ( _legend [ _legend . length - 1 ] + 1 ) ; var legendElement = calendar . root . select ( '.graph-legend' ) ; if ( legendElement [ 0 ] [ 0 ] !== null ) { legend = legendElement ; legendItem = legend . select ( 'g' ) . selectAll ( 'rect' ) . data ( _legend ) ; } else { legend = options . legendVerticalPosition === 'top' ? legend . insert ( 'svg' , '.graph' ) : legend . append ( 'svg' ) ; legend . attr ( 'x' , getLegendXPosition ( ) ) . attr ( 'y' , getLegendYPosition ( ) ) ; legendItem = legend . attr ( 'class' , 'graph-legend' ) . attr ( 'height' , parent . getDim ( 'height' ) ) . attr ( 'width' , parent . getDim ( 'width' ) ) . append ( 'g' ) . selectAll ( ) . data ( _legend ) ; } legendItem . enter ( ) . append ( 'rect' ) . call ( legendCellLayout ) . attr ( 'class' , function ( d ) { return calendar . Legend . getClass ( d , calendar . legendScale === null ) ; } ) . attr ( 'fill-opacity' , 0 ) . call ( function ( selection ) { if ( calendar . legendScale !== null && options . legendColors !== null && options . legendColors . hasOwnProperty ( 'base' ) ) { selection . attr ( 'fill' , options . legendColors . base ) ; } } ) . append ( 'title' ) ; legendItem . exit ( ) . transition ( ) . duration ( options . animationDuration ) . attr ( 'fill-opacity' , 0 ) . remove ( ) ; legendItem . transition ( ) . delay ( function ( d , i ) { return ( options . animationDuration * i ) / 10 ; } ) . call ( legendCellLayout ) . attr ( 'fill-opacity' , 1 ) . call ( function ( element ) { element . attr ( 'fill' , function ( d , i ) { if ( calendar . legendScale === null ) { return '' ; } if ( i === 0 ) { return calendar . legendScale ( d - 1 ) ; } return calendar . legendScale ( options . legend [ i - 1 ] ) ; } ) ; element . attr ( 'class' , function ( d ) { return calendar . Legend . getClass ( d , calendar . legendScale === null ) ; } ) ; } ) ; function legendCellLayout ( selection ) { selection . attr ( 'width' , options . legendCellSize ) . attr ( 'height' , options . legendCellSize ) . attr ( 'rx' , options . legendCellRadius ) . attr ( 'ry' , options . legendCellRadius ) . attr ( 'x' , function ( d , i ) { return i * ( options . legendCellSize + options . legendCellPadding ) ; } ) ; } legendItem . select ( 'title' ) . text ( function ( d , i ) { if ( i === 0 ) { return calendar . formatStringWithObject ( options . legendTitleFormat . lower , { min : options . legend [ i ] , name : options . itemName [ 1 ] , } ) ; } else if ( i === _legend . length - 1 ) { return calendar . formatStringWithObject ( options . legendTitleFormat . upper , { max : options . legend [ i - 1 ] , name : options . itemName [ 1 ] , } ) ; } else { return calendar . formatStringWithObject ( options . legendTitleFormat . inner , { down : options . legend [ i - 1 ] , up : options . legend [ i ] , name : options . itemName [ 1 ] , } ) ; } } ) ; legendItem . on ( 'mouseover' , function ( d ) { calendar . legendTip . show ( d , this ) ; } ) . on ( 'mouseout' , function ( ) { calendar . legendTip . hide ( ) ; } ) ; legend . transition ( ) . duration ( options . animationDuration ) . attr ( 'x' , getLegendXPosition ( ) ) . attr ( 'y' , getLegendYPosition ( ) ) . attr ( 'width' , parent . getDim ( 'width' ) ) . attr ( 'height' , parent . getDim ( 'height' ) ) ; legend . select ( 'g' ) . transition ( ) . duration ( options . animationDuration ) . attr ( 'transform' , function ( ) { if ( options . legendOrientation === 'vertical' ) { return ( 'rotate(90 ' + options . legendCellSize / 2 + ' ' + options . legendCellSize / 2 + ')' ) ; } return '' ; } ) ; function getLegendXPosition ( ) { switch ( options . legendHorizontalPosition ) { case 'right' : if ( options . legendVerticalPosition === 'center' || options . legendVerticalPosition === 'middle' ) { return width + options . legendMargin [ 3 ] ; } return width - parent . getDim ( 'width' ) - options . legendMargin [ 1 ] ; case 'middle' : case 'center' : return Math . round ( width / 2 - parent . getDim ( 'width' ) / 2 ) ; default : return options . legendMargin [ 3 ] ; } } function getLegendYPosition ( ) { if ( options . legendVerticalPosition === 'bottom' ) { return ( calendar . graphDim . height + options . legendMargin [ 0 ] - options . domainGutter - options . cellPadding ) ; } return options . legendMargin [ 0 ] ; } calendar . resize ( ) ; } ; param string return Legend . prototype . getDim = function ( axis ) { 'use strict' ; var isHorizontal = this . calendar . options . legendOrientation === 'horizontal' ; switch ( axis ) { case 'width' : return this . dim [ isHorizontal ? 'width' : 'height' ] ; case 'height' : return this . dim [ isHorizontal ? 'height' : 'width' ] ; } } ; Legend . prototype . buildColors = function ( ) { 'use strict' ; var options = this . calendar . options ; if ( options . legendColors === null ) { this . calendar . legendScale = null ; return false ; } var _colorRange = [ ] ; if ( Array . isArray ( options . legendColors ) ) { _colorRange = options . legendColors ; } else if ( options . legendColors . hasOwnProperty ( 'min' ) && options . legendColors . hasOwnProperty ( 'max' ) ) { _colorRange = [ options . legendColors . min , options . legendColors . max ] ; } else { options . legendColors = null ; return false ; } var _legend = options . legend . slice ( 0 ) ; if ( _legend [ 0 ] > 0 ) { _legend . unshift ( 0 ) ; } else if ( _legend [ 0 ] <= 0 ) { _legend . unshift ( _legend [ 0 ] - ( _legend [ _legend . length - 1 ] - _legend [ 0 ] ) / _legend . length ) ; } var colorScale ; if ( options . legendColors . hasOwnProperty ( 'colorScale' ) ) { colorScale = options . legendColors . colorScale ; } else { colorScale = d3 . scale . linear ( ) . range ( _colorRange ) . interpolate ( d3 . interpolateHcl ) . domain ( [ d3 . min ( _legend ) , d3 . max ( _legend ) ] ) ; } var legendColors = _legend . map ( function ( element ) { return colorScale ( element ) ; } ) ; this . calendar . legendScale = d3 . scale . threshold ( ) . domain ( options . legend ) . range ( legendColors ) ; return true ; } ; param integer param bool return Legend . prototype . getClass = function ( n , withCssClass ) { 'use strict' ; if ( n === null || isNaN ( n ) ) { return '' ; } var index = [ this . calendar . options . legend . length + 1 ] ; for ( var i = 0 , total = this . calendar . options . legend . length - 1 ; i <= total ; i += 1 ) { if ( this . calendar . options . legend [ 0 ] > 0 && n < 0 ) { index = [ '1' , 'i' ] ; break ; } if ( n <= this . calendar . options . legend [ i ] ) { index = [ i + 1 ] ; break ; } } if ( n === 0 ) { index . push ( 0 ) ; } index . unshift ( '' ) ; return ( index . join ( ' r' ) + ( withCssClass ? index . join ( ' q' ) : '' ) ) . trim ( ) ; } ; /**
 * #source http://stackoverflow.com/a/383245/805649
 */ function mergeRecursive ( obj1 , obj2 ) { 'use strict' ; for ( var p in obj2 ) { try { if ( obj2 [ p ] . constructor === Object ) { obj1 [ p ] = mergeRecursive ( obj1 [ p ] , obj2 [ p ] ) ; } else { obj1 [ p ] = obj2 [ p ] ; } } catch ( e ) { obj1 [ p ] = obj2 [ p ] ; } } return obj1 ; } link param array return function arrayEquals ( arrayA , arrayB ) { 'use strict' ; if ( ! arrayB || ! arrayA ) { return false ; } if ( arrayA . length !== arrayB . length ) { return false ; } for ( var i = 0 ; i < arrayA . length ; i += 1 ) { if ( arrayA [ i ] instanceof Array && arrayB [ i ] instanceof Array ) { if ( ! arrayEquals ( arrayA [ i ] , arrayB [ i ] ) ) { return false ; } } else if ( arrayA [ i ] !== arrayB [ i ] ) { return false ; } } return true ; } export default CalHeatMap ;