2012-03-20 23:43:21 +00:00
2012-03-21 22:20:54 +00:00
nv . models . lineWithFocus = function ( ) {
2012-03-20 23:43:21 +00:00
var margin = { top : 30 , right : 20 , bottom : 30 , left : 60 } ,
margin2 = { top : 0 , right : 20 , bottom : 20 , left : 60 } ,
width = 960 ,
height = 500 ,
height1 = 400 ,
height2 = 100 ,
dotRadius = function ( ) { return 2.5 } ,
color = d3 . scale . category10 ( ) . range ( ) ,
2012-03-23 04:44:52 +00:00
getX = function ( d ) { return d . x } ,
getY = function ( d ) { return d . y } ,
id = Math . floor ( Math . random ( ) * 10000 ) ; //Create semi-unique ID incase user doesn't select one
2012-03-20 23:43:21 +00:00
var x = d3 . scale . linear ( ) ,
y = d3 . scale . linear ( ) ,
x2 = d3 . scale . linear ( ) ,
y2 = d3 . scale . linear ( ) ,
xAxis = nv . models . xaxis ( ) . scale ( x ) ,
yAxis = nv . models . yaxis ( ) . scale ( y ) ,
xAxis2 = nv . models . xaxis ( ) . scale ( x2 ) ,
yAxis2 = nv . models . yaxis ( ) . scale ( y2 ) ,
legend = nv . models . legend ( ) . height ( 30 ) ,
focus = nv . models . line ( ) ,
2012-03-23 04:44:52 +00:00
context = nv . models . line ( ) . dotRadius ( . 1 ) . interactive ( false ) ,
2012-03-24 00:59:25 +00:00
dispatch = d3 . dispatch ( 'tooltipShow' , 'tooltipHide' ) ,
brush = d3 . svg . brush ( )
. x ( x2 ) ;
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
//var wrap, gEnter, g, focus, focusLines, contextWrap, focusWrap, contextLines; //brought all variables to this scope for use within brush function... is this a bad idea?
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
//var seriesData; //Temporarily bringing this data to this scope.... may be bad idea (same with above).. may need to rethink brushing
2012-03-20 23:43:21 +00:00
function chart ( selection ) {
selection . each ( function ( data ) {
2012-03-22 23:26:02 +00:00
seriesData = data . filter ( function ( d ) { return ! d . disabled } )
2012-03-20 23:43:21 +00:00
. map ( function ( d ) { return d . values } ) ;
x2 . domain ( d3 . extent ( d3 . merge ( seriesData ) , getX ) )
. range ( [ 0 , width - margin . left - margin . right ] ) ;
y2 . domain ( d3 . extent ( d3 . merge ( seriesData ) , getY ) )
. range ( [ height2 - margin2 . top - margin2 . bottom , 0 ] ) ;
x . domain ( brush . empty ( ) ? x2 . domain ( ) : brush . extent ( ) )
. range ( [ 0 , width - margin . left - margin . right ] ) ;
y . domain ( y2 . domain ( ) )
. range ( [ height1 - margin . top - margin . bottom , 0 ] ) ;
2012-03-24 00:59:25 +00:00
brush . on ( 'brush' , onBrush ) ;
2012-03-20 23:43:21 +00:00
focus
. width ( width - margin . left - margin . right )
. height ( height1 - margin . top - margin . bottom )
. color ( data . map ( function ( d , i ) {
return d . color || color [ i % 10 ] ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled } ) )
context
. width ( width - margin . left - margin . right )
. height ( height2 - margin2 . top - margin2 . bottom )
. color ( data . map ( function ( d , i ) {
return d . color || color [ i % 10 ] ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled } ) )
2012-03-24 00:59:25 +00:00
updateFocus ( ) ;
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
var wrap = d3 . select ( this ) . selectAll ( 'g.wrap' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'wrap d3lineWithFocus' ) . append ( 'g' ) ;
2012-03-20 23:43:21 +00:00
gEnter . append ( 'g' ) . attr ( 'class' , 'focus' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'context' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'legendWrap' ) ;
2012-03-24 00:59:25 +00:00
var g = wrap . select ( 'g' )
2012-03-20 23:43:21 +00:00
//.attr('transform', 'translate(0,0)');
// ********** LEGEND **********
legend . width ( width / 2 - margin . right ) ;
g . select ( '.legendWrap' )
. datum ( data )
. attr ( 'transform' , 'translate(' + ( width / 2 - margin . left ) + ',0)' )
. call ( legend ) ;
//TODO: margins should be adjusted based on what components are used: axes, axis labels, legend
margin . top = legend . height ( ) ;
// ********** FOCUS **********
2012-03-24 00:59:25 +00:00
var focusWrap = g . select ( '.focus' )
2012-03-20 23:43:21 +00:00
. attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
gEnter . select ( '.focus' ) . append ( 'g' ) . attr ( 'class' , 'x axis' ) ;
gEnter . select ( '.focus' ) . append ( 'g' ) . attr ( 'class' , 'y axis' ) ;
gEnter . select ( '.focus' ) . append ( 'g' ) . attr ( 'class' , 'focusLines' ) ;
2012-03-24 00:59:25 +00:00
var focusLines = focusWrap . select ( '.focusLines' )
2012-03-20 23:43:21 +00:00
. datum ( data . filter ( function ( d ) { return ! d . disabled } ) )
d3 . transition ( focusLines ) . call ( focus ) ;
xAxis
. domain ( x . domain ( ) )
. range ( x . range ( ) )
. ticks ( width / 100 )
. tickSize ( - ( height1 - margin . top - margin . bottom ) , 0 ) ;
2012-03-24 00:59:25 +00:00
focusWrap . select ( '.x.axis' )
2012-03-20 23:43:21 +00:00
. attr ( 'transform' , 'translate(0,' + y . range ( ) [ 0 ] + ')' ) ;
d3 . transition ( g . select ( '.x.axis' ) )
. call ( xAxis ) ;
yAxis
. domain ( y . domain ( ) )
. range ( y . range ( ) )
. ticks ( height / 36 )
. tickSize ( - ( width - margin . right - margin . left ) , 0 ) ;
d3 . transition ( g . select ( '.y.axis' ) )
. call ( yAxis ) ;
// ********** CONTEXT **********
2012-03-24 00:59:25 +00:00
var contextWrap = g . select ( '.context' )
2012-03-20 23:43:21 +00:00
. attr ( 'transform' , 'translate(' + margin2 . left + ',' + height1 + ')' ) ;
gEnter . select ( '.context' ) . append ( 'g' ) . attr ( 'class' , 'x2 axis' ) ;
gEnter . select ( '.context' ) . append ( 'g' ) . attr ( 'class' , 'y2 axis' ) ;
gEnter . select ( '.context' ) . append ( 'g' ) . attr ( 'class' , 'contextLines' ) ;
gEnter . select ( '.context' ) . append ( 'g' ) . attr ( 'class' , 'x brush' )
. attr ( 'class' , 'x brush' )
. call ( brush )
. selectAll ( 'rect' )
. attr ( 'y' , - 5 )
. attr ( 'height' , height2 + 4 ) ;
2012-03-24 00:59:25 +00:00
var contextLines = contextWrap . select ( '.contextLines' )
2012-03-20 23:43:21 +00:00
. datum ( data . filter ( function ( d ) { return ! d . disabled } ) )
d3 . transition ( contextLines ) . call ( context ) ;
xAxis2
. domain ( x2 . domain ( ) )
. range ( x2 . range ( ) )
. ticks ( width / 100 )
. tickSize ( - ( height2 - margin2 . top - margin2 . bottom ) , 0 ) ;
contextWrap . select ( '.x2.axis' )
. attr ( 'transform' , 'translate(0,' + y2 . range ( ) [ 0 ] + ')' ) ;
d3 . transition ( contextWrap . select ( '.x2.axis' ) )
. call ( xAxis2 ) ;
yAxis2
. domain ( y2 . domain ( ) )
. range ( y2 . range ( ) )
. ticks ( ( height2 - margin2 . top - margin2 . bottom ) / 24 )
. tickSize ( - ( width - margin2 . right - margin2 . left ) , 0 ) ;
contextWrap . select ( '.y2.axis' ) ;
d3 . transition ( contextWrap . select ( '.y2.axis' ) )
. call ( yAxis2 ) ;
// ********** EVENT LISTENERS **********
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 ) ;
} ) ;
2012-04-05 20:49:27 +00:00
/ *
2012-03-20 23:43:21 +00:00
legend . dispatch . on ( 'legendMouseover' , function ( d , i ) {
d . hover = true ;
selection . transition ( ) . call ( chart )
} ) ;
legend . dispatch . on ( 'legendMouseout' , function ( d , i ) {
d . hover = false ;
selection . transition ( ) . call ( chart )
} ) ;
2012-04-05 20:49:27 +00:00
* /
2012-03-20 23:43:21 +00:00
focus . dispatch . on ( 'pointMouseover.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
} ) ;
} ) ;
focus . dispatch . on ( 'pointMouseout.tooltip' , function ( e ) {
dispatch . tooltipHide ( e ) ;
} ) ;
2012-03-24 00:59:25 +00:00
function onBrush ( ) {
updateFocus ( ) ;
focusLines . call ( focus )
wrap . select ( '.x.axis' ) . call ( xAxis ) ;
wrap . select ( '.y.axis' ) . call ( yAxis ) ;
}
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
function updateFocus ( ) {
var yDomain = brush . empty ( ) ? y2 . domain ( ) : d3 . extent ( d3 . merge ( seriesData ) . filter ( function ( d ) {
return getX ( d ) >= brush . extent ( ) [ 0 ] && getX ( d ) <= brush . extent ( ) [ 1 ] ;
} ) , getY ) ; //This doesn't account for the 1 point before and the 1 point after the domain. Would fix, but likely need to change entire methodology here
2012-03-22 23:26:02 +00:00
2012-03-24 00:59:25 +00:00
if ( typeof yDomain [ 0 ] == 'undefined' ) yDomain = y2 . domain ( ) ; //incase the brush doesn't cover a single point
2012-03-22 23:26:02 +00:00
2012-03-24 00:59:25 +00:00
x . domain ( brush . empty ( ) ? x2 . domain ( ) : brush . extent ( ) ) ;
y . domain ( yDomain ) ;
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
//TODO: Rethink this... performance is horrible, likely need to cut off focus data to within the range
// If I limit the data for focusLines would want to include 1 point before and after the extent,
// Need to figure out an optimized way to accomplish this.
// ***One concern is to try not to make the assumption that all lines are of the same length, and
// points with the same index have the same x value (while this is true in our test cases, may
// no always be)
focus . xDomain ( x . domain ( ) ) ;
focus . yDomain ( y . domain ( ) ) ;
}
2012-03-20 23:43:21 +00:00
2012-03-24 00:59:25 +00:00
} ) ;
return chart ;
2012-03-20 23:43:21 +00:00
}
2012-03-24 00:59:25 +00:00
// ********** FUNCTIONS **********
2012-03-20 23:43:21 +00:00
// ********** PUBLIC ACCESSORS **********
chart . dispatch = dispatch ;
chart . x = function ( _ ) {
if ( ! arguments . length ) return getX ;
getX = _ ;
focus . x ( _ ) ;
context . x ( _ ) ;
return chart ;
} ;
chart . y = function ( _ ) {
if ( ! arguments . length ) return getY ;
getY = _ ;
focus . y ( _ ) ;
context . y ( _ ) ;
return chart ;
} ;
chart . margin = function ( _ ) {
if ( ! arguments . length ) return margin ;
margin = _ ;
return chart ;
} ;
chart . width = function ( _ ) {
if ( ! arguments . length ) return width ;
width = _ ;
return chart ;
} ;
chart . height = function ( _ ) {
if ( ! arguments . length ) return height ;
height = _ ;
height1 = _ - height2 ;
return chart ;
} ;
chart . contextHeight = function ( _ ) {
if ( ! arguments . length ) return height2 ;
height2 = _ ;
height1 = height - _ ;
return chart ;
} ;
chart . dotRadius = function ( _ ) {
if ( ! arguments . length ) return dotRadius ;
dotRadius = d3 . functor ( _ ) ;
focus . dotRadius = _ ;
return chart ;
} ;
chart . id = function ( _ ) {
if ( ! arguments . length ) return id ;
id = _ ;
return chart ;
} ;
// Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
chart . xTickFormat = function ( _ ) {
if ( ! arguments . length ) return x . tickFormat ( ) ;
xAxis . tickFormat ( _ ) ;
xAxis2 . tickFormat ( _ ) ;
return chart ;
} ;
chart . yTickFormat = function ( _ ) {
if ( ! arguments . length ) return y . tickFormat ( ) ;
yAxis . tickFormat ( _ ) ;
yAxis2 . tickFormat ( _ ) ;
return chart ;
} ;
//TODO: allow for both focus and context axes to be linked
chart . xAxis = xAxis ;
chart . yAxis = yAxis ;
return chart ;
}