nv . models . axis = function ( ) {
//Default Settings
var width = 60 , //only used for tickLabel currently
height = 60 , //only used for tickLabel currently
scale = d3 . scale . linear ( ) ,
axisLabelText = null ,
showMaxMin = true , //TODO: showMaxMin should be disabled on all ordinal scaled axes
highlightZero = true ,
rotateLabels = 0 ,
rotateYLabel = true ;
margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
var axis = d3 . svg . axis ( )
. scale ( scale )
. orient ( 'bottom' )
. tickFormat ( function ( d ) { return d } ) , //TODO: decide if we want to keep this
scale0 ;
function chart ( selection ) {
selection . each ( function ( data ) {
var container = d3 . select ( this ) ;
var wrap = container . selectAll ( 'g.nv-wrap.nv-axis' ) . data ( [ data ] ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-axis' ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' )
if ( axis . orient ( ) == 'top' || axis . orient ( ) == 'bottom' )
axis . ticks ( Math . abs ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) / 100 ) ;
//TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
d3 . transition ( g )
. call ( axis ) ;
scale0 = scale0 || axis . scale ( ) ;
var axisLabel = g . selectAll ( 'text.nv-axislabel' )
. data ( [ axisLabelText || null ] ) ;
axisLabel . exit ( ) . remove ( ) ;
switch ( axis . orient ( ) ) {
case 'top' :
var w = scale . range ( ) . length == 2 ?
scale . range ( ) [ 1 ] :
( scale . range ( ) [ scale . range ( ) . length - 1 ] + ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) ) ;
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' )
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , 0 ) ;
axisLabel
. attr ( 'x' , w / 2 ) ;
if ( showMaxMin ) {
var axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nv-axisMaxMin' ) . append ( 'text' ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + scale ( d ) + ',0)'
} )
. select ( 'text' )
. attr ( 'dy' , '0em' )
. attr ( 'y' , - axis . tickPadding ( ) )
. attr ( 'text-anchor' , 'middle' )
. text ( function ( d , i ) {
return ( '' + axis . tickFormat ( ) ( d ) ) . match ( 'NaN' ) ? '' : axis . tickFormat ( ) ( d )
} ) ;
d3 . transition ( axisMaxMin )
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + scale . range ( ) [ i ] + ',0)'
} ) ;
}
break ;
case 'bottom' :
var xLabelMargin = 30 ;
var maxTextWidth = 30 ;
if ( rotateLabels % 360 ) {
var xTicks = g . selectAll ( 'g' ) . select ( "text" ) ;
//Calculate the longest xTick width
xTicks . each ( function ( d , i ) {
var width = this . getBBox ( ) . width ;
if ( width > maxTextWidth ) maxTextWidth = width ;
} ) ;
//Convert to radians before calculating sin. Add 30 to margin for healthy padding.
var sin = Math . abs ( Math . sin ( rotateLabels * Math . PI / 180 ) ) ;
var xLabelMargin = ( sin ? sin * maxTextWidth : maxTextWidth ) + 30 ;
//Rotate all xTicks
xTicks
. attr ( 'transform' , 'rotate(' + rotateLabels + ' 0,0)' )
. attr ( 'text-anchor' , rotateLabels % 360 > 0 ? 'start' : 'end' ) ;
}
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' )
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , xLabelMargin ) ;
var w = ( scale . range ( ) . length == 2 ) ?
scale . range ( ) [ 1 ] :
scale . range ( ) [ scale . range ( ) . length - 1 ] + ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) ;
axisLabel
. attr ( 'x' , w / 2 ) ;
if ( showMaxMin ) {
var axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nv-axisMaxMin' ) . append ( 'text' ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + scale ( d ) + ',0)'
} )
. select ( 'text' )
. attr ( 'dy' , '.71em' )
. attr ( 'y' , axis . tickPadding ( ) )
. attr ( 'transform' , function ( d , i , j ) { return 'rotate(' + rotateLabels + ' 0,0)' } )
. attr ( 'text-anchor' , rotateLabels % 360 > 0 ? 'start' : 'end' )
. text ( function ( d , i ) {
return ( '' + axis . tickFormat ( ) ( d ) ) . match ( 'NaN' ) ? '' : axis . tickFormat ( ) ( d )
} ) ;
d3 . transition ( axisMaxMin )
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + scale . range ( ) [ i ] + ',0)'
} ) ;
}
break ;
case 'right' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' )
. attr ( 'text-anchor' , rotateYLabel ? 'middle' : 'begin' )
. attr ( 'transform' , rotateYLabel ? 'rotate(90)' : '' )
. attr ( 'y' , rotateYLabel ? ( - Math . max ( margin . right , width ) - 12 ) : - 10 ) ; //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
. attr ( 'x' , rotateYLabel ? ( scale . range ( ) [ 0 ] / 2 ) : axis . tickPadding ( ) ) ;
if ( showMaxMin ) {
var axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nv-axisMaxMin' ) . append ( 'text' )
. style ( 'opacity' , 0 ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + scale ( d ) + ')'
} )
. select ( 'text' )
. attr ( 'dy' , '.32em' )
. attr ( 'y' , 0 )
. attr ( 'x' , axis . tickPadding ( ) )
. attr ( 'text-anchor' , 'start' )
. text ( function ( d , i ) {
return ( '' + axis . tickFormat ( ) ( d ) ) . match ( 'NaN' ) ? '' : axis . tickFormat ( ) ( d )
} ) ;
d3 . transition ( axisMaxMin )
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + scale . range ( ) [ i ] + ')'
} )
. select ( 'text' )
. style ( 'opacity' , 1 ) ;
}
break ;
case 'left' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' )
. attr ( 'text-anchor' , rotateYLabel ? 'middle' : 'end' )
. attr ( 'transform' , rotateYLabel ? 'rotate(-90)' : '' )
. attr ( 'y' , rotateYLabel ? ( - Math . max ( margin . left , width ) + 12 ) : - 10 ) ; //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
. attr ( 'x' , rotateYLabel ? ( - scale . range ( ) [ 0 ] / 2 ) : - axis . tickPadding ( ) ) ;
if ( showMaxMin ) {
var axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nv-axisMaxMin' ) . append ( 'text' )
. style ( 'opacity' , 0 ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + scale0 ( d ) + ')'
} )
. select ( 'text' )
. attr ( 'dy' , '.32em' )
. attr ( 'y' , 0 )
. attr ( 'x' , - axis . tickPadding ( ) )
. attr ( 'text-anchor' , 'end' )
. text ( function ( d , i ) {
return ( '' + axis . tickFormat ( ) ( d ) ) . match ( 'NaN' ) ? '' : axis . tickFormat ( ) ( d )
} ) ;
d3 . transition ( axisMaxMin )
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + scale . range ( ) [ i ] + ')'
} )
. select ( 'text' )
. style ( 'opacity' , 1 ) ;
}
break ;
}
axisLabel
. text ( function ( d ) { return d } ) ;
//check if max and min overlap other values, if so, hide the values that overlap
if ( showMaxMin && ( axis . orient ( ) === 'left' || axis . orient ( ) === 'right' ) ) {
g . selectAll ( 'g' ) // the g's wrapping each tick
. each ( function ( d , i ) {
if ( scale ( d ) < scale . range ( ) [ 1 ] + 10 || scale ( d ) > scale . range ( ) [ 0 ] - 10 ) { // 10 is assuming text height is 16... if d is 0, leave it!
if ( d > 1e-10 || d < - 1e-10 ) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
d3 . select ( this ) . remove ( ) ;
else
d3 . select ( this ) . select ( 'text' ) . remove ( ) ; // Don't remove the ZERO line!!
}
} ) ;
}
if ( showMaxMin && ( axis . orient ( ) === 'top' || axis . orient ( ) === 'bottom' ) ) {
var maxMinRange = [ ] ;
wrap . selectAll ( 'g.nv-axisMaxMin' )
. each ( function ( d , i ) {
if ( i ) // i== 1, max position
maxMinRange . push ( scale ( d ) - this . getBBox ( ) . width - 4 ) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
else // i==0, min position
maxMinRange . push ( scale ( d ) + this . getBBox ( ) . width + 4 )
} ) ;
g . selectAll ( 'g' ) // the g's wrapping each tick
. each ( function ( d , i ) {
if ( scale ( d ) < maxMinRange [ 0 ] || scale ( d ) > maxMinRange [ 1 ] ) {
if ( d > 1e-10 || d < - 1e-10 ) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
d3 . select ( this ) . remove ( ) ;
else
d3 . select ( this ) . select ( 'text' ) . remove ( ) ; // Don't remove the ZERO line!!
}
} ) ;
}
//highlight zero line ... Maybe should not be an option and should just be in CSS?
if ( highlightZero )
g . selectAll ( 'line.tick' )
. filter ( function ( d ) { return ! parseFloat ( Math . round ( d * 100000 ) / 1000000 ) } ) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
. classed ( 'zero' , true ) ;
scale0 = scale . copy ( ) ;
} ) ;
return chart ;
}
d3 . rebind ( chart , axis , 'orient' , 'ticks' , 'tickValues' , 'tickSubdivide' , 'tickSize' , 'tickPadding' , 'tickFormat' ) ;
d3 . rebind ( chart , scale , 'domain' , 'range' , 'rangeBand' , 'rangeBands' ) ; //these are also accessible by chart.scale(), but added common ones directly for ease of use
chart . width = function ( _ ) {
if ( ! arguments . length ) return width ;
width = _ ;
return chart ;
} ;
chart . height = function ( _ ) {
if ( ! arguments . length ) return height ;
height = _ ;
return chart ;
} ;
chart . axisLabel = function ( _ ) {
if ( ! arguments . length ) return axisLabelText ;
axisLabelText = _ ;
return chart ;
}
chart . showMaxMin = function ( _ ) {
if ( ! arguments . length ) return showMaxMin ;
showMaxMin = _ ;
return chart ;
}
chart . highlightZero = function ( _ ) {
if ( ! arguments . length ) return highlightZero ;
highlightZero = _ ;
return chart ;
}
chart . scale = function ( _ ) {
if ( ! arguments . length ) return scale ;
scale = _ ;
axis . scale ( scale ) ;
d3 . rebind ( chart , scale , 'domain' , 'range' , 'rangeBand' , 'rangeBands' ) ;
return chart ;
}
chart . rotateYLabel = function ( _ ) {
if ( ! arguments . length ) return rotateYLabel ;
rotateYLabel = _ ;
return chart ;
}
chart . rotateLabels = function ( _ ) {
if ( ! arguments . length ) return rotateLabels ;
rotateLabels = _ ;
return chart ;
}
chart . margin = function ( _ ) {
if ( ! arguments . length ) return margin ;
margin = _ ;
return chart ;
}
return chart ;
}