@ -262,10 +262,12 @@ nv.models.axis = function() {
function chart ( selection ) {
selection . each ( function ( data ) {
scale . domain ( domain )
. range ( range ) ;
//scale.domain(domain)
//.range(range);
axis . orient ( orient ) ;
//axis.orient(orient);
if ( orient == 'top' || orient == 'bottom' )
axis . ticks ( Math . abs ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) / 100 ) ;
//TODO: consider calculating height based on whether or not label is added, for reference in charts using this component
@ -277,28 +279,28 @@ nv.models.axis = function() {
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , 0 ) ;
axisLabel
. attr ( 'x' , range[ 1 ] / 2 ) ;
. attr ( 'x' , scale. range( ) [ 1 ] / 2 ) ;
break ;
case 'right' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'axislabel' )
. attr ( 'transform' , 'rotate(90)' )
. attr ( 'y' , - 40 ) ; //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
. attr ( 'x' , - range[ 0 ] / 2 ) ;
. attr ( 'x' , - scale. range( ) [ 0 ] / 2 ) ;
break ;
case 'bottom' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'axislabel' )
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , 25 ) ;
axisLabel
. attr ( 'x' , range[ 1 ] / 2 ) ;
. attr ( 'x' , scale. range( ) [ 1 ] / 2 ) ;
break ;
case 'left' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'axislabel' )
. attr ( 'transform' , 'rotate(-90)' )
. attr ( 'y' , - 40 ) ; //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
. attr ( 'x' , - range[ 0 ] / 2 ) ;
. attr ( 'x' , - scale. range( ) [ 0 ] / 2 ) ;
break ;
}
axisLabel . exit ( ) . remove ( ) ;
@ -310,11 +312,12 @@ nv.models.axis = function() {
d3 . transition ( d3 . select ( this ) )
. call ( axis ) ;
d3 . select ( this )
. selectAll ( 'line.tick' )
//.filter(function(d) { return !parseFloat(d) })
. 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 ) ;
if ( orient == 'left' || orient == 'right' )
d3 . select ( this )
. selectAll ( 'line.tick' )
//.filter(function(d) { return !parseFloat(d) })
. 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 ) ;
} ) ;
@ -322,21 +325,25 @@ nv.models.axis = function() {
}
//TODO: orient, domain, and range could be rebind's... but then they won't return the chart component
chart . orient = function ( _ ) {
if ( ! arguments . length ) return orient ;
orient = _ ;
axis . orient ( _ ) ;
return chart ;
} ;
chart . domain = function ( _ ) {
if ( ! arguments . length ) return domain ;
domain = _ ;
//domain = _;
scale . domain ( _ ) ;
return chart ;
} ;
chart . range = function ( _ ) {
if ( ! arguments . length ) return range ;
range = _ ;
//range = _;
scale . range ( _ ) ;
return chart ;
} ;
@ -353,7 +360,7 @@ nv.models.axis = function() {
return chart ;
}
//d3.rebind(chart, scale, 'domain', 'range', 'rangRoundBands', 'rangeBands'); //would implement, but will break because domain and range won't return chart... will likelu implement later
d3 . rebind ( chart , axis , 'ticks' , 'tickSubdivide' , 'tickSize' , 'tickPadding' , 'tickFormat' ) ;
return chart ;
@ -1895,13 +1902,8 @@ nv.models.linePlusBar = function() {
bars . dispatch . on ( 'elementMouseover.tooltip' , function ( e ) {
dispatch . tooltipShow ( {
point : e . point ,
series : e . series ,
pos : [ e . pos [ 0 ] + margin . left , e . pos [ 1 ] + margin . top ] ,
seriesIndex : e . seriesIndex ,
pointIndex : e . pointIndex
} ) ;
e . pos = [ e . pos [ 0 ] + margin . left , e . pos [ 1 ] + margin . top ] ;
dispatch . tooltipShow ( e ) ;
} ) ;
bars . dispatch . on ( 'elementMouseout.tooltip' , function ( e ) {
@ -2579,7 +2581,8 @@ nv.models.multiBar = function() {
clipEdge = true ,
stacked = false ,
color = d3 . scale . category20 ( ) . range ( ) ,
xDomain , yDomain ;
xDomain , yDomain ,
x0 , y0 ;
//var x = d3.scale.linear(),
var x = d3 . scale . ordinal ( ) , //TODO: Need to figure out how to use axis model with ordinal scale
@ -2610,17 +2613,29 @@ nv.models.multiBar = function() {
availableWidth = width - margin . left - margin . right ,
availableHeight = height - margin . top - margin . bottom ;
//store old scales if they exist
x0 = x0 || x ;
y0 = y0 || y ;
//add series index to each data point for reference
data = data . map ( function ( series , i ) {
series . values = series . values . map ( function ( point ) {
point . series = i ;
return point ;
} ) ;
return series ;
} ) ;
//x .domain(xDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.x }).concat(forceX)))
x . domain ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . x } ) )
//x .domain(seriesData[0].map(function(d) { return d.x }))
. rangeRoundBands ( [ 0 , availableWidth ] , . 1 ) ;
. rangeBands ( [ 0 , availableWidth ] , . 1 ) ;
//.rangeRoundBands([0, availableWidth], .1);
//.range([0, availableWidth]);
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (d.y0 || 0) }).concat(forceY)))
y . domain ( yDomain || [ 0 , d3 . max ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . y + ( d. y0 || 0 ) } ) . concat ( forceY ) ) ] )
y . domain ( yDomain || [ 0 , d3 . max ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . y + ( stacked ? d . y0 : 0 ) } ) . concat ( forceY ) ) ] )
. range ( [ availableHeight , 0 ] ) ;
@ -2661,7 +2676,7 @@ nv.models.multiBar = function() {
. attr ( 'transform' , function ( d , i ) {
return stacked ?
'translate(0,0)'
: 'translate(' + ( i * x . rangeBand ( .9 ) / data . length ) + ',0)'
: 'translate(' + ( i * x . rangeBand ( ) / data . length ) + ',0)'
} ) ;
d3 . transition ( groups . exit ( ) )
. style ( 'stroke-opacity' , 1e-6 )
@ -2676,10 +2691,10 @@ nv.models.multiBar = function() {
. attr ( 'transform' , function ( d , i ) {
return stacked ?
'translate(0,0)'
: 'translate(' + ( i * x . rangeBand ( .9 ) / data . length ) + ',0)'
: 'translate(' + ( i * x . rangeBand ( ) / data . length ) + ',0)'
} )
. style ( 'stroke-opacity' , 1 )
. style ( 'fill-opacity' , . 5) ;
. style ( 'fill-opacity' , . 7 5) ;
var bars = groups . selectAll ( 'rect.bar' )
@ -2692,18 +2707,19 @@ nv.models.multiBar = function() {
. attr ( 'class' , function ( d , i ) { return getY ( d , i ) < 0 ? 'bar negative' : 'bar positive' } )
//.attr('fill', function(d,i) { return color[0]; })
. attr ( 'x' , 0 )
. attr ( 'y' , function ( d , i ) { return y ( Math . max ( 0 , getY ( d , i ) ) ) } )
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
. attr ( 'y' , y ( 0 ) )
. attr ( 'y' , function ( d ) { return y 0 ( stacked ? d . y0 : 0 ) } )
. attr ( 'height' , 0 )
. attr ( 'width' , x . rangeBand ( ) / ( stacked ? 1 : data . length ) )
. on ( 'mouseover' , function ( d , i ) {
d3 . select ( this ) . classed ( 'hover' , true ) ;
dispatch . elementMouseover ( {
point : d ,
series : data [ 0 ] ,
pos : [ x ( getX ( d , i ) ) , y ( getY ( d , i ) ) ] , // TODO: Figure out why the value appears to be shifted
series : data [ d . series ] ,
pos : [ x ( getX ( d , i ) ) + ( x . rangeBand ( ) * ( stacked ? data . length / 2 : d . series + . 5 ) / data . length ) , y ( getY ( d , i ) + ( stacked ? d . y0 : 0 ) ) ] , // TODO: Figure out why the value appears to be shifted
pointIndex : i ,
seriesIndex : 0 ,
seriesIndex : d . series ,
e : d3 . event
} ) ;
@ -2712,9 +2728,9 @@ nv.models.multiBar = function() {
d3 . select ( this ) . classed ( 'hover' , false ) ;
dispatch . elementMouseout ( {
point : d ,
series : data [ 0 ] ,
series : data [ d . series ] ,
pointIndex : i ,
seriesIndex : 0 ,
seriesIndex : d . series ,
e : d3 . event
} ) ;
} )
@ -2747,15 +2763,24 @@ nv.models.multiBar = function() {
//.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - x(.5)) + ',0)'; })
. attr ( 'transform' , function ( d , i ) { return 'translate(' + x ( getX ( d , i ) ) + ',0)' ; } )
//.attr('width', x(.9) / data.length ) //TODO: this should not assume that each consecutive bar x = x + 1
. attr ( 'width' , x . rangeBand ( ) / ( stacked ? 1 : data . length ) )
d3 . transition ( bars )
. attr ( 'width' , x . rangeBand ( ) / ( stacked ? 1 : data . length ) )
. attr ( 'y' , function ( d , i ) {
return y ( getY ( d , i ) + ( d. y0 || 0 ) ) ;
return y ( getY ( d , i ) + ( stacked ? d . y0 : 0 ) ) ;
} )
. attr ( 'height' , function ( d , i ) {
return Math . abs ( y ( d . y + ( d. y0 || 0 ) ) - y ( ( d . y0 || 0 ) ) )
return Math . abs ( y ( d . y + ( stacked ? d . y0 : 0 ) ) - y ( ( stacked ? d . y0 : 0 ) ) )
} ) ;
chart . update = function ( ) {
selection . transition ( ) . call ( chart ) ;
}
//store old scales for use in transitions on update, to animate from old to new positions, and sizes
x0 = x . copy ( ) ;
y0 = y . copy ( ) ;
} ) ;
return chart ;
@ -2844,6 +2869,229 @@ nv.models.multiBar = function() {
return chart ;
}
nv . models . multiBarWithLegend = function ( ) {
var margin = { top : 30 , right : 20 , bottom : 50 , left : 60 } ,
width = function ( ) { return 960 } ,
height = function ( ) { return 500 } ,
color = d3 . scale . category20 ( ) . range ( ) ;
//var x = d3.scale.linear(),
var x = d3 . scale . ordinal ( ) ,
y = d3 . scale . linear ( ) ,
xAxis = nv . models . axis ( ) . scale ( x ) . orient ( 'bottom' ) ,
yAxis = nv . models . axis ( ) . scale ( y ) . orient ( 'left' ) ,
legend = nv . models . legend ( ) . height ( 30 ) ,
controls = nv . models . legend ( ) . height ( 30 ) ,
multibar = nv . models . multiBar ( ) . stacked ( false ) ,
dispatch = d3 . dispatch ( 'tooltipShow' , 'tooltipHide' ) ;
//TODO: let user select default
var controlsData = [
{ key : 'Grouped' } ,
{ key : 'Stacked' , disabled : true } ,
] ;
function chart ( selection ) {
selection . each ( function ( data ) {
var availableWidth = width ( ) - margin . left - margin . right ,
availableHeight = height ( ) - margin . top - margin . bottom ,
seriesData ;
if ( multibar . stacked ( ) ) {
seriesData = data . filter ( function ( d ) { return ! d . disabled } )
. reduce ( function ( prev , curr , index ) { //sum up all the y's
curr . values . forEach ( function ( d , i ) {
if ( ! index ) prev [ i ] = { x : multibar . x ( ) ( d , i ) , y : 0 } ;
prev [ i ] . y += multibar . y ( ) ( d , i ) ;
} ) ;
return prev ;
} , [ ] ) ;
} else {
seriesData = data . filter ( function ( d ) { return ! d . disabled } )
. map ( function ( d ) {
return d . values . map ( function ( d , i ) {
return { x : multibar . x ( ) ( d , i ) , y : multibar . y ( ) ( d , i ) }
} )
} ) ;
}
//x .domain(d3.extent(d3.merge(seriesData).map(function(d) { return d.x }).concat(multibar.forceX) ))
//.range([0, availableWidth]);
x . domain ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . x } ) )
. rangeBands ( [ 0 , availableWidth ] , . 1 ) ;
//.rangeRoundBands([0, availableWidth], .1);
y . domain ( d3 . extent ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . y } ) . concat ( multibar . forceY ) ) )
. range ( [ availableHeight , 0 ] ) ;
multibar
. width ( availableWidth )
. height ( availableHeight )
//.xDomain(x.domain())
//.yDomain(y.domain())
. color ( data . map ( function ( d , i ) {
return d . color || color [ i % 10 ] ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled } ) )
var wrap = d3 . select ( this ) . selectAll ( 'g.wrap' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'wrap d3lineWithLegend' ) . append ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'x axis' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'y axis' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'linesWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'legendWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'controlsWrap' ) ;
//TODO: margins should be adjusted based on what components are used: axes, axis labels, legend
margin . top = legend . height ( ) ;
var g = wrap . select ( 'g' )
. attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
legend . width ( availableWidth / 2 ) ;
g . select ( '.legendWrap' )
. datum ( data )
. attr ( 'transform' , 'translate(' + ( availableWidth / 2 ) + ',' + ( - margin . top ) + ')' )
. call ( legend ) ;
controls . width ( 180 ) . color ( [ '#444' , '#444' , '#444' ] ) ;
g . select ( '.controlsWrap' )
. datum ( controlsData )
. attr ( 'transform' , 'translate(0,' + ( - margin . top ) + ')' )
. call ( controls ) ;
var linesWrap = g . select ( '.linesWrap' )
. datum ( data . filter ( function ( d ) { return ! d . disabled } ) )
d3 . transition ( linesWrap ) . call ( multibar ) ;
xAxis
. scale ( x )
//.domain(x.domain())
//.range(x.range())
. ticks ( availableWidth / 100 )
. tickSize ( - availableHeight , 0 ) ;
g . select ( '.x.axis' )
. attr ( 'transform' , 'translate(0,' + y . range ( ) [ 0 ] + ')' ) ;
d3 . transition ( g . select ( '.x.axis' ) )
. call ( xAxis ) ;
var xTicks = g . select ( '.x.axis' ) . selectAll ( 'g' ) ;
xTicks
. selectAll ( 'line, text' )
. style ( 'opacity' , 1 )
xTicks . filter ( function ( d , i ) {
return i % Math . ceil ( data [ 0 ] . values . length / ( availableWidth / 100 ) ) !== 0 ;
} )
. selectAll ( 'line, text' )
. style ( 'opacity' , 0 )
yAxis
. domain ( y . domain ( ) )
. range ( y . range ( ) )
. ticks ( availableHeight / 36 )
. tickSize ( - availableWidth , 0 ) ;
d3 . transition ( g . select ( '.y.axis' ) )
. call ( yAxis ) ;
legend . dispatch . on ( 'legendClick' , function ( d , i ) {
d . disabled = ! d . disabled ;
if ( ! data . filter ( function ( d ) { return ! d . disabled } ) . length ) {
data . map ( function ( d ) {
d . disabled = false ;
wrap . selectAll ( '.series' ) . classed ( 'disabled' , false ) ;
return d ;
} ) ;
}
selection . transition ( ) . call ( chart ) ;
} ) ;
controls . dispatch . on ( 'legendClick' , function ( d , i ) {
if ( ! d . disabled ) return ;
controlsData = controlsData . map ( function ( s ) {
s . disabled = true ;
return s ;
} ) ;
d . disabled = false ;
switch ( d . key ) {
case 'Grouped' :
multibar . stacked ( false ) ;
break ;
case 'Stacked' :
multibar . stacked ( true ) ;
break ;
}
selection . transition ( ) . call ( chart ) ;
} ) ;
multibar . dispatch . on ( 'elementMouseover.tooltip' , function ( e ) {
e . pos = [ e . pos [ 0 ] + margin . left , e . pos [ 1 ] + margin . top ] ;
dispatch . tooltipShow ( e ) ;
} ) ;
multibar . dispatch . on ( 'elementMouseout.tooltip' , function ( e ) {
dispatch . tooltipHide ( e ) ;
} ) ;
} ) ;
return chart ;
}
chart . dispatch = dispatch ;
chart . legend = legend ;
chart . xAxis = xAxis ;
chart . yAxis = yAxis ;
d3 . rebind ( chart , multibar , 'x' , 'y' , 'xDomain' , 'yDomain' , 'forceX' , 'forceY' , 'clipEdge' , 'id' ) ;
chart . margin = function ( _ ) {
if ( ! arguments . length ) return margin ;
margin = _ ;
return chart ;
} ;
chart . width = function ( _ ) {
if ( ! arguments . length ) return width ;
width = d3 . functor ( _ ) ;
return chart ;
} ;
chart . height = function ( _ ) {
if ( ! arguments . length ) return height ;
height = d3 . functor ( _ ) ;
return chart ;
} ;
return chart ;
}
@ -4343,8 +4591,7 @@ nv.models.stackedAreaWithLegend = function() {
availableWidth = width - margin . left - margin . right ,
availableHeight = height - margin . top - margin . bottom ;
var series = data . filter ( function ( d ) { return ! d . disabled } )
//.map(function(d) { return d.values });
var seriesData = data . filter ( function ( d ) { return ! d . disabled } )
. reduce ( function ( prev , curr , index ) { //sum up all the y's
curr . values . forEach ( function ( d , i ) {
if ( ! index ) prev [ i ] = { x : getX ( d , i ) , y : 0 } ;
@ -4354,11 +4601,11 @@ nv.models.stackedAreaWithLegend = function() {
} , [ ] ) ;
x . domain ( d3 . extent ( d3 . merge ( series ) , function ( d ) { return d . x } ) )
x . domain ( d3 . extent ( d3 . merge ( series Data ) , function ( d ) { return d . x } ) )
. range ( [ 0 , availableWidth ] ) ;
y . domain ( stacked . offset ( ) == 'zero' ?
[ 0 , d3 . max ( series , function ( d ) { return d . y } ) ] :
[ 0 , d3 . max ( series Data , function ( d ) { return d . y } ) ] :
[ 0 , 1 ] // 0 - 100%
)
. range ( [ availableHeight , 0 ] ) ;