2018-01-16 06:58:15 +01:00
/* nvd3 version 1.8.2-dev (https://github.com/novus/nvd3) 2016-02-08 */
( function ( ) {
// set up main nv object
var nv = { } ;
// the major global objects under the nv namespace
nv . dev = false ; //set false when in production
nv . tooltip = nv . tooltip || { } ; // For the tooltip system
nv . utils = nv . utils || { } ; // Utility subsystem
nv . models = nv . models || { } ; //stores all the possible models/components
nv . charts = { } ; //stores all the ready to use charts
nv . logs = { } ; //stores some statistics and potential error messages
nv . dom = { } ; //DOM manipulation functions
// Node/CommonJS - require D3
if ( typeof ( module ) !== 'undefined' && typeof ( exports ) !== 'undefined' && typeof ( d3 ) == 'undefined' ) {
d3 = require ( 'd3' ) ;
}
nv . dispatch = d3 . dispatch ( 'render_start' , 'render_end' ) ;
// Function bind polyfill
// Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
// https://github.com/ariya/phantomjs/issues/10522
// http://kangax.github.io/compat-table/es5/#Function.prototype.bind
// phantomJS is used for running the test suite
if ( ! Function . prototype . bind ) {
Function . prototype . bind = function ( oThis ) {
if ( typeof this !== "function" ) {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError ( "Function.prototype.bind - what is trying to be bound is not callable" ) ;
}
var aArgs = Array . prototype . slice . call ( arguments , 1 ) ,
fToBind = this ,
fNOP = function ( ) { } ,
fBound = function ( ) {
return fToBind . apply ( this instanceof fNOP && oThis
? this
: oThis ,
aArgs . concat ( Array . prototype . slice . call ( arguments ) ) ) ;
} ;
fNOP . prototype = this . prototype ;
fBound . prototype = new fNOP ( ) ;
return fBound ;
} ;
}
// Development render timers - disabled if dev = false
if ( nv . dev ) {
nv . dispatch . on ( 'render_start' , function ( e ) {
nv . logs . startTime = + new Date ( ) ;
} ) ;
nv . dispatch . on ( 'render_end' , function ( e ) {
nv . logs . endTime = + new Date ( ) ;
nv . logs . totalTime = nv . logs . endTime - nv . logs . startTime ;
nv . log ( 'total' , nv . logs . totalTime ) ; // used for development, to keep track of graph generation times
} ) ;
}
// Logs all arguments, and returns the last so you can test things in place
// Note: in IE8 console.log is an object not a function, and if modernizr is used
// then calling Function.prototype.bind with with anything other than a function
// causes a TypeError to be thrown.
nv . log = function ( ) {
if ( nv . dev && window . console && console . log && console . log . apply )
console . log . apply ( console , arguments ) ;
else if ( nv . dev && window . console && typeof console . log == "function" && Function . prototype . bind ) {
var log = Function . prototype . bind . call ( console . log , console ) ;
log . apply ( console , arguments ) ;
}
return arguments [ arguments . length - 1 ] ;
} ;
// print console warning, should be used by deprecated functions
nv . deprecated = function ( name , info ) {
if ( console && console . warn ) {
console . warn ( 'nvd3 warning: `' + name + '` has been deprecated. ' , info || '' ) ;
}
} ;
// The nv.render function is used to queue up chart rendering
// in non-blocking async functions.
// When all queued charts are done rendering, nv.dispatch.render_end is invoked.
nv . render = function render ( step ) {
// number of graphs to generate in each timeout loop
step = step || 1 ;
nv . render . active = true ;
nv . dispatch . render _start ( ) ;
var renderLoop = function ( ) {
var chart , graph ;
for ( var i = 0 ; i < step && ( graph = nv . render . queue [ i ] ) ; i ++ ) {
chart = graph . generate ( ) ;
if ( typeof graph . callback == typeof ( Function ) ) graph . callback ( chart ) ;
}
nv . render . queue . splice ( 0 , i ) ;
if ( nv . render . queue . length ) {
setTimeout ( renderLoop ) ;
}
else {
nv . dispatch . render _end ( ) ;
nv . render . active = false ;
}
} ;
setTimeout ( renderLoop ) ;
} ;
nv . render . active = false ;
nv . render . queue = [ ] ;
/ *
Adds a chart to the async rendering queue . This method can take arguments in two forms :
nv . addGraph ( {
generate : < Function >
callback : < Function >
} )
or
nv . addGraph ( < generate Function > , < callback Function > )
The generate function should contain code that creates the NVD3 model , sets options
on it , adds data to an SVG element , and invokes the chart model . The generate function
should return the chart model . See examples / lineChart . html for a usage example .
The callback function is optional , and it is called when the generate function completes .
* /
nv . addGraph = function ( obj ) {
if ( typeof arguments [ 0 ] === typeof ( Function ) ) {
obj = { generate : arguments [ 0 ] , callback : arguments [ 1 ] } ;
}
nv . render . queue . push ( obj ) ;
if ( ! nv . render . active ) {
nv . render ( ) ;
}
} ;
// Node/CommonJS exports
if ( typeof ( module ) !== 'undefined' && typeof ( exports ) !== 'undefined' ) {
module . exports = nv ;
}
if ( typeof ( window ) !== 'undefined' ) {
window . nv = nv ;
}
2018-04-05 10:25:40 +02:00
/ * F a c a d e f o r q u e u e i n g D O M w r i t e o p e r a t i o n s
* with Fastdom ( https : //github.com/wilsonpage/fastdom)
* if available .
* This could easily be extended to support alternate
* implementations in the future .
* /
nv . dom . write = function ( callback ) {
if ( window . fastdom !== undefined ) {
return fastdom . write ( callback ) ;
}
return callback ( ) ;
} ;
/ * F a c a d e f o r q u e u e i n g D O M r e a d o p e r a t i o n s
* with Fastdom ( https : //github.com/wilsonpage/fastdom)
* if available .
* This could easily be extended to support alternate
* implementations in the future .
* /
nv . dom . read = function ( callback ) {
if ( window . fastdom !== undefined ) {
return fastdom . read ( callback ) ;
}
return callback ( ) ;
2018-01-16 06:58:15 +01:00
} ; / * U t i l i t y c l a s s t o h a n d l e c r e a t i o n o f a n i n t e r a c t i v e l a y e r .
This places a rectangle on top of the chart . When you mouse move over it , it sends a dispatch
containing the X - coordinate . It can also render a vertical line where the mouse is located .
dispatch . elementMousemove is the important event to latch onto . It is fired whenever the mouse moves over
the rectangle . The dispatch is given one object which contains the mouseX / Y location .
It also has 'pointXValue' , which is the conversion of mouseX to the x - axis scale .
* /
nv . interactiveGuideline = function ( ) {
"use strict" ;
var margin = { left : 0 , top : 0 } //Pass the chart's top and left magins. Used to calculate the mouseX/Y.
, width = null
, height = null
, xScale = d3 . scale . linear ( )
, dispatch = d3 . dispatch ( 'elementMousemove' , 'elementMouseout' , 'elementClick' , 'elementDblclick' , 'elementMouseDown' , 'elementMouseUp' )
, showGuideLine = true
, svgContainer = null // Must pass the chart's svg, we'll use its mousemove event.
, tooltip = nv . models . tooltip ( )
, isMSIE = "ActiveXObject" in window // Checkt if IE by looking for activeX.
;
tooltip
. duration ( 0 )
. hideDelay ( 0 )
. hidden ( false ) ;
function layer ( selection ) {
selection . each ( function ( data ) {
var container = d3 . select ( this ) ;
var availableWidth = ( width || 960 ) , availableHeight = ( height || 400 ) ;
var wrap = container . selectAll ( "g.nv-wrap.nv-interactiveLineLayer" )
. data ( [ data ] ) ;
var wrapEnter = wrap . enter ( )
. append ( "g" ) . attr ( "class" , " nv-wrap nv-interactiveLineLayer" ) ;
wrapEnter . append ( "g" ) . attr ( "class" , "nv-interactiveGuideLine" ) ;
if ( ! svgContainer ) {
return ;
}
function mouseHandler ( ) {
var d3mouse = d3 . mouse ( this ) ;
var mouseX = d3mouse [ 0 ] ;
var mouseY = d3mouse [ 1 ] ;
var subtractMargin = true ;
var mouseOutAnyReason = false ;
if ( isMSIE ) {
/ *
D3 . js ( or maybe SVG . getScreenCTM ) has a nasty bug in Internet Explorer 10.
d3 . mouse ( ) returns incorrect X , Y mouse coordinates when mouse moving
over a rect in IE 10.
However , d3 . event . offsetX / Y also returns the mouse coordinates
relative to the triggering < rect > . So we use offsetX / Y on IE .
* /
mouseX = d3 . event . offsetX ;
mouseY = d3 . event . offsetY ;
/ *
On IE , if you attach a mouse event listener to the < svg > container ,
it will actually trigger it for all the child elements ( like < path > , < circle > , etc ) .
When this happens on IE , the offsetX / Y is set to where ever the child element
is located .
As a r esult , we do NOT need to subtract margins to figure out the mouse X / Y
position under this scenario . Removing the line below * will * cause
the interactive layer to not work right on IE .
* /
if ( d3 . event . target . tagName !== "svg" ) {
subtractMargin = false ;
}
if ( d3 . event . target . className . baseVal . match ( "nv-legend" ) ) {
mouseOutAnyReason = true ;
}
}
if ( subtractMargin ) {
mouseX -= margin . left ;
mouseY -= margin . top ;
}
/ * I f m o u s e X / Y i s o u t s i d e o f t h e c h a r t ' s b o u n d s ,
trigger a mouseOut event .
* /
if ( d3 . event . type === 'mouseout'
|| mouseX < 0 || mouseY < 0
|| mouseX > availableWidth || mouseY > availableHeight
|| ( d3 . event . relatedTarget && d3 . event . relatedTarget . ownerSVGElement === undefined )
|| mouseOutAnyReason
) {
if ( isMSIE ) {
if ( d3 . event . relatedTarget
&& d3 . event . relatedTarget . ownerSVGElement === undefined
&& ( d3 . event . relatedTarget . className === undefined
|| d3 . event . relatedTarget . className . match ( tooltip . nvPointerEventsClass ) ) ) {
return ;
}
}
dispatch . elementMouseout ( {
mouseX : mouseX ,
mouseY : mouseY
} ) ;
layer . renderGuideLine ( null ) ; //hide the guideline
tooltip . hidden ( true ) ;
return ;
} else {
tooltip . hidden ( false ) ;
}
var scaleIsOrdinal = typeof xScale . rangeBands === 'function' ;
var pointXValue = undefined ;
// Ordinal scale has no invert method
if ( scaleIsOrdinal ) {
var elementIndex = d3 . bisect ( xScale . range ( ) , mouseX ) - 1 ;
// Check if mouseX is in the range band
if ( xScale . range ( ) [ elementIndex ] + xScale . rangeBand ( ) >= mouseX ) {
pointXValue = xScale . domain ( ) [ d3 . bisect ( xScale . range ( ) , mouseX ) - 1 ] ;
}
else {
dispatch . elementMouseout ( {
mouseX : mouseX ,
mouseY : mouseY
} ) ;
layer . renderGuideLine ( null ) ; //hide the guideline
tooltip . hidden ( true ) ;
return ;
}
}
else {
pointXValue = xScale . invert ( mouseX ) ;
}
dispatch . elementMousemove ( {
mouseX : mouseX ,
mouseY : mouseY ,
pointXValue : pointXValue
} ) ;
//If user double clicks the layer, fire a elementDblclick
if ( d3 . event . type === "dblclick" ) {
dispatch . elementDblclick ( {
mouseX : mouseX ,
mouseY : mouseY ,
pointXValue : pointXValue
} ) ;
}
// if user single clicks the layer, fire elementClick
if ( d3 . event . type === 'click' ) {
dispatch . elementClick ( {
mouseX : mouseX ,
mouseY : mouseY ,
pointXValue : pointXValue
} ) ;
}
// if user presses mouse down the layer, fire elementMouseDown
if ( d3 . event . type === 'mousedown' ) {
dispatch . elementMouseDown ( {
mouseX : mouseX ,
mouseY : mouseY ,
pointXValue : pointXValue
} ) ;
}
// if user presses mouse down the layer, fire elementMouseUp
if ( d3 . event . type === 'mouseup' ) {
dispatch . elementMouseUp ( {
mouseX : mouseX ,
mouseY : mouseY ,
pointXValue : pointXValue
} ) ;
}
}
svgContainer
. on ( "touchmove" , mouseHandler )
. on ( "mousemove" , mouseHandler , true )
. on ( "mouseout" , mouseHandler , true )
. on ( "mousedown" , mouseHandler , true )
. on ( "mouseup" , mouseHandler , true )
. on ( "dblclick" , mouseHandler )
. on ( "click" , mouseHandler )
;
layer . guideLine = null ;
//Draws a vertical guideline at the given X postion.
layer . renderGuideLine = function ( x ) {
if ( ! showGuideLine ) return ;
if ( layer . guideLine && layer . guideLine . attr ( "x1" ) === x ) return ;
nv . dom . write ( function ( ) {
var line = wrap . select ( ".nv-interactiveGuideLine" )
. selectAll ( "line" )
. data ( ( x != null ) ? [ nv . utils . NaNtoZero ( x ) ] : [ ] , String ) ;
line . enter ( )
. append ( "line" )
. attr ( "class" , "nv-guideline" )
. attr ( "x1" , function ( d ) { return d ; } )
. attr ( "x2" , function ( d ) { return d ; } )
. attr ( "y1" , availableHeight )
. attr ( "y2" , 0 ) ;
line . exit ( ) . remove ( ) ;
} ) ;
}
} ) ;
}
layer . dispatch = dispatch ;
layer . tooltip = tooltip ;
layer . margin = function ( _ ) {
if ( ! arguments . length ) return margin ;
margin . top = typeof _ . top != 'undefined' ? _ . top : margin . top ;
margin . left = typeof _ . left != 'undefined' ? _ . left : margin . left ;
return layer ;
} ;
layer . width = function ( _ ) {
if ( ! arguments . length ) return width ;
width = _ ;
return layer ;
} ;
layer . height = function ( _ ) {
if ( ! arguments . length ) return height ;
height = _ ;
return layer ;
} ;
layer . xScale = function ( _ ) {
if ( ! arguments . length ) return xScale ;
xScale = _ ;
return layer ;
} ;
layer . showGuideLine = function ( _ ) {
if ( ! arguments . length ) return showGuideLine ;
showGuideLine = _ ;
return layer ;
} ;
layer . svgContainer = function ( _ ) {
if ( ! arguments . length ) return svgContainer ;
svgContainer = _ ;
return layer ;
} ;
return layer ;
} ;
/ * U t i l i t y c l a s s t h a t u s e s d 3 . b i s e c t t o f i n d t h e i n d e x i n a g i v e n a r r a y , w h e r e a s e a r c h v a l u e c a n b e i n s e r t e d .
This is different from normal bisectLeft ; this function finds the nearest index to insert the search value .
For instance , lets say your array is [ 1 , 2 , 3 , 5 , 10 , 30 ] , and you search for 28.
Normal d3 . bisectLeft will return 4 , because 28 is inserted after the number 10. But interactiveBisect will return 5
because 28 is closer to 30 than 10.
Unit tests can be found in : interactiveBisectTest . html
Has the following known issues :
* Will not work if the data points move backwards ( ie , 10 , 9 , 8 , 7 , etc ) or if the data points are in random order .
* Won ' t work if there are duplicate x coordinate values .
* /
nv . interactiveBisect = function ( values , searchVal , xAccessor ) {
"use strict" ;
if ( ! ( values instanceof Array ) ) {
return null ;
}
var _xAccessor ;
if ( typeof xAccessor !== 'function' ) {
_xAccessor = function ( d ) {
return d . x ;
}
} else {
_xAccessor = xAccessor ;
}
var _cmp = function ( d , v ) {
// Accessors are no longer passed the index of the element along with
// the element itself when invoked by d3.bisector.
//
// Starting at D3 v3.4.4, d3.bisector() started inspecting the
// function passed to determine if it should consider it an accessor
// or a comparator. This meant that accessors that take two arguments
// (expecting an index as the second parameter) are treated as
// comparators where the second argument is the search value against
// which the first argument is compared.
return _xAccessor ( d ) - v ;
} ;
var bisect = d3 . bisector ( _cmp ) . left ;
var index = d3 . max ( [ 0 , bisect ( values , searchVal ) - 1 ] ) ;
var currentValue = _xAccessor ( values [ index ] ) ;
if ( typeof currentValue === 'undefined' ) {
currentValue = index ;
}
if ( currentValue === searchVal ) {
return index ; //found exact match
}
var nextIndex = d3 . min ( [ index + 1 , values . length - 1 ] ) ;
var nextValue = _xAccessor ( values [ nextIndex ] ) ;
if ( typeof nextValue === 'undefined' ) {
nextValue = nextIndex ;
}
if ( Math . abs ( nextValue - searchVal ) >= Math . abs ( currentValue - searchVal ) ) {
return index ;
} else {
return nextIndex
}
} ;
/ *
Returns the index in the array "values" that is closest to searchVal .
Only returns an index if searchVal is within some "threshold" .
Otherwise , returns null .
* /
nv . nearestValueIndex = function ( values , searchVal , threshold ) {
"use strict" ;
var yDistMax = Infinity , indexToHighlight = null ;
values . forEach ( function ( d , i ) {
var delta = Math . abs ( searchVal - d ) ;
if ( d != null && delta <= yDistMax && delta < threshold ) {
yDistMax = delta ;
indexToHighlight = i ;
}
} ) ;
return indexToHighlight ;
} ;
/ * M o d e l w h i c h c a n b e i n s t a n t i a t e d t o h a n d l e t o o l t i p r e n d e r i n g .
Example usage :
var tip = nv . models . tooltip ( ) . gravity ( 'w' ) . distance ( 23 )
. data ( myDataObject ) ;
tip ( ) ; //just invoke the returned function to render tooltip.
* /
nv . models . tooltip = function ( ) {
"use strict" ;
/ *
Tooltip data . If data is given in the proper format , a consistent tooltip is generated .
Example Format of data :
{
key : "Date" ,
value : "August 2009" ,
series : [
{ key : "Series 1" , value : "Value 1" , color : "#000" } ,
{ key : "Series 2" , value : "Value 2" , color : "#00f" }
]
}
* /
var id = "nvtooltip-" + Math . floor ( Math . random ( ) * 100000 ) // Generates a unique id when you create a new tooltip() object.
, data = null
, gravity = 'w' // Can be 'n','s','e','w'. Determines how tooltip is positioned.
, distance = 25 // Distance to offset tooltip from the mouse location.
, snapDistance = 0 // Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
, classes = null // Attaches additional CSS classes to the tooltip DIV that is created.
, chartContainer = null // Parent dom element of the SVG that holds the chart.
, hidden = true // Start off hidden, toggle with hide/show functions below.
, hideDelay = 200 // Delay (in ms) before the tooltip hides after calling hide().
, tooltip = null // d3 select of the tooltip div.
, lastPosition = { left : null , top : null } // Last position the tooltip was in.
, enabled = true // True -> tooltips are rendered. False -> don't render tooltips.
, duration = 100 // Tooltip movement duration, in ms.
, headerEnabled = true // If is to show the tooltip header.
, nvPointerEventsClass = "nv-pointer-events-none" // CSS class to specify whether element should not have mouse events.
;
/ *
Function that returns the position ( relative to the viewport ) the tooltip should be placed in .
Should return : {
left : < leftPos > ,
top : < topPos >
}
* /
var position = function ( ) {
return {
left : d3 . event !== null ? d3 . event . clientX : 0 ,
top : d3 . event !== null ? d3 . event . clientY : 0
} ;
} ;
// Format function for the tooltip values column.
var valueFormatter = function ( d , i ) {
return d ;
} ;
// Format function for the tooltip header value.
var headerFormatter = function ( d ) {
return d ;
} ;
var keyFormatter = function ( d , i ) {
return d ;
} ;
// By default, the tooltip model renders a beautiful table inside a DIV.
// You can override this function if a custom tooltip is desired.
var contentGenerator = function ( d ) {
if ( d === null ) {
return '' ;
}
var table = d3 . select ( document . createElement ( "table" ) ) ;
if ( headerEnabled ) {
var theadEnter = table . selectAll ( "thead" )
. data ( [ d ] )
. enter ( ) . append ( "thead" ) ;
theadEnter . append ( "tr" )
. append ( "td" )
. attr ( "colspan" , 3 )
. append ( "strong" )
. classed ( "x-value" , true )
. html ( headerFormatter ( d . value ) ) ;
}
var tbodyEnter = table . selectAll ( "tbody" )
. data ( [ d ] )
. enter ( ) . append ( "tbody" ) ;
var trowEnter = tbodyEnter . selectAll ( "tr" )
. data ( function ( p ) { return p . series } )
. enter ( )
. append ( "tr" )
. classed ( "highlight" , function ( p ) { return p . highlight } ) ;
trowEnter . append ( "td" )
. classed ( "legend-color-guide" , true )
. append ( "div" )
. style ( "background-color" , function ( p ) { return p . color } ) ;
trowEnter . append ( "td" )
. classed ( "key" , true )
. classed ( "total" , function ( p ) { return ! ! p . total } )
. html ( function ( p , i ) { return keyFormatter ( p . key , i ) } ) ;
trowEnter . append ( "td" )
. classed ( "value" , true )
. html ( function ( p , i ) { return valueFormatter ( p . value , i ) } ) ;
trowEnter . selectAll ( "td" ) . each ( function ( p ) {
if ( p . highlight ) {
var opacityScale = d3 . scale . linear ( ) . domain ( [ 0 , 1 ] ) . range ( [ "#fff" , p . color ] ) ;
var opacity = 0.6 ;
d3 . select ( this )
. style ( "border-bottom-color" , opacityScale ( opacity ) )
. style ( "border-top-color" , opacityScale ( opacity ) )
;
}
} ) ;
var html = table . node ( ) . outerHTML ;
if ( d . footer !== undefined )
html += "<div class='footer'>" + d . footer + "</div>" ;
return html ;
} ;
var dataSeriesExists = function ( d ) {
if ( d && d . series ) {
if ( nv . utils . isArray ( d . series ) ) {
return true ;
}
// if object, it's okay just convert to array of the object
if ( nv . utils . isObject ( d . series ) ) {
d . series = [ d . series ] ;
return true ;
}
}
return false ;
} ;
// Calculates the gravity offset of the tooltip. Parameter is position of tooltip
// relative to the viewport.
var calcGravityOffset = function ( pos ) {
var height = tooltip . node ( ) . offsetHeight ,
width = tooltip . node ( ) . offsetWidth ,
clientWidth = document . documentElement . clientWidth , // Don't want scrollbars.
clientHeight = document . documentElement . clientHeight , // Don't want scrollbars.
left , top , tmp ;
// calculate position based on gravity
switch ( gravity ) {
case 'e' :
left = - width - distance ;
top = - ( height / 2 ) ;
if ( pos . left + left < 0 ) left = distance ;
if ( ( tmp = pos . top + top ) < 0 ) top -= tmp ;
if ( ( tmp = pos . top + top + height ) > clientHeight ) top -= tmp - clientHeight ;
break ;
case 'w' :
left = distance ;
top = - ( height / 2 ) ;
if ( pos . left + left + width > clientWidth ) left = - width - distance ;
if ( ( tmp = pos . top + top ) < 0 ) top -= tmp ;
if ( ( tmp = pos . top + top + height ) > clientHeight ) top -= tmp - clientHeight ;
break ;
case 'n' :
left = - ( width / 2 ) - 5 ; // - 5 is an approximation of the mouse's height.
top = distance ;
if ( pos . top + top + height > clientHeight ) top = - height - distance ;
if ( ( tmp = pos . left + left ) < 0 ) left -= tmp ;
if ( ( tmp = pos . left + left + width ) > clientWidth ) left -= tmp - clientWidth ;
break ;
case 's' :
left = - ( width / 2 ) ;
top = - height - distance ;
if ( pos . top + top < 0 ) top = distance ;
if ( ( tmp = pos . left + left ) < 0 ) left -= tmp ;
if ( ( tmp = pos . left + left + width ) > clientWidth ) left -= tmp - clientWidth ;
break ;
case 'center' :
left = - ( width / 2 ) ;
top = - ( height / 2 ) ;
break ;
default :
left = 0 ;
top = 0 ;
break ;
}
return { 'left' : left , 'top' : top } ;
} ;
/ *
Positions the tooltip in the correct place , as given by the position ( ) function .
* /
var positionTooltip = function ( ) {
nv . dom . read ( function ( ) {
var pos = position ( ) ,
gravityOffset = calcGravityOffset ( pos ) ,
left = pos . left + gravityOffset . left ,
top = pos . top + gravityOffset . top ;
// delay hiding a bit to avoid flickering
if ( hidden ) {
tooltip
. interrupt ( )
. transition ( )
. delay ( hideDelay )
. duration ( 0 )
. style ( 'opacity' , 0 ) ;
} else {
// using tooltip.style('transform') returns values un-usable for tween
var old _translate = 'translate(' + lastPosition . left + 'px, ' + lastPosition . top + 'px)' ;
var new _translate = 'translate(' + left + 'px, ' + top + 'px)' ;
var translateInterpolator = d3 . interpolateString ( old _translate , new _translate ) ;
var is _hidden = tooltip . style ( 'opacity' ) < 0.1 ;
tooltip
. interrupt ( ) // cancel running transitions
. transition ( )
. duration ( is _hidden ? 0 : duration )
// using tween since some versions of d3 can't auto-tween a translate on a div
. styleTween ( 'transform' , function ( d ) {
return translateInterpolator ;
} , 'important' )
// Safari has its own `-webkit-transform` and does not support `transform`
. styleTween ( '-webkit-transform' , function ( d ) {
return translateInterpolator ;
} )
. style ( '-ms-transform' , new _translate )
. style ( 'opacity' , 1 ) ;
}
lastPosition . left = left ;
lastPosition . top = top ;
} ) ;
} ;
// Creates new tooltip container, or uses existing one on DOM.
function initTooltip ( ) {
if ( ! tooltip || ! tooltip . node ( ) ) {
var container = chartContainer ? chartContainer : document . body ;
// Create new tooltip div if it doesn't exist on DOM.
var data = [ 1 ] ;
tooltip = d3 . select ( container ) . selectAll ( '.nvtooltip' ) . data ( data ) ;
tooltip . enter ( ) . append ( 'div' )
. attr ( "class" , "nvtooltip " + ( classes ? classes : "xy-tooltip" ) )
. attr ( "id" , id )
. style ( "top" , 0 ) . style ( "left" , 0 )
. style ( 'opacity' , 0 )
. style ( 'position' , 'fixed' )
. selectAll ( "div, table, td, tr" ) . classed ( nvPointerEventsClass , true )
. classed ( nvPointerEventsClass , true ) ;
tooltip . exit ( ) . remove ( )
}
}
// Draw the tooltip onto the DOM.
function nvtooltip ( ) {
if ( ! enabled ) return ;
if ( ! dataSeriesExists ( data ) ) return ;
nv . dom . write ( function ( ) {
initTooltip ( ) ;
// Generate data and set it into tooltip.
// Bonus - If you override contentGenerator and return falsey you can use something like
// React or Knockout to bind the data for your tooltip.
var newContent = contentGenerator ( data ) ;
if ( newContent ) {
tooltip . node ( ) . innerHTML = newContent ;
}
positionTooltip ( ) ;
} ) ;
return nvtooltip ;
}
nvtooltip . nvPointerEventsClass = nvPointerEventsClass ;
nvtooltip . options = nv . utils . optionsFunc . bind ( nvtooltip ) ;
nvtooltip . _options = Object . create ( { } , {
// simple read/write options
duration : { get : function ( ) { return duration ; } , set : function ( _ ) { duration = _ ; } } ,
gravity : { get : function ( ) { return gravity ; } , set : function ( _ ) { gravity = _ ; } } ,
distance : { get : function ( ) { return distance ; } , set : function ( _ ) { distance = _ ; } } ,
snapDistance : { get : function ( ) { return snapDistance ; } , set : function ( _ ) { snapDistance = _ ; } } ,
classes : { get : function ( ) { return classes ; } , set : function ( _ ) { classes = _ ; } } ,
chartContainer : { get : function ( ) { return chartContainer ; } , set : function ( _ ) { chartContainer = _ ; } } ,
enabled : { get : function ( ) { return enabled ; } , set : function ( _ ) { enabled = _ ; } } ,
hideDelay : { get : function ( ) { return hideDelay ; } , set : function ( _ ) { hideDelay = _ ; } } ,
contentGenerator : { get : function ( ) { return contentGenerator ; } , set : function ( _ ) { contentGenerator = _ ; } } ,
valueFormatter : { get : function ( ) { return valueFormatter ; } , set : function ( _ ) { valueFormatter = _ ; } } ,
headerFormatter : { get : function ( ) { return headerFormatter ; } , set : function ( _ ) { headerFormatter = _ ; } } ,
keyFormatter : { get : function ( ) { return keyFormatter ; } , set : function ( _ ) { keyFormatter = _ ; } } ,
headerEnabled : { get : function ( ) { return headerEnabled ; } , set : function ( _ ) { headerEnabled = _ ; } } ,
position : { get : function ( ) { return position ; } , set : function ( _ ) { position = _ ; } } ,
// Deprecated options
fixedTop : { get : function ( ) { return null ; } , set : function ( _ ) {
// deprecated after 1.8.1
nv . deprecated ( 'fixedTop' , 'feature removed after 1.8.1' ) ;
} } ,
offset : { get : function ( ) { return { left : 0 , top : 0 } ; } , set : function ( _ ) {
// deprecated after 1.8.1
nv . deprecated ( 'offset' , 'use chart.tooltip.distance() instead' ) ;
} } ,
// options with extra logic
hidden : { get : function ( ) { return hidden ; } , set : function ( _ ) {
if ( hidden != _ ) {
hidden = ! ! _ ;
nvtooltip ( ) ;
}
} } ,
data : { get : function ( ) { return data ; } , set : function ( _ ) {
// if showing a single data point, adjust data format with that
if ( _ . point ) {
_ . value = _ . point . x ;
_ . series = _ . series || { } ;
_ . series . value = _ . point . y ;
_ . series . color = _ . point . color || _ . series . color ;
}
data = _ ;
} } ,
// read only properties
node : { get : function ( ) { return tooltip . node ( ) ; } , set : function ( _ ) { } } ,
id : { get : function ( ) { return id ; } , set : function ( _ ) { } }
} ) ;
nv . utils . initOptions ( nvtooltip ) ;
return nvtooltip ;
} ;
/ *
Gets the browser window size
Returns object with height and width properties
* /
nv . utils . windowSize = function ( ) {
// Sane defaults
var size = { width : 640 , height : 480 } ;
// Most recent browsers use
if ( window . innerWidth && window . innerHeight ) {
size . width = window . innerWidth ;
size . height = window . innerHeight ;
return ( size ) ;
}
// IE can use depending on mode it is in
if ( document . compatMode == 'CSS1Compat' &&
document . documentElement &&
document . documentElement . offsetWidth ) {
size . width = document . documentElement . offsetWidth ;
size . height = document . documentElement . offsetHeight ;
return ( size ) ;
}
// Earlier IE uses Doc.body
if ( document . body && document . body . offsetWidth ) {
size . width = document . body . offsetWidth ;
size . height = document . body . offsetHeight ;
return ( size ) ;
}
return ( size ) ;
} ;
/ * h a n d l e d u m b b r o w s e r q u i r k s . . . i s i n s t a n c e b r e a k s i f y o u u s e f r a m e s
typeof returns 'object' for null , NaN is a number , etc .
* /
nv . utils . isArray = Array . isArray ;
nv . utils . isObject = function ( a ) {
return a !== null && typeof a === 'object' ;
} ;
nv . utils . isFunction = function ( a ) {
return typeof a === 'function' ;
} ;
nv . utils . isDate = function ( a ) {
return toString . call ( a ) === '[object Date]' ;
} ;
nv . utils . isNumber = function ( a ) {
return ! isNaN ( a ) && typeof a === 'number' ;
} ;
/ *
Binds callback function to run when window is resized
* /
nv . utils . windowResize = function ( handler ) {
if ( window . addEventListener ) {
window . addEventListener ( 'resize' , handler ) ;
} else {
nv . log ( "ERROR: Failed to bind to window.resize with: " , handler ) ;
}
// return object with clear function to remove the single added callback.
return {
callback : handler ,
clear : function ( ) {
window . removeEventListener ( 'resize' , handler ) ;
}
}
} ;
/ *
Backwards compatible way to implement more d3 - like coloring of graphs .
Can take in nothing , an array , or a function / s c a l e
To use a normal scale , get the range and pass that because we must be able
to take two arguments and use the index to keep backward compatibility
* /
nv . utils . getColor = function ( color ) {
//if you pass in nothing, get default colors back
if ( color === undefined ) {
return nv . utils . defaultColor ( ) ;
//if passed an array, turn it into a color scale
} else if ( nv . utils . isArray ( color ) ) {
var color _scale = d3 . scale . ordinal ( ) . range ( color ) ;
return function ( d , i ) {
var key = i === undefined ? d : i ;
return d . color || color _scale ( key ) ;
} ;
//if passed a function or scale, return it, or whatever it may be
//external libs, such as angularjs-nvd3-directives use this
} else {
//can't really help it if someone passes rubbish as color
return color ;
}
} ;
/ *
Default color chooser uses a color scale of 20 colors from D3
https : //github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
* /
nv . utils . defaultColor = function ( ) {
// get range of the scale so we'll turn it into our own function.
return nv . utils . getColor ( d3 . scale . category20 ( ) . range ( ) ) ;
} ;
/ *
Returns a color function that takes the result of 'getKey' for each series and
looks for a corresponding color from the dictionary
* /
nv . utils . customTheme = function ( dictionary , getKey , defaultColors ) {
// use default series.key if getKey is undefined
getKey = getKey || function ( series ) { return series . key } ;
defaultColors = defaultColors || d3 . scale . category20 ( ) . range ( ) ;
// start at end of default color list and walk back to index 0
var defIndex = defaultColors . length ;
return function ( series , index ) {
var key = getKey ( series ) ;
if ( nv . utils . isFunction ( dictionary [ key ] ) ) {
return dictionary [ key ] ( ) ;
} else if ( dictionary [ key ] !== undefined ) {
return dictionary [ key ] ;
} else {
// no match in dictionary, use a default color
if ( ! defIndex ) {
// used all the default colors, start over
defIndex = defaultColors . length ;
}
defIndex = defIndex - 1 ;
return defaultColors [ defIndex ] ;
}
} ;
} ;
/ *
From the PJAX example on d3js . org , while this is not really directly needed
it ' s a very cool method for doing pjax , I may expand upon it a little bit ,
open to suggestions on anything that may be useful
* /
nv . utils . pjax = function ( links , content ) {
var load = function ( href ) {
d3 . html ( href , function ( fragment ) {
var target = d3 . select ( content ) . node ( ) ;
target . parentNode . replaceChild (
d3 . select ( fragment ) . select ( content ) . node ( ) ,
target ) ;
nv . utils . pjax ( links , content ) ;
} ) ;
} ;
d3 . selectAll ( links ) . on ( "click" , function ( ) {
history . pushState ( this . href , this . textContent , this . href ) ;
load ( this . href ) ;
d3 . event . preventDefault ( ) ;
} ) ;
d3 . select ( window ) . on ( "popstate" , function ( ) {
if ( d3 . event . state ) {
load ( d3 . event . state ) ;
}
} ) ;
} ;
/ *
For when we want to approximate the width in pixels for an SVG : text element .
Most common instance is when the element is in a display : none ; container .
Forumla is : text . length * font - size * constant _factor
* /
nv . utils . calcApproxTextWidth = function ( svgTextElem ) {
if ( nv . utils . isFunction ( svgTextElem . style ) && nv . utils . isFunction ( svgTextElem . text ) ) {
var fontSize = parseInt ( svgTextElem . style ( "font-size" ) . replace ( "px" , "" ) , 10 ) ;
var textLength = svgTextElem . text ( ) . length ;
return nv . utils . NaNtoZero ( textLength * fontSize * 0.5 ) ;
}
return 0 ;
} ;
/ *
Numbers that are undefined , null or NaN , convert them to zeros .
* /
nv . utils . NaNtoZero = function ( n ) {
if ( ! nv . utils . isNumber ( n )
|| isNaN ( n )
|| n === null
|| n === Infinity
|| n === - Infinity ) {
return 0 ;
}
return n ;
} ;
/ *
Add a way to watch for d3 transition ends to d3
* /
d3 . selection . prototype . watchTransition = function ( renderWatch ) {
var args = [ this ] . concat ( [ ] . slice . call ( arguments , 1 ) ) ;
return renderWatch . transition . apply ( renderWatch , args ) ;
} ;
/ *
Helper object to watch when d3 has rendered something
* /
nv . utils . renderWatch = function ( dispatch , duration ) {
if ( ! ( this instanceof nv . utils . renderWatch ) ) {
return new nv . utils . renderWatch ( dispatch , duration ) ;
}
var _duration = duration !== undefined ? duration : 250 ;
var renderStack = [ ] ;
var self = this ;
this . models = function ( models ) {
models = [ ] . slice . call ( arguments , 0 ) ;
models . forEach ( function ( model ) {
model . _ _rendered = false ;
( function ( m ) {
m . dispatch . on ( 'renderEnd' , function ( arg ) {
m . _ _rendered = true ;
self . renderEnd ( 'model' ) ;
} ) ;
} ) ( model ) ;
if ( renderStack . indexOf ( model ) < 0 ) {
renderStack . push ( model ) ;
}
} ) ;
return this ;
} ;
this . reset = function ( duration ) {
if ( duration !== undefined ) {
_duration = duration ;
}
renderStack = [ ] ;
} ;
this . transition = function ( selection , args , duration ) {
args = arguments . length > 1 ? [ ] . slice . call ( arguments , 1 ) : [ ] ;
if ( args . length > 1 ) {
duration = args . pop ( ) ;
} else {
duration = _duration !== undefined ? _duration : 250 ;
}
selection . _ _rendered = false ;
if ( renderStack . indexOf ( selection ) < 0 ) {
renderStack . push ( selection ) ;
}
if ( duration === 0 ) {
selection . _ _rendered = true ;
selection . delay = function ( ) { return this ; } ;
selection . duration = function ( ) { return this ; } ;
return selection ;
} else {
if ( selection . length === 0 ) {
selection . _ _rendered = true ;
} else if ( selection . every ( function ( d ) { return ! d . length ; } ) ) {
selection . _ _rendered = true ;
} else {
selection . _ _rendered = false ;
}
var n = 0 ;
return selection
. transition ( )
. duration ( duration )
. each ( function ( ) { ++ n ; } )
. each ( 'end' , function ( d , i ) {
if ( -- n === 0 ) {
selection . _ _rendered = true ;
self . renderEnd . apply ( this , args ) ;
}
} ) ;
}
} ;
this . renderEnd = function ( ) {
if ( renderStack . every ( function ( d ) { return d . _ _rendered ; } ) ) {
renderStack . forEach ( function ( d ) { d . _ _rendered = false ; } ) ;
dispatch . renderEnd . apply ( this , arguments ) ;
}
}
} ;
/ *
Takes multiple objects and combines them into the first one ( dst )
example : nv . utils . deepExtend ( { a : 1 } , { a : 2 , b : 3 } , { c : 4 } ) ;
gives : { a : 2 , b : 3 , c : 4 }
* /
nv . utils . deepExtend = function ( dst ) {
var sources = arguments . length > 1 ? [ ] . slice . call ( arguments , 1 ) : [ ] ;
sources . forEach ( function ( source ) {
for ( var key in source ) {
var isArray = nv . utils . isArray ( dst [ key ] ) ;
var isObject = nv . utils . isObject ( dst [ key ] ) ;
var srcObj = nv . utils . isObject ( source [ key ] ) ;
if ( isObject && ! isArray && srcObj ) {
nv . utils . deepExtend ( dst [ key ] , source [ key ] ) ;
} else {
dst [ key ] = source [ key ] ;
}
}
} ) ;
} ;
/ *
state utility object , used to track d3 states in the models
* /
nv . utils . state = function ( ) {
if ( ! ( this instanceof nv . utils . state ) ) {
return new nv . utils . state ( ) ;
}
var state = { } ;
var _self = this ;
var _setState = function ( ) { } ;
var _getState = function ( ) { return { } ; } ;
var init = null ;
var changed = null ;
this . dispatch = d3 . dispatch ( 'change' , 'set' ) ;
this . dispatch . on ( 'set' , function ( state ) {
_setState ( state , true ) ;
} ) ;
this . getter = function ( fn ) {
_getState = fn ;
return this ;
} ;
this . setter = function ( fn , callback ) {
if ( ! callback ) {
callback = function ( ) { } ;
}
_setState = function ( state , update ) {
fn ( state ) ;
if ( update ) {
callback ( ) ;
}
} ;
return this ;
} ;
this . init = function ( state ) {
init = init || { } ;
nv . utils . deepExtend ( init , state ) ;
} ;
var _set = function ( ) {
var settings = _getState ( ) ;
if ( JSON . stringify ( settings ) === JSON . stringify ( state ) ) {
return false ;
}
for ( var key in settings ) {
if ( state [ key ] === undefined ) {
state [ key ] = { } ;
}
state [ key ] = settings [ key ] ;
changed = true ;
}
return true ;
} ;
this . update = function ( ) {
if ( init ) {
_setState ( init , false ) ;
init = null ;
}
if ( _set . call ( this ) ) {
this . dispatch . change ( state ) ;
}
} ;
} ;
/ *
Snippet of code you can insert into each nv . models . * to give you the ability to
do things like :
chart . options ( {
showXAxis : true ,
tooltips : true
} ) ;
To enable in the chart :
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
* /
nv . utils . optionsFunc = function ( args ) {
if ( args ) {
d3 . map ( args ) . forEach ( ( function ( key , value ) {
if ( nv . utils . isFunction ( this [ key ] ) ) {
this [ key ] ( value ) ;
}
} ) . bind ( this ) ) ;
}
return this ;
} ;
/ *
numTicks : requested number of ticks
data : the chart data
returns the number of ticks to actually use on X axis , based on chart data
to avoid duplicate ticks with the same value
* /
nv . utils . calcTicksX = function ( numTicks , data ) {
// find max number of values from all data streams
var numValues = 1 ;
var i = 0 ;
for ( i ; i < data . length ; i += 1 ) {
var stream _len = data [ i ] && data [ i ] . values ? data [ i ] . values . length : 0 ;
numValues = stream _len > numValues ? stream _len : numValues ;
}
nv . log ( "Requested number of ticks: " , numTicks ) ;
nv . log ( "Calculated max values to be: " , numValues ) ;
// make sure we don't have more ticks than values to avoid duplicates
numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks ;
// make sure we have at least one tick
numTicks = numTicks < 1 ? 1 : numTicks ;
// make sure it's an integer
numTicks = Math . floor ( numTicks ) ;
nv . log ( "Calculating tick count as: " , numTicks ) ;
return numTicks ;
} ;
/ *
returns number of ticks to actually use on Y axis , based on chart data
* /
nv . utils . calcTicksY = function ( numTicks , data ) {
// currently uses the same logic but we can adjust here if needed later
return nv . utils . calcTicksX ( numTicks , data ) ;
} ;
/ *
Add a particular option from an options object onto chart
Options exposed on a chart are a getter / setter function that returns chart
on set to mimic typical d3 option chaining , e . g . svg . option1 ( 'a' ) . option2 ( 'b' ) ;
option objects should be generated via Object . create ( ) to provide
the option of manipulating data via get / set functions .
* /
nv . utils . initOption = function ( chart , name ) {
// if it's a call option, just call it directly, otherwise do get/set
if ( chart . _calls && chart . _calls [ name ] ) {
chart [ name ] = chart . _calls [ name ] ;
} else {
chart [ name ] = function ( _ ) {
if ( ! arguments . length ) return chart . _options [ name ] ;
chart . _overrides [ name ] = true ;
chart . _options [ name ] = _ ;
return chart ;
} ;
// calling the option as _option will ignore if set by option already
// so nvd3 can set options internally but the stop if set manually
chart [ '_' + name ] = function ( _ ) {
if ( ! arguments . length ) return chart . _options [ name ] ;
if ( ! chart . _overrides [ name ] ) {
chart . _options [ name ] = _ ;
}
return chart ;
}
}
} ;
/ *
Add all options in an options object to the chart
* /
nv . utils . initOptions = function ( chart ) {
chart . _overrides = chart . _overrides || { } ;
var ops = Object . getOwnPropertyNames ( chart . _options || { } ) ;
var calls = Object . getOwnPropertyNames ( chart . _calls || { } ) ;
ops = ops . concat ( calls ) ;
for ( var i in ops ) {
nv . utils . initOption ( chart , ops [ i ] ) ;
}
} ;
/ *
Inherit options from a D3 object
d3 . rebind makes calling the function on target actually call it on source
Also use _d3options so we can track what we inherit for documentation and chained inheritance
* /
nv . utils . inheritOptionsD3 = function ( target , d3 _source , oplist ) {
target . _d3options = oplist . concat ( target . _d3options || [ ] ) ;
oplist . unshift ( d3 _source ) ;
oplist . unshift ( target ) ;
d3 . rebind . apply ( this , oplist ) ;
} ;
/ *
Remove duplicates from an array
* /
nv . utils . arrayUnique = function ( a ) {
return a . sort ( ) . filter ( function ( item , pos ) {
return ! pos || item != a [ pos - 1 ] ;
} ) ;
} ;
/ *
Keeps a list of custom symbols to draw from in addition to d3 . svg . symbol
Necessary since d3 doesn ' t let you extend its list - _ -
Add new symbols by doing nv . utils . symbols . set ( 'name' , function ( size ) { ... } ) ;
* /
nv . utils . symbolMap = d3 . map ( ) ;
/ *
Replaces d3 . svg . symbol so that we can look both there and our own map
* /
nv . utils . symbol = function ( ) {
var type ,
size = 64 ;
function symbol ( d , i ) {
var t = type . call ( this , d , i ) ;
var s = size . call ( this , d , i ) ;
if ( d3 . svg . symbolTypes . indexOf ( t ) !== - 1 ) {
return d3 . svg . symbol ( ) . type ( t ) . size ( s ) ( ) ;
} else {
return nv . utils . symbolMap . get ( t ) ( s ) ;
}
}
symbol . type = function ( _ ) {
if ( ! arguments . length ) return type ;
type = d3 . functor ( _ ) ;
return symbol ;
} ;
symbol . size = function ( _ ) {
if ( ! arguments . length ) return size ;
size = d3 . functor ( _ ) ;
return symbol ;
} ;
return symbol ;
} ;
/ *
Inherit option getter / setter functions from source to target
d3 . rebind makes calling the function on target actually call it on source
Also track via _inherited and _d3options so we can track what we inherit
for documentation generation purposes and chained inheritance
* /
nv . utils . inheritOptions = function ( target , source ) {
// inherit all the things
var ops = Object . getOwnPropertyNames ( source . _options || { } ) ;
var calls = Object . getOwnPropertyNames ( source . _calls || { } ) ;
var inherited = source . _inherited || [ ] ;
var d3ops = source . _d3options || [ ] ;
var args = ops . concat ( calls ) . concat ( inherited ) . concat ( d3ops ) ;
args . unshift ( source ) ;
args . unshift ( target ) ;
d3 . rebind . apply ( this , args ) ;
// pass along the lists to keep track of them, don't allow duplicates
target . _inherited = nv . utils . arrayUnique ( ops . concat ( calls ) . concat ( inherited ) . concat ( ops ) . concat ( target . _inherited || [ ] ) ) ;
target . _d3options = nv . utils . arrayUnique ( d3ops . concat ( target . _d3options || [ ] ) ) ;
} ;
/ *
Runs common initialize code on the svg before the chart builds
* /
nv . utils . initSVG = function ( svg ) {
svg . classed ( { 'nvd3-svg' : true } ) ;
} ;
/ *
Sanitize and provide default for the container height .
* /
nv . utils . sanitizeHeight = function ( height , container ) {
return ( height || parseInt ( container . style ( 'height' ) , 10 ) || 400 ) ;
} ;
/ *
Sanitize and provide default for the container width .
* /
nv . utils . sanitizeWidth = function ( width , container ) {
return ( width || parseInt ( container . style ( 'width' ) , 10 ) || 960 ) ;
} ;
/ *
Calculate the available height for a chart .
* /
nv . utils . availableHeight = function ( height , container , margin ) {
return Math . max ( 0 , nv . utils . sanitizeHeight ( height , container ) - margin . top - margin . bottom ) ;
} ;
/ *
Calculate the available width for a chart .
* /
nv . utils . availableWidth = function ( width , container , margin ) {
return Math . max ( 0 , nv . utils . sanitizeWidth ( width , container ) - margin . left - margin . right ) ;
} ;
/ *
Clear any rendered chart components and display a chart 's ' noData ' message
* /
nv . utils . noData = function ( chart , container ) {
var opt = chart . options ( ) ,
margin = opt . margin ( ) ,
noData = opt . noData ( ) ,
data = ( noData == null ) ? [ "No Data Available." ] : [ noData ] ,
height = nv . utils . availableHeight ( null , container , margin ) ,
width = nv . utils . availableWidth ( null , container , margin ) ,
x = margin . left + width / 2 ,
y = margin . top + height / 2 ;
//Remove any previously created chart components
container . selectAll ( 'g' ) . remove ( ) ;
var noDataText = container . selectAll ( '.nv-noData' ) . data ( data ) ;
noDataText . enter ( ) . append ( 'text' )
. attr ( 'class' , 'nvd3 nv-noData' )
. attr ( 'dy' , '-.7em' )
. style ( 'text-anchor' , 'middle' ) ;
noDataText
. attr ( 'x' , x )
. attr ( 'y' , y )
. text ( function ( t ) { return t ; } ) ;
} ;
/ *
Wrap long labels .
* /
nv . utils . wrapTicks = function ( text , width ) {
text . each ( function ( ) {
var text = d3 . select ( this ) ,
words = text . text ( ) . split ( /\s+/ ) . reverse ( ) ,
word ,
line = [ ] ,
lineNumber = 0 ,
lineHeight = 1.1 ,
y = text . attr ( "y" ) ,
dy = parseFloat ( text . attr ( "dy" ) ) ,
tspan = text . text ( null ) . append ( "tspan" ) . attr ( "x" , 0 ) . attr ( "y" , y ) . attr ( "dy" , dy + "em" ) ;
while ( word = words . pop ( ) ) {
line . push ( word ) ;
tspan . text ( line . join ( " " ) ) ;
if ( tspan . node ( ) . getComputedTextLength ( ) > width ) {
line . pop ( ) ;
tspan . text ( line . join ( " " ) ) ;
line = [ word ] ;
tspan = text . append ( "tspan" ) . attr ( "x" , 0 ) . attr ( "y" , y ) . attr ( "dy" , ++ lineNumber * lineHeight + dy + "em" ) . text ( word ) ;
}
}
} ) ;
} ;
/ *
Check equality of 2 array
* /
nv . utils . arrayEquals = function ( array1 , array2 ) {
if ( array1 === array2 )
return true ;
if ( ! array1 || ! array2 )
return false ;
// compare lengths - can save a lot of time
if ( array1 . length != array2 . length )
return false ;
for ( var i = 0 ,
l = array1 . length ; i < l ; i ++ ) {
// Check if we have nested arrays
if ( array1 [ i ] instanceof Array && array2 [ i ] instanceof Array ) {
// recurse into the nested arrays
if ( ! nv . arrayEquals ( array1 [ i ] , array2 [ i ] ) )
return false ;
} else if ( array1 [ i ] != array2 [ i ] ) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false ;
}
}
return true ;
} ; nv . models . axis = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var axis = d3 . svg . axis ( ) ;
var scale = d3 . scale . linear ( ) ;
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = 75 //only used for tickLabel currently
, height = 60 //only used for tickLabel currently
, axisLabelText = null
, showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
, rotateLabels = 0
, rotateYLabel = true
, staggerLabels = false
, isOrdinal = false
, ticks = null
, axisLabelDistance = 0
, fontSize = undefined
, duration = 250
, dispatch = d3 . dispatch ( 'renderEnd' )
;
axis
. scale ( scale )
. orient ( 'bottom' )
. tickFormat ( function ( d ) { return d } )
;
//============================================================
// Private Variables
//------------------------------------------------------------
var scale0 ;
var renderWatch = nv . utils . renderWatch ( dispatch , duration ) ;
function chart ( selection ) {
renderWatch . reset ( ) ;
selection . each ( function ( data ) {
var container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
// Setup containers and skeleton of chart
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 ( ticks !== null )
axis . ticks ( ticks ) ;
else 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
g . watchTransition ( renderWatch , 'axis' ) . call ( axis ) ;
scale0 = scale0 || axis . scale ( ) ;
var fmt = axis . tickFormat ( ) ;
if ( fmt == null ) {
fmt = scale0 . tickFormat ( ) ;
}
var axisLabel = g . selectAll ( 'text.nv-axislabel' )
. data ( [ axisLabelText || null ] ) ;
axisLabel . exit ( ) . remove ( ) ;
//only skip when fontSize is undefined so it can be cleared with a null or blank string
if ( fontSize !== undefined ) {
g . selectAll ( 'g' ) . select ( "text" ) . style ( 'font-size' , fontSize ) ;
}
var xLabelMargin ;
var axisMaxMin ;
var w ;
switch ( axis . orient ( ) ) {
case 'top' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' ) ;
w = 0 ;
if ( scale . range ( ) . length === 1 ) {
w = isOrdinal ? scale . range ( ) [ 0 ] * 2 + scale . rangeBand ( ) : 0 ;
} else if ( scale . range ( ) . length === 2 ) {
w = isOrdinal ? scale . range ( ) [ 0 ] + scale . range ( ) [ 1 ] + scale . rangeBand ( ) : scale . range ( ) [ 1 ] ;
} else if ( scale . range ( ) . length > 2 ) {
w = scale . range ( ) [ scale . range ( ) . length - 1 ] + ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) ;
} ;
axisLabel
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , 0 )
. attr ( 'x' , w / 2 ) ;
if ( showMaxMin ) {
axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , function ( d , i ) {
return [ 'nv-axisMaxMin' , 'nv-axisMaxMin-x' , ( i == 0 ? 'nv-axisMin-x' : 'nv-axisMax-x' ) ] . join ( ' ' )
} ) . append ( 'text' ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + nv . utils . NaNtoZero ( scale ( d ) ) + ',0)'
} )
. select ( 'text' )
. attr ( 'dy' , '-0.5em' )
. attr ( 'y' , - axis . tickPadding ( ) )
. attr ( 'text-anchor' , 'middle' )
. text ( function ( d , i ) {
var v = fmt ( d ) ;
return ( '' + v ) . match ( 'NaN' ) ? '' : v ;
} ) ;
axisMaxMin . watchTransition ( renderWatch , 'min-max top' )
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + nv . utils . NaNtoZero ( scale . range ( ) [ i ] ) + ',0)'
} ) ;
}
break ;
case 'bottom' :
xLabelMargin = axisLabelDistance + 36 ;
var maxTextWidth = 30 ;
var textHeight = 0 ;
var xTicks = g . selectAll ( 'g' ) . select ( "text" ) ;
var rotateLabelsRule = '' ;
if ( rotateLabels % 360 ) {
//Reset transform on ticks so textHeight can be calculated correctly
xTicks . attr ( 'transform' , '' ) ;
//Calculate the longest xTick width
xTicks . each ( function ( d , i ) {
var box = this . getBoundingClientRect ( ) ;
var width = box . width ;
textHeight = box . height ;
if ( width > maxTextWidth ) maxTextWidth = width ;
} ) ;
rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + ( textHeight / 2 + axis . tickPadding ( ) ) + ')' ;
//Convert to radians before calculating sin. Add 30 to margin for healthy padding.
var sin = Math . abs ( Math . sin ( rotateLabels * Math . PI / 180 ) ) ;
xLabelMargin = ( sin ? sin * maxTextWidth : maxTextWidth ) + 30 ;
//Rotate all xTicks
xTicks
. attr ( 'transform' , rotateLabelsRule )
. style ( 'text-anchor' , rotateLabels % 360 > 0 ? 'start' : 'end' ) ;
} else {
if ( staggerLabels ) {
xTicks
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + ( i % 2 == 0 ? '0' : '12' ) + ')'
} ) ;
} else {
xTicks . attr ( 'transform' , "translate(0,0)" ) ;
}
}
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' ) ;
w = 0 ;
if ( scale . range ( ) . length === 1 ) {
w = isOrdinal ? scale . range ( ) [ 0 ] * 2 + scale . rangeBand ( ) : 0 ;
} else if ( scale . range ( ) . length === 2 ) {
w = isOrdinal ? scale . range ( ) [ 0 ] + scale . range ( ) [ 1 ] + scale . rangeBand ( ) : scale . range ( ) [ 1 ] ;
} else if ( scale . range ( ) . length > 2 ) {
w = scale . range ( ) [ scale . range ( ) . length - 1 ] + ( scale . range ( ) [ 1 ] - scale . range ( ) [ 0 ] ) ;
} ;
axisLabel
. attr ( 'text-anchor' , 'middle' )
. attr ( 'y' , xLabelMargin )
. attr ( 'x' , w / 2 ) ;
if ( showMaxMin ) {
//if (showMaxMin && !isOrdinal) {
axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
//.data(scale.domain())
. data ( [ scale . domain ( ) [ 0 ] , scale . domain ( ) [ scale . domain ( ) . length - 1 ] ] ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , function ( d , i ) {
return [ 'nv-axisMaxMin' , 'nv-axisMaxMin-x' , ( i == 0 ? 'nv-axisMin-x' : 'nv-axisMax-x' ) ] . join ( ' ' )
} ) . append ( 'text' ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + nv . utils . NaNtoZero ( ( scale ( d ) + ( isOrdinal ? scale . rangeBand ( ) / 2 : 0 ) ) ) + ',0)'
} )
. select ( 'text' )
. attr ( 'dy' , '.71em' )
. attr ( 'y' , axis . tickPadding ( ) )
. attr ( 'transform' , rotateLabelsRule )
. style ( 'text-anchor' , rotateLabels ? ( rotateLabels % 360 > 0 ? 'start' : 'end' ) : 'middle' )
. text ( function ( d , i ) {
var v = fmt ( d ) ;
return ( '' + v ) . match ( 'NaN' ) ? '' : v ;
} ) ;
axisMaxMin . watchTransition ( renderWatch , 'min-max bottom' )
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + nv . utils . NaNtoZero ( ( scale ( d ) + ( isOrdinal ? scale . rangeBand ( ) / 2 : 0 ) ) ) + ',0)'
} ) ;
}
break ;
case 'right' :
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' ) ;
axisLabel
. style ( '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
. attr ( 'x' , rotateYLabel ? ( d3 . max ( scale . range ( ) ) / 2 ) : axis . tickPadding ( ) ) ;
if ( showMaxMin ) {
axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , function ( d , i ) {
return [ 'nv-axisMaxMin' , 'nv-axisMaxMin-y' , ( i == 0 ? 'nv-axisMin-y' : 'nv-axisMax-y' ) ] . join ( ' ' )
} ) . append ( 'text' )
. style ( 'opacity' , 0 ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + nv . utils . NaNtoZero ( scale ( d ) ) + ')'
} )
. select ( 'text' )
. attr ( 'dy' , '.32em' )
. attr ( 'y' , 0 )
. attr ( 'x' , axis . tickPadding ( ) )
. style ( 'text-anchor' , 'start' )
. text ( function ( d , i ) {
var v = fmt ( d ) ;
return ( '' + v ) . match ( 'NaN' ) ? '' : v ;
} ) ;
axisMaxMin . watchTransition ( renderWatch , 'min-max right' )
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + nv . utils . NaNtoZero ( scale . range ( ) [ i ] ) + ')'
} )
. select ( 'text' )
. style ( 'opacity' , 1 ) ;
}
break ;
case 'left' :
/ *
//For dynamically placing the label. Can be used with dynamically-sized chart axis margins
var yTicks = g . selectAll ( 'g' ) . select ( "text" ) ;
yTicks . each ( function ( d , i ) {
var labelPadding = this . getBoundingClientRect ( ) . width + axis . tickPadding ( ) + 16 ;
if ( labelPadding > width ) width = labelPadding ;
} ) ;
* /
axisLabel . enter ( ) . append ( 'text' ) . attr ( 'class' , 'nv-axislabel' ) ;
axisLabel
. style ( 'text-anchor' , rotateYLabel ? 'middle' : 'end' )
. attr ( 'transform' , rotateYLabel ? 'rotate(-90)' : '' )
. attr ( 'y' , rotateYLabel ? ( - Math . max ( margin . left , width ) + 25 - ( axisLabelDistance || 0 ) ) : - 10 )
. attr ( 'x' , rotateYLabel ? ( - d3 . max ( scale . range ( ) ) / 2 ) : - axis . tickPadding ( ) ) ;
if ( showMaxMin ) {
axisMaxMin = wrap . selectAll ( 'g.nv-axisMaxMin' )
. data ( scale . domain ( ) ) ;
axisMaxMin . enter ( ) . append ( 'g' ) . attr ( 'class' , function ( d , i ) {
return [ 'nv-axisMaxMin' , 'nv-axisMaxMin-y' , ( i == 0 ? 'nv-axisMin-y' : 'nv-axisMax-y' ) ] . join ( ' ' )
} ) . append ( 'text' )
. style ( 'opacity' , 0 ) ;
axisMaxMin . exit ( ) . remove ( ) ;
axisMaxMin
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + nv . utils . NaNtoZero ( scale0 ( d ) ) + ')'
} )
. select ( 'text' )
. attr ( 'dy' , '.32em' )
. attr ( 'y' , 0 )
. attr ( 'x' , - axis . tickPadding ( ) )
. attr ( 'text-anchor' , 'end' )
. text ( function ( d , i ) {
var v = fmt ( d ) ;
return ( '' + v ) . match ( 'NaN' ) ? '' : v ;
} ) ;
axisMaxMin . watchTransition ( renderWatch , 'min-max right' )
. attr ( 'transform' , function ( d , i ) {
return 'translate(0,' + nv . utils . NaNtoZero ( scale . range ( ) [ i ] ) + ')'
} )
. select ( 'text' )
. style ( 'opacity' , 1 ) ;
}
break ;
}
axisLabel . text ( function ( d ) { return d } ) ;
if ( showMaxMin && ( axis . orient ( ) === 'left' || axis . orient ( ) === 'right' ) ) {
//check if max and min overlap other values, if so, hide the values that overlap
g . selectAll ( 'g' ) // the g's wrapping each tick
. each ( function ( d , i ) {
d3 . select ( this ) . select ( 'text' ) . attr ( 'opacity' , 1 ) ;
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 ) . attr ( 'opacity' , 0 ) ;
d3 . select ( this ) . select ( 'text' ) . attr ( 'opacity' , 0 ) ; // Don't remove the ZERO line!!
}
} ) ;
//if Max and Min = 0 only show min, Issue #281
if ( scale . domain ( ) [ 0 ] == scale . domain ( ) [ 1 ] && scale . domain ( ) [ 0 ] == 0 ) {
wrap . selectAll ( 'g.nv-axisMaxMin' ) . style ( 'opacity' , function ( d , i ) {
return ! i ? 1 : 0
} ) ;
}
}
if ( showMaxMin && ( axis . orient ( ) === 'top' || axis . orient ( ) === 'bottom' ) ) {
var maxMinRange = [ ] ;
wrap . selectAll ( 'g.nv-axisMaxMin' )
. each ( function ( d , i ) {
try {
if ( i ) // i== 1, max position
maxMinRange . push ( scale ( d ) - this . getBoundingClientRect ( ) . 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 . getBoundingClientRect ( ) . width + 4 )
} catch ( err ) {
if ( i ) // i== 1, max position
maxMinRange . push ( scale ( d ) - 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 ) + 4 ) ;
}
} ) ;
// the g's wrapping each tick
g . selectAll ( 'g' ) . 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 tick line
g . selectAll ( '.tick' )
. filter ( function ( d ) {
/ *
The filter needs to return only ticks at or near zero .
Numbers like 0.00001 need to count as zero as well ,
and the arithmetic trick below solves that .
* /
return ! parseFloat ( Math . round ( d * 100000 ) / 1000000 ) && ( d !== undefined )
} )
. classed ( 'zero' , true ) ;
//store old scales for use in transitions on update
scale0 = scale . copy ( ) ;
} ) ;
renderWatch . renderEnd ( 'axis immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart . axis = axis ;
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
axisLabelDistance : { get : function ( ) { return axisLabelDistance ; } , set : function ( _ ) { axisLabelDistance = _ ; } } ,
staggerLabels : { get : function ( ) { return staggerLabels ; } , set : function ( _ ) { staggerLabels = _ ; } } ,
rotateLabels : { get : function ( ) { return rotateLabels ; } , set : function ( _ ) { rotateLabels = _ ; } } ,
rotateYLabel : { get : function ( ) { return rotateYLabel ; } , set : function ( _ ) { rotateYLabel = _ ; } } ,
showMaxMin : { get : function ( ) { return showMaxMin ; } , set : function ( _ ) { showMaxMin = _ ; } } ,
axisLabel : { get : function ( ) { return axisLabelText ; } , set : function ( _ ) { axisLabelText = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
ticks : { get : function ( ) { return ticks ; } , set : function ( _ ) { ticks = _ ; } } ,
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
fontSize : { get : function ( ) { return fontSize ; } , set : function ( _ ) { fontSize = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
} } ,
scale : { get : function ( ) { return scale ; } , set : function ( _ ) {
scale = _ ;
axis . scale ( scale ) ;
isOrdinal = typeof scale . rangeBands === 'function' ;
nv . utils . inheritOptionsD3 ( chart , scale , [ 'domain' , 'range' , 'rangeBand' , 'rangeBands' ] ) ;
} }
} ) ;
nv . utils . initOptions ( chart ) ;
nv . utils . inheritOptionsD3 ( chart , axis , [ 'orient' , 'tickValues' , 'tickSubdivide' , 'tickSize' , 'tickPadding' , 'tickFormat' ] ) ;
nv . utils . inheritOptionsD3 ( chart , scale , [ 'domain' , 'range' , 'rangeBand' , 'rangeBands' ] ) ;
return chart ;
} ;
nv . models . scatter = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = null
, height = null
, color = nv . utils . defaultColor ( ) // chooses color
, id = Math . floor ( Math . random ( ) * 100000 ) //Create semi-unique ID incase user doesn't select one
, container = null
, x = d3 . scale . linear ( )
, y = d3 . scale . linear ( )
, z = d3 . scale . linear ( ) //linear because d3.svg.shape.size is treated as area
, getX = function ( d ) { return d . x } // accessor to get the x value
, getY = function ( d ) { return d . y } // accessor to get the y value
, getSize = function ( d ) { return d . size || 1 } // accessor to get the point size
, getShape = function ( d ) { return d . shape || 'circle' } // accessor to get point shape
, forceX = [ ] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
, forceY = [ ] // List of numbers to Force into the Y scale
, forceSize = [ ] // List of numbers to Force into the Size scale
, interactive = true // If true, plots a voronoi overlay for advanced point intersection
, pointActive = function ( d ) { return ! d . notActive } // any points that return false will be filtered out
, padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
, padDataOuter = . 1 //outerPadding to imitate ordinal scale outer padding
, clipEdge = false // if true, masks points within x and y scale
, clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
, showVoronoi = false // display the voronoi areas
, clipRadius = function ( ) { return 25 } // function to get the radius for voronoi point clips
, xDomain = null // Override x domain (skips the calculation from data)
, yDomain = null // Override y domain
, xRange = null // Override x range
, yRange = null // Override y range
, sizeDomain = null // Override point size domain
, sizeRange = null
, singlePoint = false
, dispatch = d3 . dispatch ( 'elementClick' , 'elementDblClick' , 'elementMouseover' , 'elementMouseout' , 'renderEnd' )
, useVoronoi = true
, duration = 250
, interactiveUpdateDelay = 300
, showLabels = false
;
//============================================================
// Private Variables
//------------------------------------------------------------
var x0 , y0 , z0 // used to store previous scales
, timeoutID
, needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
, renderWatch = nv . utils . renderWatch ( dispatch , duration )
, _sizeRange _def = [ 16 , 256 ]
, _caches
;
function getCache ( d ) {
var cache , i ;
cache = _caches = _caches || { } ;
i = d [ 0 ] . series ;
cache = cache [ i ] = cache [ i ] || { } ;
i = d [ 1 ] ;
cache = cache [ i ] = cache [ i ] || { } ;
return cache ;
}
function getDiffs ( d ) {
var i , key ,
point = d [ 0 ] ,
cache = getCache ( d ) ,
diffs = false ;
for ( i = 1 ; i < arguments . length ; i ++ ) {
key = arguments [ i ] ;
if ( cache [ key ] !== point [ key ] || ! cache . hasOwnProperty ( key ) ) {
cache [ key ] = point [ key ] ;
diffs = true ;
}
}
return diffs ;
}
function chart ( selection ) {
renderWatch . reset ( ) ;
selection . each ( function ( data ) {
container = d3 . select ( this ) ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
nv . utils . initSVG ( container ) ;
//add series index to each data point for reference
data . forEach ( function ( series , i ) {
series . values . forEach ( function ( point ) {
point . series = i ;
} ) ;
} ) ;
// Setup Scales
var logScale = chart . yScale ( ) . name === d3 . scale . log ( ) . name ? true : false ;
// remap and flatten the data for use in calculating the scales' domains
var seriesData = ( xDomain && yDomain && sizeDomain ) ? [ ] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
d3 . merge (
data . map ( function ( d ) {
return d . values . map ( function ( d , i ) {
return { x : getX ( d , i ) , y : getY ( d , i ) , size : getSize ( d , i ) }
} )
} )
) ;
x . domain ( xDomain || d3 . extent ( seriesData . map ( function ( d ) { return d . x ; } ) . concat ( forceX ) ) )
if ( padData && data [ 0 ] )
x . range ( xRange || [ ( availableWidth * padDataOuter + availableWidth ) / ( 2 * data [ 0 ] . values . length ) , availableWidth - availableWidth * ( 1 + padDataOuter ) / ( 2 * data [ 0 ] . values . length ) ] ) ;
//x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
else
x . range ( xRange || [ 0 , availableWidth ] ) ;
if ( logScale ) {
var min = d3 . min ( seriesData . map ( function ( d ) { if ( d . y !== 0 ) return d . y ; } ) ) ;
y . clamp ( true )
. domain ( yDomain || d3 . extent ( seriesData . map ( function ( d ) {
if ( d . y !== 0 ) return d . y ;
else return min * 0.1 ;
} ) . concat ( forceY ) ) )
. range ( yRange || [ availableHeight , 0 ] ) ;
} else {
y . domain ( yDomain || d3 . extent ( seriesData . map ( function ( d ) { return d . y ; } ) . concat ( forceY ) ) )
. range ( yRange || [ availableHeight , 0 ] ) ;
}
z . domain ( sizeDomain || d3 . extent ( seriesData . map ( function ( d ) { return d . size } ) . concat ( forceSize ) ) )
. range ( sizeRange || _sizeRange _def ) ;
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
singlePoint = x . domain ( ) [ 0 ] === x . domain ( ) [ 1 ] || y . domain ( ) [ 0 ] === y . domain ( ) [ 1 ] ;
if ( x . domain ( ) [ 0 ] === x . domain ( ) [ 1 ] )
x . domain ( ) [ 0 ] ?
x . domain ( [ x . domain ( ) [ 0 ] - x . domain ( ) [ 0 ] * 0.01 , x . domain ( ) [ 1 ] + x . domain ( ) [ 1 ] * 0.01 ] )
: x . domain ( [ - 1 , 1 ] ) ;
if ( y . domain ( ) [ 0 ] === y . domain ( ) [ 1 ] )
y . domain ( ) [ 0 ] ?
y . domain ( [ y . domain ( ) [ 0 ] - y . domain ( ) [ 0 ] * 0.01 , y . domain ( ) [ 1 ] + y . domain ( ) [ 1 ] * 0.01 ] )
: y . domain ( [ - 1 , 1 ] ) ;
if ( isNaN ( x . domain ( ) [ 0 ] ) ) {
x . domain ( [ - 1 , 1 ] ) ;
}
if ( isNaN ( y . domain ( ) [ 0 ] ) ) {
y . domain ( [ - 1 , 1 ] ) ;
}
x0 = x0 || x ;
y0 = y0 || y ;
z0 = z0 || z ;
var scaleDiff = x ( 1 ) !== x0 ( 1 ) || y ( 1 ) !== y0 ( 1 ) || z ( 1 ) !== z0 ( 1 ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-scatter' ) . data ( [ data ] ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-scatter nv-chart-' + id ) ;
var defsEnter = wrapEnter . append ( 'defs' ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
wrap . classed ( 'nv-single-point' , singlePoint ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-groups' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-point-paths' ) ;
wrapEnter . append ( 'g' ) . attr ( 'class' , 'nv-point-clips' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
defsEnter . append ( 'clipPath' )
. attr ( 'id' , 'nv-edge-clip-' + id )
. append ( 'rect' ) ;
wrap . select ( '#nv-edge-clip-' + id + ' rect' )
. attr ( 'width' , availableWidth )
. attr ( 'height' , ( availableHeight > 0 ) ? availableHeight : 0 ) ;
g . attr ( 'clip-path' , clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '' ) ;
function updateInteractiveLayer ( ) {
// Always clear needs-update flag regardless of whether or not
// we will actually do anything (avoids needless invocations).
needsUpdate = false ;
if ( ! interactive ) return false ;
// inject series and point index for reference into voronoi
if ( useVoronoi === true ) {
var vertices = d3 . merge ( data . map ( function ( group , groupIndex ) {
return group . values
. map ( function ( point , pointIndex ) {
// *Adding noise to make duplicates very unlikely
// *Injecting series and point index for reference
/ * * A d d i n g a ' j i t t e r ' t o t h e p o i n t s , b e c a u s e t h e r e ' s a n i s s u e i n d 3 . g e o m . v o r o n o i .
* /
var pX = getX ( point , pointIndex ) ;
var pY = getY ( point , pointIndex ) ;
return [ nv . utils . NaNtoZero ( x ( pX ) ) + Math . random ( ) * 1e-4 ,
nv . utils . NaNtoZero ( y ( pY ) ) + Math . random ( ) * 1e-4 ,
groupIndex ,
pointIndex , point ] ; //temp hack to add noise until I think of a better way so there are no duplicates
} )
. filter ( function ( pointArray , pointIndex ) {
return pointActive ( pointArray [ 4 ] , pointIndex ) ; // Issue #237.. move filter to after map, so pointIndex is correct!
} )
} )
) ;
if ( vertices . length == 0 ) return false ; // No active points, we're done
if ( vertices . length < 3 ) {
// Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
vertices . push ( [ x . range ( ) [ 0 ] - 20 , y . range ( ) [ 0 ] - 20 , null , null ] ) ;
vertices . push ( [ x . range ( ) [ 1 ] + 20 , y . range ( ) [ 1 ] + 20 , null , null ] ) ;
vertices . push ( [ x . range ( ) [ 0 ] - 20 , y . range ( ) [ 0 ] + 20 , null , null ] ) ;
vertices . push ( [ x . range ( ) [ 1 ] + 20 , y . range ( ) [ 1 ] - 20 , null , null ] ) ;
}
// keep voronoi sections from going more than 10 outside of graph
// to avoid overlap with other things like legend etc
var bounds = d3 . geom . polygon ( [
[ - 10 , - 10 ] ,
[ - 10 , height + 10 ] ,
[ width + 10 , height + 10 ] ,
[ width + 10 , - 10 ]
] ) ;
var voronoi = d3 . geom . voronoi ( vertices ) . map ( function ( d , i ) {
return {
'data' : bounds . clip ( d ) ,
'series' : vertices [ i ] [ 2 ] ,
'point' : vertices [ i ] [ 3 ]
}
} ) ;
// nuke all voronoi paths on reload and recreate them
wrap . select ( '.nv-point-paths' ) . selectAll ( 'path' ) . remove ( ) ;
var pointPaths = wrap . select ( '.nv-point-paths' ) . selectAll ( 'path' ) . data ( voronoi ) ;
var vPointPaths = pointPaths
. enter ( ) . append ( "svg:path" )
. attr ( "d" , function ( d ) {
if ( ! d || ! d . data || d . data . length === 0 )
return 'M 0 0' ;
else
return "M" + d . data . join ( "," ) + "Z" ;
} )
. attr ( "id" , function ( d , i ) {
return "nv-path-" + i ; } )
. attr ( "clip-path" , function ( d , i ) { return "url(#nv-clip-" + id + "-" + i + ")" ; } )
;
// good for debugging point hover issues
if ( showVoronoi ) {
vPointPaths . style ( "fill" , d3 . rgb ( 230 , 230 , 230 ) )
. style ( 'fill-opacity' , 0.4 )
. style ( 'stroke-opacity' , 1 )
. style ( "stroke" , d3 . rgb ( 200 , 200 , 200 ) ) ;
}
if ( clipVoronoi ) {
// voronoi sections are already set to clip,
// just create the circles with the IDs they expect
wrap . select ( '.nv-point-clips' ) . selectAll ( '*' ) . remove ( ) ; // must do * since it has sub-dom
var pointClips = wrap . select ( '.nv-point-clips' ) . selectAll ( 'clipPath' ) . data ( vertices ) ;
var vPointClips = pointClips
. enter ( ) . append ( "svg:clipPath" )
. attr ( "id" , function ( d , i ) { return "nv-clip-" + id + "-" + i ; } )
. append ( "svg:circle" )
. attr ( 'cx' , function ( d ) { return d [ 0 ] ; } )
. attr ( 'cy' , function ( d ) { return d [ 1 ] ; } )
. attr ( 'r' , clipRadius ) ;
}
var mouseEventCallback = function ( d , mDispatch ) {
if ( needsUpdate ) return 0 ;
var series = data [ d . series ] ;
if ( series === undefined ) return ;
var point = series . values [ d . point ] ;
point [ 'color' ] = color ( series , d . series ) ;
// standardize attributes for tooltip.
point [ 'x' ] = getX ( point ) ;
point [ 'y' ] = getY ( point ) ;
// can't just get box of event node since it's actually a voronoi polygon
var box = container . node ( ) . getBoundingClientRect ( ) ;
var scrollTop = window . pageYOffset || document . documentElement . scrollTop ;
var scrollLeft = window . pageXOffset || document . documentElement . scrollLeft ;
var pos = {
left : x ( getX ( point , d . point ) ) + box . left + scrollLeft + margin . left + 10 ,
top : y ( getY ( point , d . point ) ) + box . top + scrollTop + margin . top + 10
} ;
mDispatch ( {
point : point ,
series : series ,
pos : pos ,
relativePos : [ x ( getX ( point , d . point ) ) + margin . left , y ( getY ( point , d . point ) ) + margin . top ] ,
seriesIndex : d . series ,
pointIndex : d . point
} ) ;
} ;
pointPaths
. on ( 'click' , function ( d ) {
mouseEventCallback ( d , dispatch . elementClick ) ;
} )
. on ( 'dblclick' , function ( d ) {
mouseEventCallback ( d , dispatch . elementDblClick ) ;
} )
. on ( 'mouseover' , function ( d ) {
mouseEventCallback ( d , dispatch . elementMouseover ) ;
} )
. on ( 'mouseout' , function ( d , i ) {
mouseEventCallback ( d , dispatch . elementMouseout ) ;
} ) ;
} else {
// add event handlers to points instead voronoi paths
wrap . select ( '.nv-groups' ) . selectAll ( '.nv-group' )
. selectAll ( '.nv-point' )
//.data(dataWithPoints)
//.style('pointer-events', 'auto') // recativate events, disabled by css
. on ( 'click' , function ( d , i ) {
//nv.log('test', d, i);
if ( needsUpdate || ! data [ d . series ] ) return 0 ; //check if this is a dummy point
var series = data [ d . series ] ,
point = series . values [ i ] ;
dispatch . elementClick ( {
point : point ,
series : series ,
pos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] , //TODO: make this pos base on the page
relativePos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] ,
seriesIndex : d . series ,
pointIndex : i
} ) ;
} )
. on ( 'dblclick' , function ( d , i ) {
if ( needsUpdate || ! data [ d . series ] ) return 0 ; //check if this is a dummy point
var series = data [ d . series ] ,
point = series . values [ i ] ;
dispatch . elementDblClick ( {
point : point ,
series : series ,
pos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] , //TODO: make this pos base on the page
relativePos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] ,
seriesIndex : d . series ,
pointIndex : i
} ) ;
} )
. on ( 'mouseover' , function ( d , i ) {
if ( needsUpdate || ! data [ d . series ] ) return 0 ; //check if this is a dummy point
var series = data [ d . series ] ,
point = series . values [ i ] ;
dispatch . elementMouseover ( {
point : point ,
series : series ,
pos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] , //TODO: make this pos base on the page
relativePos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] ,
seriesIndex : d . series ,
pointIndex : i ,
color : color ( d , i )
} ) ;
} )
. on ( 'mouseout' , function ( d , i ) {
if ( needsUpdate || ! data [ d . series ] ) return 0 ; //check if this is a dummy point
var series = data [ d . series ] ,
point = series . values [ i ] ;
dispatch . elementMouseout ( {
point : point ,
series : series ,
pos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] , //TODO: make this pos base on the page
relativePos : [ x ( getX ( point , i ) ) + margin . left , y ( getY ( point , i ) ) + margin . top ] ,
seriesIndex : d . series ,
pointIndex : i ,
color : color ( d , i )
} ) ;
} ) ;
}
}
needsUpdate = true ;
var groups = wrap . select ( '.nv-groups' ) . selectAll ( '.nv-group' )
. data ( function ( d ) { return d } , function ( d ) { return d . key } ) ;
groups . enter ( ) . append ( 'g' )
. style ( 'stroke-opacity' , 1e-6 )
. style ( 'fill-opacity' , 1e-6 ) ;
groups . exit ( )
. remove ( ) ;
groups
. attr ( 'class' , function ( d , i ) {
return ( d . classed || '' ) + ' nv-group nv-series-' + i ;
} )
. classed ( 'nv-noninteractive' , ! interactive )
. classed ( 'hover' , function ( d ) { return d . hover } ) ;
groups . watchTransition ( renderWatch , 'scatter: groups' )
. style ( 'fill' , function ( d , i ) { return color ( d , i ) } )
. style ( 'stroke' , function ( d , i ) { return color ( d , i ) } )
. style ( 'stroke-opacity' , 1 )
. style ( 'fill-opacity' , . 5 ) ;
// create the points, maintaining their IDs from the original data set
var points = groups . selectAll ( 'path.nv-point' )
. data ( function ( d ) {
return d . values . map (
function ( point , pointIndex ) {
return [ point , pointIndex ]
} ) . filter (
function ( pointArray , pointIndex ) {
return pointActive ( pointArray [ 0 ] , pointIndex )
} )
} ) ;
points . enter ( ) . append ( 'path' )
. attr ( 'class' , function ( d ) {
return 'nv-point nv-point-' + d [ 1 ] ;
} )
. style ( 'fill' , function ( d ) { return d . color } )
. style ( 'stroke' , function ( d ) { return d . color } )
. attr ( 'transform' , function ( d ) {
return 'translate(' + nv . utils . NaNtoZero ( x0 ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + ',' + nv . utils . NaNtoZero ( y0 ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')'
} )
. attr ( 'd' ,
nv . utils . symbol ( )
. type ( function ( d ) { return getShape ( d [ 0 ] ) ; } )
. size ( function ( d ) { return z ( getSize ( d [ 0 ] , d [ 1 ] ) ) } )
) ;
points . exit ( ) . remove ( ) ;
groups . exit ( ) . selectAll ( 'path.nv-point' )
. watchTransition ( renderWatch , 'scatter exit' )
. attr ( 'transform' , function ( d ) {
return 'translate(' + nv . utils . NaNtoZero ( x ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + ',' + nv . utils . NaNtoZero ( y ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')'
} )
. remove ( ) ;
points . filter ( function ( d ) { return scaleDiff || getDiffs ( d , 'x' , 'y' ) ; } )
. watchTransition ( renderWatch , 'scatter points' )
. attr ( 'transform' , function ( d ) {
//nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
return 'translate(' + nv . utils . NaNtoZero ( x ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + ',' + nv . utils . NaNtoZero ( y ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')'
} ) ;
points . filter ( function ( d ) { return scaleDiff || getDiffs ( d , 'shape' , 'size' ) ; } )
. watchTransition ( renderWatch , 'scatter points' )
. attr ( 'd' ,
nv . utils . symbol ( )
. type ( function ( d ) { return getShape ( d [ 0 ] ) ; } )
. size ( function ( d ) { return z ( getSize ( d [ 0 ] , d [ 1 ] ) ) } )
) ;
// add label a label to scatter chart
if ( showLabels )
{
var titles = groups . selectAll ( '.nv-label' )
. data ( function ( d ) {
return d . values . map (
function ( point , pointIndex ) {
return [ point , pointIndex ]
} ) . filter (
function ( pointArray , pointIndex ) {
return pointActive ( pointArray [ 0 ] , pointIndex )
} )
} ) ;
titles . enter ( ) . append ( 'text' )
. style ( 'fill' , function ( d , i ) {
return d . color } )
. style ( 'stroke-opacity' , 0 )
. style ( 'fill-opacity' , 1 )
. attr ( 'transform' , function ( d ) {
var dx = nv . utils . NaNtoZero ( x0 ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + Math . sqrt ( z ( getSize ( d [ 0 ] , d [ 1 ] ) ) / Math . PI ) + 2 ;
return 'translate(' + dx + ',' + nv . utils . NaNtoZero ( y0 ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')' ;
} )
. text ( function ( d , i ) {
return d [ 0 ] . label ; } ) ;
titles . exit ( ) . remove ( ) ;
groups . exit ( ) . selectAll ( 'path.nv-label' )
. watchTransition ( renderWatch , 'scatter exit' )
. attr ( 'transform' , function ( d ) {
var dx = nv . utils . NaNtoZero ( x ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + Math . sqrt ( z ( getSize ( d [ 0 ] , d [ 1 ] ) ) / Math . PI ) + 2 ;
return 'translate(' + dx + ',' + nv . utils . NaNtoZero ( y ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')' ;
} )
. remove ( ) ;
titles . each ( function ( d ) {
d3 . select ( this )
. classed ( 'nv-label' , true )
. classed ( 'nv-label-' + d [ 1 ] , false )
. classed ( 'hover' , false ) ;
} ) ;
titles . watchTransition ( renderWatch , 'scatter labels' )
. attr ( 'transform' , function ( d ) {
var dx = nv . utils . NaNtoZero ( x ( getX ( d [ 0 ] , d [ 1 ] ) ) ) + Math . sqrt ( z ( getSize ( d [ 0 ] , d [ 1 ] ) ) / Math . PI ) + 2 ;
return 'translate(' + dx + ',' + nv . utils . NaNtoZero ( y ( getY ( d [ 0 ] , d [ 1 ] ) ) ) + ')'
} ) ;
}
// Delay updating the invisible interactive layer for smoother animation
if ( interactiveUpdateDelay )
{
clearTimeout ( timeoutID ) ; // stop repeat calls to updateInteractiveLayer
timeoutID = setTimeout ( updateInteractiveLayer , interactiveUpdateDelay ) ;
}
else
{
updateInteractiveLayer ( ) ;
}
//store old scales for use in transitions on update
x0 = x . copy ( ) ;
y0 = y . copy ( ) ;
z0 = z . copy ( ) ;
} ) ;
renderWatch . renderEnd ( 'scatter immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
// utility function calls provided by this chart
chart . _calls = new function ( ) {
this . clearHighlights = function ( ) {
nv . dom . write ( function ( ) {
container . selectAll ( ".nv-point.hover" ) . classed ( "hover" , false ) ;
} ) ;
return null ;
} ;
this . highlightPoint = function ( seriesIndex , pointIndex , isHoverOver ) {
nv . dom . write ( function ( ) {
container . select ( '.nv-groups' )
. selectAll ( ".nv-series-" + seriesIndex )
. selectAll ( ".nv-point-" + pointIndex )
. classed ( "hover" , isHoverOver ) ;
} ) ;
} ;
} ;
// trigger calls from events too
dispatch . on ( 'elementMouseover.point' , function ( d ) {
if ( interactive ) chart . _calls . highlightPoint ( d . seriesIndex , d . pointIndex , true ) ;
} ) ;
dispatch . on ( 'elementMouseout.point' , function ( d ) {
if ( interactive ) chart . _calls . highlightPoint ( d . seriesIndex , d . pointIndex , false ) ;
} ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
xScale : { get : function ( ) { return x ; } , set : function ( _ ) { x = _ ; } } ,
yScale : { get : function ( ) { return y ; } , set : function ( _ ) { y = _ ; } } ,
pointScale : { get : function ( ) { return z ; } , set : function ( _ ) { z = _ ; } } ,
xDomain : { get : function ( ) { return xDomain ; } , set : function ( _ ) { xDomain = _ ; } } ,
yDomain : { get : function ( ) { return yDomain ; } , set : function ( _ ) { yDomain = _ ; } } ,
pointDomain : { get : function ( ) { return sizeDomain ; } , set : function ( _ ) { sizeDomain = _ ; } } ,
xRange : { get : function ( ) { return xRange ; } , set : function ( _ ) { xRange = _ ; } } ,
yRange : { get : function ( ) { return yRange ; } , set : function ( _ ) { yRange = _ ; } } ,
pointRange : { get : function ( ) { return sizeRange ; } , set : function ( _ ) { sizeRange = _ ; } } ,
forceX : { get : function ( ) { return forceX ; } , set : function ( _ ) { forceX = _ ; } } ,
forceY : { get : function ( ) { return forceY ; } , set : function ( _ ) { forceY = _ ; } } ,
forcePoint : { get : function ( ) { return forceSize ; } , set : function ( _ ) { forceSize = _ ; } } ,
interactive : { get : function ( ) { return interactive ; } , set : function ( _ ) { interactive = _ ; } } ,
pointActive : { get : function ( ) { return pointActive ; } , set : function ( _ ) { pointActive = _ ; } } ,
padDataOuter : { get : function ( ) { return padDataOuter ; } , set : function ( _ ) { padDataOuter = _ ; } } ,
padData : { get : function ( ) { return padData ; } , set : function ( _ ) { padData = _ ; } } ,
clipEdge : { get : function ( ) { return clipEdge ; } , set : function ( _ ) { clipEdge = _ ; } } ,
clipVoronoi : { get : function ( ) { return clipVoronoi ; } , set : function ( _ ) { clipVoronoi = _ ; } } ,
clipRadius : { get : function ( ) { return clipRadius ; } , set : function ( _ ) { clipRadius = _ ; } } ,
showVoronoi : { get : function ( ) { return showVoronoi ; } , set : function ( _ ) { showVoronoi = _ ; } } ,
id : { get : function ( ) { return id ; } , set : function ( _ ) { id = _ ; } } ,
interactiveUpdateDelay : { get : function ( ) { return interactiveUpdateDelay ; } , set : function ( _ ) { interactiveUpdateDelay = _ ; } } ,
showLabels : { get : function ( ) { return showLabels ; } , set : function ( _ ) { showLabels = _ ; } } ,
// simple functor options
x : { get : function ( ) { return getX ; } , set : function ( _ ) { getX = d3 . functor ( _ ) ; } } ,
y : { get : function ( ) { return getY ; } , set : function ( _ ) { getY = d3 . functor ( _ ) ; } } ,
pointSize : { get : function ( ) { return getSize ; } , set : function ( _ ) { getSize = d3 . functor ( _ ) ; } } ,
pointShape : { get : function ( ) { return getShape ; } , set : function ( _ ) { getShape = d3 . functor ( _ ) ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
} } ,
useVoronoi : { get : function ( ) { return useVoronoi ; } , set : function ( _ ) {
useVoronoi = _ ;
if ( useVoronoi === false ) {
clipVoronoi = false ;
}
} }
} ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . multiBar = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = 960
, height = 500
, x = d3 . scale . ordinal ( )
, y = d3 . scale . linear ( )
, id = Math . floor ( Math . random ( ) * 10000 ) //Create semi-unique ID in case user doesn't select one
, container = null
, getX = function ( d ) { return d . x }
, getY = function ( d ) { return d . y }
, forceY = [ 0 ] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
, clipEdge = true
, stacked = false
, stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
, color = nv . utils . defaultColor ( )
, hideable = false
, barColor = null // adding the ability to set the color for each rather than the whole group
, disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
, duration = 500
, xDomain
, yDomain
, xRange
, yRange
, groupSpacing = 0.1
, dispatch = d3 . dispatch ( 'chartClick' , 'elementClick' , 'elementDblClick' , 'elementMouseover' , 'elementMouseout' , 'elementMousemove' , 'renderEnd' )
;
//============================================================
// Private Variables
//------------------------------------------------------------
var x0 , y0 //used to store previous scales
, renderWatch = nv . utils . renderWatch ( dispatch , duration )
;
var last _datalength = 0 ;
function chart ( selection ) {
renderWatch . reset ( ) ;
selection . each ( function ( data ) {
var availableWidth = width - margin . left - margin . right ,
availableHeight = height - margin . top - margin . bottom ;
container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
var nonStackableCount = 0 ;
// This function defines the requirements for render complete
var endFn = function ( d , i ) {
if ( d . series === data . length - 1 && i === data [ 0 ] . values . length - 1 )
return true ;
return false ;
} ;
if ( hideable && data . length ) hideable = [ {
values : data [ 0 ] . values . map ( function ( d ) {
return {
x : d . x ,
y : 0 ,
series : d . series ,
size : 0.01
} ; }
) } ] ;
if ( stacked ) {
var parsed = d3 . layout . stack ( )
. offset ( stackOffset )
. values ( function ( d ) { return d . values } )
. y ( getY )
( ! data . length && hideable ? hideable : data ) ;
parsed . forEach ( function ( series , i ) {
// if series is non-stackable, use un-parsed data
if ( series . nonStackable ) {
data [ i ] . nonStackableSeries = nonStackableCount ++ ;
parsed [ i ] = data [ i ] ;
} else {
// don't stack this seires on top of the nonStackable seriees
if ( i > 0 && parsed [ i - 1 ] . nonStackable ) {
parsed [ i ] . values . map ( function ( d , j ) {
d . y0 -= parsed [ i - 1 ] . values [ j ] . y ;
d . y1 = d . y0 + d . y ;
} ) ;
}
}
} ) ;
data = parsed ;
}
//add series index and key to each data point for reference
data . forEach ( function ( series , i ) {
series . values . forEach ( function ( point ) {
point . series = i ;
point . key = series . key ;
} ) ;
} ) ;
// HACK for negative value stacking
if ( stacked ) {
data [ 0 ] . values . map ( function ( d , i ) {
var posBase = 0 , negBase = 0 ;
data . map ( function ( d , idx ) {
if ( ! data [ idx ] . nonStackable ) {
var f = d . values [ i ]
f . size = Math . abs ( f . y ) ;
if ( f . y < 0 ) {
f . y1 = negBase ;
negBase = negBase - f . size ;
} else
{
f . y1 = f . size + posBase ;
posBase = posBase + f . size ;
}
}
} ) ;
} ) ;
}
// Setup Scales
// remap and flatten the data for use in calculating the scales' domains
var seriesData = ( xDomain && yDomain ) ? [ ] : // if we know xDomain and yDomain, no need to calculate
data . map ( function ( d , idx ) {
return d . values . map ( function ( d , i ) {
return { x : getX ( d , i ) , y : getY ( d , i ) , y0 : d . y0 , y1 : d . y1 , idx : idx }
} )
} ) ;
x . domain ( xDomain || d3 . merge ( seriesData ) . map ( function ( d ) { return d . x } ) )
. rangeBands ( xRange || [ 0 , availableWidth ] , groupSpacing ) ;
y . domain ( yDomain || d3 . extent ( d3 . merge ( seriesData ) . map ( function ( d ) {
var domain = d . y ;
// increase the domain range if this series is stackable
if ( stacked && ! data [ d . idx ] . nonStackable ) {
if ( d . y > 0 ) {
domain = d . y1
} else {
domain = d . y1 + d . y
}
}
return domain ;
} ) . concat ( forceY ) ) )
. range ( yRange || [ availableHeight , 0 ] ) ;
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
if ( x . domain ( ) [ 0 ] === x . domain ( ) [ 1 ] )
x . domain ( ) [ 0 ] ?
x . domain ( [ x . domain ( ) [ 0 ] - x . domain ( ) [ 0 ] * 0.01 , x . domain ( ) [ 1 ] + x . domain ( ) [ 1 ] * 0.01 ] )
: x . domain ( [ - 1 , 1 ] ) ;
if ( y . domain ( ) [ 0 ] === y . domain ( ) [ 1 ] )
y . domain ( ) [ 0 ] ?
y . domain ( [ y . domain ( ) [ 0 ] + y . domain ( ) [ 0 ] * 0.01 , y . domain ( ) [ 1 ] - y . domain ( ) [ 1 ] * 0.01 ] )
: y . domain ( [ - 1 , 1 ] ) ;
x0 = x0 || x ;
y0 = y0 || y ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-multibar' ) . data ( [ data ] ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-multibar' ) ;
var defsEnter = wrapEnter . append ( 'defs' ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-groups' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
defsEnter . append ( 'clipPath' )
. attr ( 'id' , 'nv-edge-clip-' + id )
. append ( 'rect' ) ;
wrap . select ( '#nv-edge-clip-' + id + ' rect' )
. attr ( 'width' , availableWidth )
. attr ( 'height' , availableHeight ) ;
g . attr ( 'clip-path' , clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '' ) ;
var groups = wrap . select ( '.nv-groups' ) . selectAll ( '.nv-group' )
. data ( function ( d ) { return d } , function ( d , i ) { return i } ) ;
groups . enter ( ) . append ( 'g' )
. style ( 'stroke-opacity' , 1e-6 )
. style ( 'fill-opacity' , 1e-6 ) ;
var exitTransition = renderWatch
. transition ( groups . exit ( ) . selectAll ( 'rect.nv-bar' ) , 'multibarExit' , Math . min ( 100 , duration ) )
. attr ( 'y' , function ( d , i , j ) {
var yVal = y0 ( 0 ) || 0 ;
if ( stacked ) {
if ( data [ d . series ] && ! data [ d . series ] . nonStackable ) {
yVal = y0 ( d . y0 ) ;
}
}
return yVal ;
} )
. attr ( 'height' , 0 )
. remove ( ) ;
if ( exitTransition . delay )
exitTransition . delay ( function ( d , i ) {
var delay = i * ( duration / ( last _datalength + 1 ) ) - i ;
return delay ;
} ) ;
groups
. attr ( 'class' , function ( d , i ) { return 'nv-group nv-series-' + i } )
. classed ( 'hover' , function ( d ) { return d . hover } )
. style ( 'fill' , function ( d , i ) { return color ( d , i ) } )
. style ( 'stroke' , function ( d , i ) { return color ( d , i ) } ) ;
groups
. style ( 'stroke-opacity' , 1 )
. style ( 'fill-opacity' , 0.75 ) ;
var bars = groups . selectAll ( 'rect.nv-bar' )
. data ( function ( d ) { return ( hideable && ! data . length ) ? hideable . values : d . values } ) ;
bars . exit ( ) . remove ( ) ;
var barsEnter = bars . enter ( ) . append ( 'rect' )
. attr ( 'class' , function ( d , i ) { return getY ( d , i ) < 0 ? 'nv-bar negative' : 'nv-bar positive' } )
. attr ( 'x' , function ( d , i , j ) {
return stacked && ! data [ j ] . nonStackable ? 0 : ( j * x . rangeBand ( ) / data . length )
} )
. attr ( 'y' , function ( d , i , j ) { return y0 ( stacked && ! data [ j ] . nonStackable ? d . y0 : 0 ) || 0 } )
. attr ( 'height' , 0 )
. attr ( 'width' , function ( d , i , j ) { return x . rangeBand ( ) / ( stacked && ! data [ j ] . nonStackable ? 1 : data . length ) } )
. attr ( 'transform' , function ( d , i ) { return 'translate(' + x ( getX ( d , i ) ) + ',0)' ; } )
;
bars
. style ( 'fill' , function ( d , i , j ) { return color ( d , j , i ) ; } )
. style ( 'stroke' , function ( d , i , j ) { return color ( d , j , i ) ; } )
. on ( 'mouseover' , function ( d , i ) { //TODO: figure out why j works above, but not here
d3 . select ( this ) . classed ( 'hover' , true ) ;
dispatch . elementMouseover ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'mouseout' , function ( d , i ) {
d3 . select ( this ) . classed ( 'hover' , false ) ;
dispatch . elementMouseout ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'mousemove' , function ( d , i ) {
dispatch . elementMousemove ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'click' , function ( d , i ) {
var element = this ;
dispatch . elementClick ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" ) ,
event : d3 . event ,
element : element
} ) ;
d3 . event . stopPropagation ( ) ;
} )
. on ( 'dblclick' , function ( d , i ) {
dispatch . elementDblClick ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
d3 . event . stopPropagation ( ) ;
} ) ;
bars
. attr ( 'class' , function ( d , i ) { return getY ( d , i ) < 0 ? 'nv-bar negative' : 'nv-bar positive' } )
. attr ( 'transform' , function ( d , i ) { return 'translate(' + x ( getX ( d , i ) ) + ',0)' ; } )
if ( barColor ) {
if ( ! disabled ) disabled = data . map ( function ( ) { return true } ) ;
bars
. style ( 'fill' , function ( d , i , j ) { return d3 . rgb ( barColor ( d , i ) ) . darker ( disabled . map ( function ( d , i ) { return i } ) . filter ( function ( d , i ) { return ! disabled [ i ] } ) [ j ] ) . toString ( ) ; } )
. style ( 'stroke' , function ( d , i , j ) { return d3 . rgb ( barColor ( d , i ) ) . darker ( disabled . map ( function ( d , i ) { return i } ) . filter ( function ( d , i ) { return ! disabled [ i ] } ) [ j ] ) . toString ( ) ; } ) ;
}
var barSelection =
bars . watchTransition ( renderWatch , 'multibar' , Math . min ( 250 , duration ) )
. delay ( function ( d , i ) {
return i * duration / data [ 0 ] . values . length ;
} ) ;
if ( stacked ) {
barSelection
. attr ( 'y' , function ( d , i , j ) {
var yVal = 0 ;
// if stackable, stack it on top of the previous series
if ( ! data [ j ] . nonStackable ) {
yVal = y ( d . y1 ) ;
} else {
if ( getY ( d , i ) < 0 ) {
yVal = y ( 0 ) ;
} else {
if ( y ( 0 ) - y ( getY ( d , i ) ) < - 1 ) {
yVal = y ( 0 ) - 1 ;
} else {
yVal = y ( getY ( d , i ) ) || 0 ;
}
}
}
return yVal ;
} )
. attr ( 'height' , function ( d , i , j ) {
if ( ! data [ j ] . nonStackable ) {
return Math . max ( Math . abs ( y ( d . y + d . y0 ) - y ( d . y0 ) ) , 0 ) ;
} else {
return Math . max ( Math . abs ( y ( getY ( d , i ) ) - y ( 0 ) ) , 0 ) || 0 ;
}
} )
. attr ( 'x' , function ( d , i , j ) {
var width = 0 ;
if ( data [ j ] . nonStackable ) {
width = d . series * x . rangeBand ( ) / data . length ;
if ( data . length !== nonStackableCount ) {
width = data [ j ] . nonStackableSeries * x . rangeBand ( ) / ( nonStackableCount * 2 ) ;
}
}
return width ;
} )
. attr ( 'width' , function ( d , i , j ) {
if ( ! data [ j ] . nonStackable ) {
return x . rangeBand ( ) ;
} else {
// if all series are nonStacable, take the full width
var width = ( x . rangeBand ( ) / nonStackableCount ) ;
// otherwise, nonStackable graph will be only taking the half-width
// of the x rangeBand
if ( data . length !== nonStackableCount ) {
width = x . rangeBand ( ) / ( nonStackableCount * 2 ) ;
}
return width ;
}
} ) ;
}
else {
barSelection
. attr ( 'x' , function ( d , i ) {
return d . series * x . rangeBand ( ) / data . length ;
} )
. attr ( 'width' , x . rangeBand ( ) / data . length )
. attr ( 'y' , function ( d , i ) {
return getY ( d , i ) < 0 ?
y ( 0 ) :
y ( 0 ) - y ( getY ( d , i ) ) < 1 ?
y ( 0 ) - 1 :
y ( getY ( d , i ) ) || 0 ;
} )
. attr ( 'height' , function ( d , i ) {
return Math . max ( Math . abs ( y ( getY ( d , i ) ) - y ( 0 ) ) , 1 ) || 0 ;
} ) ;
}
//store old scales for use in transitions on update
x0 = x . copy ( ) ;
y0 = y . copy ( ) ;
// keep track of the last data value length for transition calculations
if ( data [ 0 ] && data [ 0 ] . values ) {
last _datalength = data [ 0 ] . values . length ;
}
} ) ;
renderWatch . renderEnd ( 'multibar immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
x : { get : function ( ) { return getX ; } , set : function ( _ ) { getX = _ ; } } ,
y : { get : function ( ) { return getY ; } , set : function ( _ ) { getY = _ ; } } ,
xScale : { get : function ( ) { return x ; } , set : function ( _ ) { x = _ ; } } ,
yScale : { get : function ( ) { return y ; } , set : function ( _ ) { y = _ ; } } ,
xDomain : { get : function ( ) { return xDomain ; } , set : function ( _ ) { xDomain = _ ; } } ,
yDomain : { get : function ( ) { return yDomain ; } , set : function ( _ ) { yDomain = _ ; } } ,
xRange : { get : function ( ) { return xRange ; } , set : function ( _ ) { xRange = _ ; } } ,
yRange : { get : function ( ) { return yRange ; } , set : function ( _ ) { yRange = _ ; } } ,
forceY : { get : function ( ) { return forceY ; } , set : function ( _ ) { forceY = _ ; } } ,
stacked : { get : function ( ) { return stacked ; } , set : function ( _ ) { stacked = _ ; } } ,
stackOffset : { get : function ( ) { return stackOffset ; } , set : function ( _ ) { stackOffset = _ ; } } ,
clipEdge : { get : function ( ) { return clipEdge ; } , set : function ( _ ) { clipEdge = _ ; } } ,
disabled : { get : function ( ) { return disabled ; } , set : function ( _ ) { disabled = _ ; } } ,
id : { get : function ( ) { return id ; } , set : function ( _ ) { id = _ ; } } ,
hideable : { get : function ( ) { return hideable ; } , set : function ( _ ) { hideable = _ ; } } ,
groupSpacing : { get : function ( ) { return groupSpacing ; } , set : function ( _ ) { groupSpacing = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
} } ,
barColor : { get : function ( ) { return barColor ; } , set : function ( _ ) {
barColor = _ ? nv . utils . getColor ( _ ) : null ;
} }
} ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ; nv . models . legend = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var margin = { top : 5 , right : 0 , bottom : 5 , left : 0 }
, width = 400
, height = 20
, getKey = function ( d ) { return d . key }
, color = nv . utils . getColor ( )
, maxKeyLength = 20 //default value for key lengths
, align = true
, padding = 32 //define how much space between legend items. - recommend 32 for furious version
, rightAlign = true
, updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
, radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
, expanded = false
, dispatch = d3 . dispatch ( 'legendClick' , 'legendDblclick' , 'legendMouseover' , 'legendMouseout' , 'stateChange' )
, vers = 'classic' //Options are "classic" and "furious"
;
function chart ( selection ) {
selection . each ( function ( data ) {
var availableWidth = width - margin . left - margin . right ,
container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-legend' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-legend' ) . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
var series = g . selectAll ( '.nv-series' )
. data ( function ( d ) {
if ( vers != 'furious' ) return d ;
return d . filter ( function ( n ) {
return expanded ? true : ! n . disengaged ;
} ) ;
} ) ;
var seriesEnter = series . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nv-series' ) ;
var seriesShape ;
var versPadding ;
switch ( vers ) {
case 'furious' :
versPadding = 23 ;
break ;
case 'classic' :
versPadding = 20 ;
}
if ( vers == 'classic' ) {
seriesEnter . append ( 'circle' )
. style ( 'stroke-width' , 2 )
. attr ( 'class' , 'nv-legend-symbol' )
. attr ( 'r' , 5 ) ;
seriesShape = series . select ( 'circle' ) ;
} else if ( vers == 'furious' ) {
seriesEnter . append ( 'rect' )
. style ( 'stroke-width' , 2 )
. attr ( 'class' , 'nv-legend-symbol' )
. attr ( 'rx' , 3 )
. attr ( 'ry' , 3 ) ;
seriesShape = series . select ( '.nv-legend-symbol' ) ;
seriesEnter . append ( 'g' )
. attr ( 'class' , 'nv-check-box' )
. property ( 'innerHTML' , '<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>' )
. attr ( 'transform' , 'translate(-10,-8)scale(0.5)' ) ;
var seriesCheckbox = series . select ( '.nv-check-box' ) ;
seriesCheckbox . each ( function ( d , i ) {
d3 . select ( this ) . selectAll ( 'path' )
. attr ( 'stroke' , setTextColor ( d , i ) ) ;
} ) ;
}
seriesEnter . append ( 'text' )
. attr ( 'text-anchor' , 'start' )
. attr ( 'class' , 'nv-legend-text' )
. attr ( 'dy' , '.32em' )
. attr ( 'dx' , '8' ) ;
var seriesText = series . select ( 'text.nv-legend-text' ) ;
series
. on ( 'mouseover' , function ( d , i ) {
dispatch . legendMouseover ( d , i ) ; //TODO: Make consistent with other event objects
} )
. on ( 'mouseout' , function ( d , i ) {
dispatch . legendMouseout ( d , i ) ;
} )
. on ( 'click' , function ( d , i ) {
dispatch . legendClick ( d , i ) ;
// make sure we re-get data in case it was modified
var data = series . data ( ) ;
if ( updateState ) {
if ( vers == 'classic' ) {
if ( radioButtonMode ) {
//Radio button mode: set every series to disabled,
// and enable the clicked series.
data . forEach ( function ( series ) { series . disabled = true } ) ;
d . disabled = false ;
}
else {
d . disabled = ! d . disabled ;
if ( data . every ( function ( series ) { return series . disabled } ) ) {
//the default behavior of NVD3 legends is, if every single series
// is disabled, turn all series' back on.
data . forEach ( function ( series ) { series . disabled = false } ) ;
}
}
} else if ( vers == 'furious' ) {
if ( expanded ) {
d . disengaged = ! d . disengaged ;
d . userDisabled = d . userDisabled == undefined ? ! ! d . disabled : d . userDisabled ;
d . disabled = d . disengaged || d . userDisabled ;
} else if ( ! expanded ) {
d . disabled = ! d . disabled ;
d . userDisabled = d . disabled ;
var engaged = data . filter ( function ( d ) { return ! d . disengaged ; } ) ;
if ( engaged . every ( function ( series ) { return series . userDisabled } ) ) {
//the default behavior of NVD3 legends is, if every single series
// is disabled, turn all series' back on.
data . forEach ( function ( series ) {
series . disabled = series . userDisabled = false ;
} ) ;
}
}
}
dispatch . stateChange ( {
disabled : data . map ( function ( d ) { return ! ! d . disabled } ) ,
disengaged : data . map ( function ( d ) { return ! ! d . disengaged } )
} ) ;
}
} )
. on ( 'dblclick' , function ( d , i ) {
if ( vers == 'furious' && expanded ) return ;
dispatch . legendDblclick ( d , i ) ;
if ( updateState ) {
// make sure we re-get data in case it was modified
var data = series . data ( ) ;
//the default behavior of NVD3 legends, when double clicking one,
// is to set all other series' to false, and make the double clicked series enabled.
data . forEach ( function ( series ) {
series . disabled = true ;
if ( vers == 'furious' ) series . userDisabled = series . disabled ;
} ) ;
d . disabled = false ;
if ( vers == 'furious' ) d . userDisabled = d . disabled ;
dispatch . stateChange ( {
disabled : data . map ( function ( d ) { return ! ! d . disabled } )
} ) ;
}
} ) ;
series . classed ( 'nv-disabled' , function ( d ) { return d . userDisabled } ) ;
series . exit ( ) . remove ( ) ;
seriesText
. attr ( 'fill' , setTextColor )
. text ( getKey ) ;
//TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
// NEW ALIGNING CODE, TODO: clean up
var legendWidth = 0 ;
if ( align ) {
var seriesWidths = [ ] ;
series . each ( function ( d , i ) {
var legendText ;
if ( getKey ( d ) . length > maxKeyLength ) {
var trimmedKey = getKey ( d ) . substring ( 0 , maxKeyLength ) ;
legendText = d3 . select ( this ) . select ( 'text' ) . text ( trimmedKey + "..." ) ;
d3 . select ( this ) . append ( "svg:title" ) . text ( getKey ( d ) ) ;
} else {
legendText = d3 . select ( this ) . select ( 'text' ) ;
}
var nodeTextLength ;
try {
nodeTextLength = legendText . node ( ) . getComputedTextLength ( ) ;
// If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
if ( nodeTextLength <= 0 ) throw Error ( ) ;
}
catch ( e ) {
nodeTextLength = nv . utils . calcApproxTextWidth ( legendText ) ;
}
seriesWidths . push ( nodeTextLength + padding ) ;
} ) ;
var seriesPerRow = 0 ;
var columnWidths = [ ] ;
legendWidth = 0 ;
while ( legendWidth < availableWidth && seriesPerRow < seriesWidths . length ) {
columnWidths [ seriesPerRow ] = seriesWidths [ seriesPerRow ] ;
legendWidth += seriesWidths [ seriesPerRow ++ ] ;
}
if ( seriesPerRow === 0 ) seriesPerRow = 1 ; //minimum of one series per row
while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
columnWidths = [ ] ;
seriesPerRow -- ;
for ( var k = 0 ; k < seriesWidths . length ; k ++ ) {
if ( seriesWidths [ k ] > ( columnWidths [ k % seriesPerRow ] || 0 ) )
columnWidths [ k % seriesPerRow ] = seriesWidths [ k ] ;
}
legendWidth = columnWidths . reduce ( function ( prev , cur , index , array ) {
return prev + cur ;
} ) ;
}
var xPositions = [ ] ;
for ( var i = 0 , curX = 0 ; i < seriesPerRow ; i ++ ) {
xPositions [ i ] = curX ;
curX += columnWidths [ i ] ;
}
series
. attr ( 'transform' , function ( d , i ) {
return 'translate(' + xPositions [ i % seriesPerRow ] + ',' + ( 5 + Math . floor ( i / seriesPerRow ) * versPadding ) + ')' ;
} ) ;
//position legend as far right as possible within the total width
if ( rightAlign ) {
g . attr ( 'transform' , 'translate(' + ( width - margin . right - legendWidth ) + ',' + margin . top + ')' ) ;
}
else {
g . attr ( 'transform' , 'translate(0' + ',' + margin . top + ')' ) ;
}
height = margin . top + margin . bottom + ( Math . ceil ( seriesWidths . length / seriesPerRow ) * versPadding ) ;
} else {
var ypos = 5 ,
newxpos = 5 ,
maxwidth = 0 ,
xpos ;
series
. attr ( 'transform' , function ( d , i ) {
var length = d3 . select ( this ) . select ( 'text' ) . node ( ) . getComputedTextLength ( ) + padding ;
xpos = newxpos ;
if ( width < margin . left + margin . right + xpos + length ) {
newxpos = xpos = 5 ;
ypos += versPadding ;
}
newxpos += length ;
if ( newxpos > maxwidth ) maxwidth = newxpos ;
if ( legendWidth < xpos + maxwidth ) {
legendWidth = xpos + maxwidth ;
}
return 'translate(' + xpos + ',' + ypos + ')' ;
} ) ;
//position legend as far right as possible within the total width
g . attr ( 'transform' , 'translate(' + ( width - margin . right - maxwidth ) + ',' + margin . top + ')' ) ;
height = margin . top + margin . bottom + ypos + 15 ;
}
if ( vers == 'furious' ) {
// Size rectangles after text is placed
seriesShape
. attr ( 'width' , function ( d , i ) {
return seriesText [ 0 ] [ i ] . getComputedTextLength ( ) + 27 ;
} )
. attr ( 'height' , 18 )
. attr ( 'y' , - 9 )
. attr ( 'x' , - 15 ) ;
// The background for the expanded legend (UI)
gEnter . insert ( 'rect' , ':first-child' )
. attr ( 'class' , 'nv-legend-bg' )
. attr ( 'fill' , '#eee' )
// .attr('stroke', '#444')
. attr ( 'opacity' , 0 ) ;
var seriesBG = g . select ( '.nv-legend-bg' ) ;
seriesBG
. transition ( ) . duration ( 300 )
. attr ( 'x' , - versPadding )
. attr ( 'width' , legendWidth + versPadding - 12 )
. attr ( 'height' , height + 10 )
. attr ( 'y' , - margin . top - 10 )
. attr ( 'opacity' , expanded ? 1 : 0 ) ;
}
seriesShape
. style ( 'fill' , setBGColor )
. style ( 'fill-opacity' , setBGOpacity )
. style ( 'stroke' , setBGColor ) ;
} ) ;
function setTextColor ( d , i ) {
if ( vers != 'furious' ) return '#000' ;
if ( expanded ) {
return d . disengaged ? '#000' : '#fff' ;
} else if ( ! expanded ) {
if ( ! d . color ) d . color = color ( d , i ) ;
return ! ! d . disabled ? d . color : '#fff' ;
}
}
function setBGColor ( d , i ) {
if ( expanded && vers == 'furious' ) {
return d . disengaged ? '#eee' : d . color || color ( d , i ) ;
} else {
return d . color || color ( d , i ) ;
}
}
function setBGOpacity ( d , i ) {
if ( expanded && vers == 'furious' ) {
return 1 ;
} else {
return ! ! d . disabled ? 0 : 1 ;
}
}
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
key : { get : function ( ) { return getKey ; } , set : function ( _ ) { getKey = _ ; } } ,
align : { get : function ( ) { return align ; } , set : function ( _ ) { align = _ ; } } ,
maxKeyLength : { get : function ( ) { return maxKeyLength ; } , set : function ( _ ) { maxKeyLength = _ ; } } ,
rightAlign : { get : function ( ) { return rightAlign ; } , set : function ( _ ) { rightAlign = _ ; } } ,
padding : { get : function ( ) { return padding ; } , set : function ( _ ) { padding = _ ; } } ,
updateState : { get : function ( ) { return updateState ; } , set : function ( _ ) { updateState = _ ; } } ,
radioButtonMode : { get : function ( ) { return radioButtonMode ; } , set : function ( _ ) { radioButtonMode = _ ; } } ,
expanded : { get : function ( ) { return expanded ; } , set : function ( _ ) { expanded = _ ; } } ,
vers : { get : function ( ) { return vers ; } , set : function ( _ ) { vers = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
} }
} ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . lineChart = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var lines = nv . models . line ( )
, xAxis = nv . models . axis ( )
, yAxis = nv . models . axis ( )
, legend = nv . models . legend ( )
, interactiveLayer = nv . interactiveGuideline ( )
, tooltip = nv . models . tooltip ( )
, lines2 = nv . models . line ( )
, x2Axis = nv . models . axis ( )
, y2Axis = nv . models . axis ( )
, brush = d3 . svg . brush ( )
;
var margin = { top : 30 , right : 20 , bottom : 50 , left : 60 }
, margin2 = { top : 0 , right : 20 , bottom : 20 , left : 60 }
, color = nv . utils . defaultColor ( )
, width = null
, height = null
, showLegend = true
, legendPosition = 'top'
, showXAxis = true
, showYAxis = true
, rightAlignYAxis = false
, useInteractiveGuideline = false
, x
, y
, x2
, y2
, focusEnable = false
, focusShowAxisY = false
, focusShowAxisX = true
, focusHeight = 50
, brushExtent = null
, state = nv . utils . state ( )
, defaultState = null
, noData = null
, dispatch = d3 . dispatch ( 'tooltipShow' , 'tooltipHide' , 'brush' , 'stateChange' , 'changeState' , 'renderEnd' )
, duration = 250
;
// set options on sub-objects for this chart
xAxis . orient ( 'bottom' ) . tickPadding ( 7 ) ;
yAxis . orient ( rightAlignYAxis ? 'right' : 'left' ) ;
lines . clipEdge ( true ) . duration ( 0 ) ;
lines2 . interactive ( false ) ;
// We don't want any points emitted for the focus chart's scatter graph.
lines2 . pointActive ( function ( d ) { return false ; } ) ;
x2Axis . orient ( 'bottom' ) . tickPadding ( 5 ) ;
y2Axis . orient ( rightAlignYAxis ? 'right' : 'left' ) ;
tooltip . valueFormatter ( function ( d , i ) {
return yAxis . tickFormat ( ) ( d , i ) ;
} ) . headerFormatter ( function ( d , i ) {
return xAxis . tickFormat ( ) ( d , i ) ;
} ) ;
interactiveLayer . tooltip . valueFormatter ( function ( d , i ) {
return yAxis . tickFormat ( ) ( d , i ) ;
} ) . headerFormatter ( function ( d , i ) {
return xAxis . tickFormat ( ) ( d , i ) ;
} ) ;
//============================================================
// Private Variables
//------------------------------------------------------------
var renderWatch = nv . utils . renderWatch ( dispatch , duration ) ;
var stateGetter = function ( data ) {
return function ( ) {
return {
active : data . map ( function ( d ) { return ! d . disabled ; } )
} ;
} ;
} ;
var stateSetter = function ( data ) {
return function ( state ) {
if ( state . active !== undefined )
data . forEach ( function ( series , i ) {
series . disabled = ! state . active [ i ] ;
} ) ;
} ;
} ;
function chart ( selection ) {
renderWatch . reset ( ) ;
renderWatch . models ( lines ) ;
renderWatch . models ( lines2 ) ;
if ( showXAxis ) renderWatch . models ( xAxis ) ;
if ( showYAxis ) renderWatch . models ( yAxis ) ;
if ( focusShowAxisX ) renderWatch . models ( x2Axis ) ;
if ( focusShowAxisY ) renderWatch . models ( y2Axis ) ;
selection . each ( function ( data ) {
var container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight1 = nv . utils . availableHeight ( height , container , margin ) - ( focusEnable ? focusHeight : 0 ) ,
availableHeight2 = focusHeight - margin2 . top - margin2 . bottom ;
chart . update = function ( ) {
if ( duration === 0 ) {
container . call ( chart ) ;
} else {
container . transition ( ) . duration ( duration ) . call ( chart ) ;
}
} ;
chart . container = this ;
state
. setter ( stateSetter ( data ) , chart . update )
. getter ( stateGetter ( data ) )
. update ( ) ;
// DEPRECATED set state.disabled
state . disabled = data . map ( function ( d ) { return ! ! d . disabled ; } ) ;
if ( ! defaultState ) {
var key ;
defaultState = { } ;
for ( key in state ) {
if ( state [ key ] instanceof Array )
defaultState [ key ] = state [ key ] . slice ( 0 ) ;
else
defaultState [ key ] = state [ key ] ;
}
}
// Display noData message if there's nothing to show.
if ( ! data || ! data . length || ! data . filter ( function ( d ) { return d . values . length ; } ) . length ) {
nv . utils . noData ( chart , container ) ;
return chart ;
} else {
container . selectAll ( '.nv-noData' ) . remove ( ) ;
}
// Setup Scales
x = lines . xScale ( ) ;
y = lines . yScale ( ) ;
x2 = lines2 . xScale ( ) ;
y2 = lines2 . yScale ( ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-lineChart' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-lineChart' ) . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-legendWrap' ) ;
var focusEnter = gEnter . append ( 'g' ) . attr ( 'class' , 'nv-focus' ) ;
focusEnter . append ( 'g' ) . attr ( 'class' , 'nv-background' ) . append ( 'rect' ) ;
focusEnter . append ( 'g' ) . attr ( 'class' , 'nv-x nv-axis' ) ;
focusEnter . append ( 'g' ) . attr ( 'class' , 'nv-y nv-axis' ) ;
focusEnter . append ( 'g' ) . attr ( 'class' , 'nv-linesWrap' ) ;
focusEnter . append ( 'g' ) . attr ( 'class' , 'nv-interactive' ) ;
var contextEnter = gEnter . append ( 'g' ) . attr ( 'class' , 'nv-context' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-background' ) . append ( 'rect' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-x nv-axis' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-y nv-axis' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-linesWrap' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-brushBackground' ) ;
contextEnter . append ( 'g' ) . attr ( 'class' , 'nv-x nv-brush' ) ;
// Legend
if ( ! showLegend ) {
g . select ( '.nv-legendWrap' ) . selectAll ( '*' ) . remove ( ) ;
} else {
legend . width ( availableWidth ) ;
g . select ( '.nv-legendWrap' )
. datum ( data )
. call ( legend ) ;
if ( legendPosition === 'bottom' ) {
wrap . select ( '.nv-legendWrap' )
. attr ( 'transform' , 'translate(0,' + ( availableHeight1 + legend . height ( ) ) + ')' ) ;
} else if ( legendPosition === 'top' ) {
if ( margin . top != legend . height ( ) ) {
margin . top = legend . height ( ) ;
availableHeight1 = nv . utils . availableHeight ( height , container , margin ) - ( focusEnable ? focusHeight : 0 ) ;
}
wrap . select ( '.nv-legendWrap' )
. attr ( 'transform' , 'translate(0,' + ( - margin . top ) + ')' ) ;
}
}
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
if ( rightAlignYAxis ) {
g . select ( ".nv-y.nv-axis" )
. attr ( "transform" , "translate(" + availableWidth + ",0)" ) ;
}
//Set up interactive layer
if ( useInteractiveGuideline ) {
interactiveLayer
. width ( availableWidth )
. height ( availableHeight1 )
. margin ( { left : margin . left , top : margin . top } )
. svgContainer ( container )
. xScale ( x ) ;
wrap . select ( ".nv-interactive" ) . call ( interactiveLayer ) ;
}
g . select ( '.nv-focus .nv-background rect' )
. attr ( 'width' , availableWidth )
. attr ( 'height' , availableHeight1 ) ;
lines
. width ( availableWidth )
. height ( availableHeight1 )
. color ( data . map ( function ( d , i ) {
return d . color || color ( d , i ) ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled ; } ) ) ;
var linesWrap = g . select ( '.nv-linesWrap' )
. datum ( data . filter ( function ( d ) { return ! d . disabled ; } ) ) ;
// Setup Main (Focus) Axes
if ( showXAxis ) {
xAxis
. scale ( x )
. _ticks ( nv . utils . calcTicksX ( availableWidth / 100 , data ) )
. tickSize ( - availableHeight1 , 0 ) ;
}
if ( showYAxis ) {
yAxis
. scale ( y )
. _ticks ( nv . utils . calcTicksY ( availableHeight1 / 36 , data ) )
. tickSize ( - availableWidth , 0 ) ;
}
//============================================================
// Update Axes
//============================================================
function updateXAxis ( ) {
if ( showXAxis ) {
g . select ( '.nv-focus .nv-x.nv-axis' )
. transition ( )
. duration ( duration )
. call ( xAxis )
;
}
}
function updateYAxis ( ) {
if ( showYAxis ) {
g . select ( '.nv-focus .nv-y.nv-axis' )
. transition ( )
. duration ( duration )
. call ( yAxis )
;
}
}
g . select ( '.nv-focus .nv-x.nv-axis' )
. attr ( 'transform' , 'translate(0,' + availableHeight1 + ')' ) ;
if ( ! focusEnable )
{
linesWrap . call ( lines ) ;
updateXAxis ( ) ;
updateYAxis ( ) ;
}
else
{
lines2
. defined ( lines . defined ( ) )
. width ( availableWidth )
. height ( availableHeight2 )
. color ( data . map ( function ( d , i ) {
return d . color || color ( d , i ) ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled ; } ) ) ;
g . select ( '.nv-context' )
. attr ( 'transform' , 'translate(0,' + ( availableHeight1 + margin . bottom + margin2 . top ) + ')' )
. style ( 'display' , focusEnable ? 'initial' : 'none' )
;
var contextLinesWrap = g . select ( '.nv-context .nv-linesWrap' )
. datum ( data . filter ( function ( d ) { return ! d . disabled ; } ) )
;
d3 . transition ( contextLinesWrap ) . call ( lines2 ) ;
// Setup Brush
brush
. x ( x2 )
. on ( 'brush' , function ( ) {
onBrush ( ) ;
} ) ;
if ( brushExtent ) brush . extent ( brushExtent ) ;
var brushBG = g . select ( '.nv-brushBackground' ) . selectAll ( 'g' )
. data ( [ brushExtent || brush . extent ( ) ] ) ;
var brushBGenter = brushBG . enter ( )
. append ( 'g' ) ;
brushBGenter . append ( 'rect' )
. attr ( 'class' , 'left' )
. attr ( 'x' , 0 )
. attr ( 'y' , 0 )
. attr ( 'height' , availableHeight2 ) ;
brushBGenter . append ( 'rect' )
. attr ( 'class' , 'right' )
. attr ( 'x' , 0 )
. attr ( 'y' , 0 )
. attr ( 'height' , availableHeight2 ) ;
var gBrush = g . select ( '.nv-x.nv-brush' )
. call ( brush ) ;
gBrush . selectAll ( 'rect' )
. attr ( 'height' , availableHeight2 ) ;
gBrush . selectAll ( '.resize' ) . append ( 'path' ) . attr ( 'd' , resizePath ) ;
onBrush ( ) ;
g . select ( '.nv-context .nv-background rect' )
. attr ( 'width' , availableWidth )
. attr ( 'height' , availableHeight2 ) ;
// Setup Secondary (Context) Axes
if ( focusShowAxisX ) {
x2Axis
. scale ( x2 )
. _ticks ( nv . utils . calcTicksX ( availableWidth / 100 , data ) )
. tickSize ( - availableHeight2 , 0 ) ;
g . select ( '.nv-context .nv-x.nv-axis' )
. attr ( 'transform' , 'translate(0,' + y2 . range ( ) [ 0 ] + ')' ) ;
d3 . transition ( g . select ( '.nv-context .nv-x.nv-axis' ) )
. call ( x2Axis ) ;
}
if ( focusShowAxisY ) {
y2Axis
. scale ( y2 )
. _ticks ( nv . utils . calcTicksY ( availableHeight2 / 36 , data ) )
. tickSize ( - availableWidth , 0 ) ;
d3 . transition ( g . select ( '.nv-context .nv-y.nv-axis' ) )
. call ( y2Axis ) ;
}
g . select ( '.nv-context .nv-x.nv-axis' )
. attr ( 'transform' , 'translate(0,' + y2 . range ( ) [ 0 ] + ')' ) ;
}
//============================================================
// Event Handling/Dispatching (in chart's scope)
//------------------------------------------------------------
legend . dispatch . on ( 'stateChange' , function ( newState ) {
for ( var key in newState )
state [ key ] = newState [ key ] ;
dispatch . stateChange ( state ) ;
chart . update ( ) ;
} ) ;
interactiveLayer . dispatch . on ( 'elementMousemove' , function ( e ) {
lines . clearHighlights ( ) ;
var singlePoint , pointIndex , pointXLocation , allData = [ ] ;
data
. filter ( function ( series , i ) {
series . seriesIndex = i ;
return ! series . disabled && ! series . disableTooltip ;
} )
. forEach ( function ( series , i ) {
var extent = focusEnable ? ( brush . empty ( ) ? x2 . domain ( ) : brush . extent ( ) ) : x . domain ( ) ;
var currentValues = series . values . filter ( function ( d , i ) {
return lines . x ( ) ( d , i ) >= extent [ 0 ] && lines . x ( ) ( d , i ) <= extent [ 1 ] ;
} ) ;
pointIndex = nv . interactiveBisect ( currentValues , e . pointXValue , lines . x ( ) ) ;
var point = currentValues [ pointIndex ] ;
var pointYValue = chart . y ( ) ( point , pointIndex ) ;
if ( pointYValue !== null ) {
lines . highlightPoint ( series . seriesIndex , pointIndex , true ) ;
}
if ( point === undefined ) return ;
if ( singlePoint === undefined ) singlePoint = point ;
if ( pointXLocation === undefined ) pointXLocation = chart . xScale ( ) ( chart . x ( ) ( point , pointIndex ) ) ;
allData . push ( {
key : series . key ,
value : pointYValue ,
color : color ( series , series . seriesIndex ) ,
data : point
} ) ;
} ) ;
//Highlight the tooltip entry based on which point the mouse is closest to.
if ( allData . length > 2 ) {
var yValue = chart . yScale ( ) . invert ( e . mouseY ) ;
var domainExtent = Math . abs ( chart . yScale ( ) . domain ( ) [ 0 ] - chart . yScale ( ) . domain ( ) [ 1 ] ) ;
var threshold = 0.03 * domainExtent ;
var indexToHighlight = nv . nearestValueIndex ( allData . map ( function ( d ) { return d . value ; } ) , yValue , threshold ) ;
if ( indexToHighlight !== null )
allData [ indexToHighlight ] . highlight = true ;
}
var defaultValueFormatter = function ( d , i ) {
return d == null ? "N/A" : yAxis . tickFormat ( ) ( d ) ;
} ;
interactiveLayer . tooltip
. chartContainer ( chart . container . parentNode )
. valueFormatter ( interactiveLayer . tooltip . valueFormatter ( ) || defaultValueFormatter )
. data ( {
value : chart . x ( ) ( singlePoint , pointIndex ) ,
index : pointIndex ,
series : allData
} ) ( ) ;
interactiveLayer . renderGuideLine ( pointXLocation ) ;
} ) ;
interactiveLayer . dispatch . on ( 'elementClick' , function ( e ) {
var pointXLocation , allData = [ ] ;
data . filter ( function ( series , i ) {
series . seriesIndex = i ;
return ! series . disabled ;
} ) . forEach ( function ( series ) {
var pointIndex = nv . interactiveBisect ( series . values , e . pointXValue , chart . x ( ) ) ;
var point = series . values [ pointIndex ] ;
if ( typeof point === 'undefined' ) return ;
if ( typeof pointXLocation === 'undefined' ) pointXLocation = chart . xScale ( ) ( chart . x ( ) ( point , pointIndex ) ) ;
var yPos = chart . yScale ( ) ( chart . y ( ) ( point , pointIndex ) ) ;
allData . push ( {
point : point ,
pointIndex : pointIndex ,
pos : [ pointXLocation , yPos ] ,
seriesIndex : series . seriesIndex ,
series : series
} ) ;
} ) ;
lines . dispatch . elementClick ( allData ) ;
} ) ;
interactiveLayer . dispatch . on ( "elementMouseout" , function ( e ) {
lines . clearHighlights ( ) ;
} ) ;
dispatch . on ( 'changeState' , function ( e ) {
if ( typeof e . disabled !== 'undefined' && data . length === e . disabled . length ) {
data . forEach ( function ( series , i ) {
series . disabled = e . disabled [ i ] ;
} ) ;
state . disabled = e . disabled ;
}
chart . update ( ) ;
} ) ;
//============================================================
// Functions
//------------------------------------------------------------
// Taken from crossfilter (http://square.github.com/crossfilter/)
function resizePath ( d ) {
var e = + ( d == 'e' ) ,
x = e ? 1 : - 1 ,
y = availableHeight2 / 3 ;
return 'M' + ( 0.5 * x ) + ',' + y
+ 'A6,6 0 0 ' + e + ' ' + ( 6.5 * x ) + ',' + ( y + 6 )
+ 'V' + ( 2 * y - 6 )
+ 'A6,6 0 0 ' + e + ' ' + ( 0.5 * x ) + ',' + ( 2 * y )
+ 'Z'
+ 'M' + ( 2.5 * x ) + ',' + ( y + 8 )
+ 'V' + ( 2 * y - 8 )
+ 'M' + ( 4.5 * x ) + ',' + ( y + 8 )
+ 'V' + ( 2 * y - 8 ) ;
}
function updateBrushBG ( ) {
if ( ! brush . empty ( ) ) brush . extent ( brushExtent ) ;
brushBG
. data ( [ brush . empty ( ) ? x2 . domain ( ) : brushExtent ] )
. each ( function ( d , i ) {
var leftWidth = x2 ( d [ 0 ] ) - x . range ( ) [ 0 ] ,
rightWidth = availableWidth - x2 ( d [ 1 ] ) ;
d3 . select ( this ) . select ( '.left' )
. attr ( 'width' , leftWidth < 0 ? 0 : leftWidth ) ;
d3 . select ( this ) . select ( '.right' )
. attr ( 'x' , x2 ( d [ 1 ] ) )
. attr ( 'width' , rightWidth < 0 ? 0 : rightWidth ) ;
} ) ;
}
function onBrush ( ) {
brushExtent = brush . empty ( ) ? null : brush . extent ( ) ;
var extent = brush . empty ( ) ? x2 . domain ( ) : brush . extent ( ) ;
//The brush extent cannot be less than one. If it is, don't update the line chart.
if ( Math . abs ( extent [ 0 ] - extent [ 1 ] ) <= 1 ) {
return ;
}
dispatch . brush ( { extent : extent , brush : brush } ) ;
updateBrushBG ( ) ;
// Update Main (Focus)
var focusLinesWrap = g . select ( '.nv-focus .nv-linesWrap' )
. datum (
data
. filter ( function ( d ) { return ! d . disabled ; } )
. map ( function ( d , i ) {
return {
key : d . key ,
area : d . area ,
classed : d . classed ,
values : d . values . filter ( function ( d , i ) {
return lines . x ( ) ( d , i ) >= extent [ 0 ] && lines . x ( ) ( d , i ) <= extent [ 1 ] ;
} ) ,
disableTooltip : d . disableTooltip
} ;
} )
) ;
focusLinesWrap . transition ( ) . duration ( duration ) . call ( lines ) ;
// Update Main (Focus) Axes
updateXAxis ( ) ;
updateYAxis ( ) ;
}
} ) ;
renderWatch . renderEnd ( 'lineChart immediate' ) ;
return chart ;
}
//============================================================
// Event Handling/Dispatching (out of chart's scope)
//------------------------------------------------------------
lines . dispatch . on ( 'elementMouseover.tooltip' , function ( evt ) {
if ( ! evt . series . disableTooltip ) {
tooltip . data ( evt ) . hidden ( false ) ;
}
} ) ;
lines . dispatch . on ( 'elementMouseout.tooltip' , function ( evt ) {
tooltip . hidden ( true ) ;
} ) ;
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart . dispatch = dispatch ;
chart . lines = lines ;
chart . lines2 = lines2 ;
chart . legend = legend ;
chart . xAxis = xAxis ;
chart . x2Axis = x2Axis ;
chart . yAxis = yAxis ;
chart . y2Axis = y2Axis ;
chart . interactiveLayer = interactiveLayer ;
chart . tooltip = tooltip ;
chart . state = state ;
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
showLegend : { get : function ( ) { return showLegend ; } , set : function ( _ ) { showLegend = _ ; } } ,
legendPosition : { get : function ( ) { return legendPosition ; } , set : function ( _ ) { legendPosition = _ ; } } ,
showXAxis : { get : function ( ) { return showXAxis ; } , set : function ( _ ) { showXAxis = _ ; } } ,
showYAxis : { get : function ( ) { return showYAxis ; } , set : function ( _ ) { showYAxis = _ ; } } ,
focusEnable : { get : function ( ) { return focusEnable ; } , set : function ( _ ) { focusEnable = _ ; } } ,
focusHeight : { get : function ( ) { return height2 ; } , set : function ( _ ) { focusHeight = _ ; } } ,
focusShowAxisX : { get : function ( ) { return focusShowAxisX ; } , set : function ( _ ) { focusShowAxisX = _ ; } } ,
focusShowAxisY : { get : function ( ) { return focusShowAxisY ; } , set : function ( _ ) { focusShowAxisY = _ ; } } ,
brushExtent : { get : function ( ) { return brushExtent ; } , set : function ( _ ) { brushExtent = _ ; } } ,
defaultState : { get : function ( ) { return defaultState ; } , set : function ( _ ) { defaultState = _ ; } } ,
noData : { get : function ( ) { return noData ; } , set : function ( _ ) { noData = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
lines . duration ( duration ) ;
xAxis . duration ( duration ) ;
x2Axis . duration ( duration ) ;
yAxis . duration ( duration ) ;
y2Axis . duration ( duration ) ;
} } ,
focusMargin : { get : function ( ) { return margin2 ; } , set : function ( _ ) {
margin2 . top = _ . top !== undefined ? _ . top : margin2 . top ;
margin2 . right = _ . right !== undefined ? _ . right : margin2 . right ;
margin2 . bottom = _ . bottom !== undefined ? _ . bottom : margin2 . bottom ;
margin2 . left = _ . left !== undefined ? _ . left : margin2 . left ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
legend . color ( color ) ;
lines . color ( color ) ;
} } ,
interpolate : { get : function ( ) { return lines . interpolate ( ) ; } , set : function ( _ ) {
lines . interpolate ( _ ) ;
lines2 . interpolate ( _ ) ;
} } ,
xTickFormat : { get : function ( ) { return xAxis . tickFormat ( ) ; } , set : function ( _ ) {
xAxis . tickFormat ( _ ) ;
x2Axis . tickFormat ( _ ) ;
} } ,
yTickFormat : { get : function ( ) { return yAxis . tickFormat ( ) ; } , set : function ( _ ) {
yAxis . tickFormat ( _ ) ;
y2Axis . tickFormat ( _ ) ;
} } ,
x : { get : function ( ) { return lines . x ( ) ; } , set : function ( _ ) {
lines . x ( _ ) ;
lines2 . x ( _ ) ;
} } ,
y : { get : function ( ) { return lines . y ( ) ; } , set : function ( _ ) {
lines . y ( _ ) ;
lines2 . y ( _ ) ;
} } ,
rightAlignYAxis : { get : function ( ) { return rightAlignYAxis ; } , set : function ( _ ) {
rightAlignYAxis = _ ;
yAxis . orient ( rightAlignYAxis ? 'right' : 'left' ) ;
} } ,
useInteractiveGuideline : { get : function ( ) { return useInteractiveGuideline ; } , set : function ( _ ) {
useInteractiveGuideline = _ ;
if ( useInteractiveGuideline ) {
lines . interactive ( false ) ;
lines . useVoronoi ( false ) ;
}
} }
} ) ;
nv . utils . inheritOptions ( chart , lines ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . lineWithFocusChart = function ( ) {
return nv . models . lineChart ( )
. margin ( { bottom : 30 } )
. focusEnable ( true ) ;
} ;
nv . models . line = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var scatter = nv . models . scatter ( )
;
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = 960
, height = 500
, container = null
, strokeWidth = 1.5
, color = nv . utils . defaultColor ( ) // a function that returns a color
, getX = function ( d ) { return d . x } // accessor to get the x value from a data point
, getY = function ( d ) { return d . y } // accessor to get the y value from a data point
, defined = function ( d , i ) { return ! isNaN ( getY ( d , i ) ) && getY ( d , i ) !== null } // allows a line to be not continuous when it is not defined
, isArea = function ( d ) { return d . area } // decides if a line is an area or just a line
, clipEdge = false // if true, masks lines within x and y scale
, x //can be accessed via chart.xScale()
, y //can be accessed via chart.yScale()
, interpolate = "linear" // controls the line interpolation
, duration = 250
, dispatch = d3 . dispatch ( 'elementClick' , 'elementMouseover' , 'elementMouseout' , 'renderEnd' )
;
scatter
. pointSize ( 16 ) // default size
. pointDomain ( [ 16 , 256 ] ) //set to speed up calculation, needs to be unset if there is a custom size accessor
;
//============================================================
//============================================================
// Private Variables
//------------------------------------------------------------
var x0 , y0 //used to store previous scales
, renderWatch = nv . utils . renderWatch ( dispatch , duration )
;
//============================================================
function chart ( selection ) {
renderWatch . reset ( ) ;
renderWatch . models ( scatter ) ;
selection . each ( function ( data ) {
container = d3 . select ( this ) ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
nv . utils . initSVG ( container ) ;
// Setup Scales
x = scatter . xScale ( ) ;
y = scatter . yScale ( ) ;
x0 = x0 || x ;
y0 = y0 || y ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-line' ) . data ( [ data ] ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-line' ) ;
var defsEnter = wrapEnter . append ( 'defs' ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-groups' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-scatterWrap' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
scatter
. width ( availableWidth )
. height ( availableHeight ) ;
var scatterWrap = wrap . select ( '.nv-scatterWrap' ) ;
scatterWrap . call ( scatter ) ;
defsEnter . append ( 'clipPath' )
. attr ( 'id' , 'nv-edge-clip-' + scatter . id ( ) )
. append ( 'rect' ) ;
wrap . select ( '#nv-edge-clip-' + scatter . id ( ) + ' rect' )
. attr ( 'width' , availableWidth )
. attr ( 'height' , ( availableHeight > 0 ) ? availableHeight : 0 ) ;
g . attr ( 'clip-path' , clipEdge ? 'url(#nv-edge-clip-' + scatter . id ( ) + ')' : '' ) ;
scatterWrap
. attr ( 'clip-path' , clipEdge ? 'url(#nv-edge-clip-' + scatter . id ( ) + ')' : '' ) ;
var groups = wrap . select ( '.nv-groups' ) . selectAll ( '.nv-group' )
. data ( function ( d ) { return d } , function ( d ) { return d . key } ) ;
groups . enter ( ) . append ( 'g' )
. style ( 'stroke-opacity' , 1e-6 )
. style ( 'stroke-width' , function ( d ) { return d . strokeWidth || strokeWidth } )
. style ( 'fill-opacity' , 1e-6 ) ;
groups . exit ( ) . remove ( ) ;
groups
. attr ( 'class' , function ( d , i ) {
return ( d . classed || '' ) + ' nv-group nv-series-' + i ;
} )
. classed ( 'hover' , function ( d ) { return d . hover } )
. style ( 'fill' , function ( d , i ) { return color ( d , i ) } )
. style ( 'stroke' , function ( d , i ) { return color ( d , i ) } ) ;
groups . watchTransition ( renderWatch , 'line: groups' )
. style ( 'stroke-opacity' , 1 )
. style ( 'fill-opacity' , function ( d ) { return d . fillOpacity || . 5 } ) ;
var areaPaths = groups . selectAll ( 'path.nv-area' )
. data ( function ( d ) { return isArea ( d ) ? [ d ] : [ ] } ) ; // this is done differently than lines because I need to check if series is an area
areaPaths . enter ( ) . append ( 'path' )
. attr ( 'class' , 'nv-area' )
. attr ( 'd' , function ( d ) {
return d3 . svg . area ( )
. interpolate ( interpolate )
. defined ( defined )
. x ( function ( d , i ) { return nv . utils . NaNtoZero ( x0 ( getX ( d , i ) ) ) } )
. y0 ( function ( d , i ) { return nv . utils . NaNtoZero ( y0 ( getY ( d , i ) ) ) } )
. y1 ( function ( d , i ) { return y0 ( y . domain ( ) [ 0 ] <= 0 ? y . domain ( ) [ 1 ] >= 0 ? 0 : y . domain ( ) [ 1 ] : y . domain ( ) [ 0 ] ) } )
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
. apply ( this , [ d . values ] )
} ) ;
groups . exit ( ) . selectAll ( 'path.nv-area' )
. remove ( ) ;
areaPaths . watchTransition ( renderWatch , 'line: areaPaths' )
. attr ( 'd' , function ( d ) {
return d3 . svg . area ( )
. interpolate ( interpolate )
. defined ( defined )
. x ( function ( d , i ) { return nv . utils . NaNtoZero ( x ( getX ( d , i ) ) ) } )
. y0 ( function ( d , i ) { return nv . utils . NaNtoZero ( y ( getY ( d , i ) ) ) } )
. y1 ( function ( d , i ) { return y ( y . domain ( ) [ 0 ] <= 0 ? y . domain ( ) [ 1 ] >= 0 ? 0 : y . domain ( ) [ 1 ] : y . domain ( ) [ 0 ] ) } )
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
. apply ( this , [ d . values ] )
} ) ;
var linePaths = groups . selectAll ( 'path.nv-line' )
. data ( function ( d ) { return [ d . values ] } ) ;
linePaths . enter ( ) . append ( 'path' )
. attr ( 'class' , 'nv-line' )
. attr ( 'd' ,
d3 . svg . line ( )
. interpolate ( interpolate )
. defined ( defined )
. x ( function ( d , i ) { return nv . utils . NaNtoZero ( x0 ( getX ( d , i ) ) ) } )
. y ( function ( d , i ) { return nv . utils . NaNtoZero ( y0 ( getY ( d , i ) ) ) } )
) ;
linePaths . watchTransition ( renderWatch , 'line: linePaths' )
. attr ( 'd' ,
d3 . svg . line ( )
. interpolate ( interpolate )
. defined ( defined )
. x ( function ( d , i ) { return nv . utils . NaNtoZero ( x ( getX ( d , i ) ) ) } )
. y ( function ( d , i ) { return nv . utils . NaNtoZero ( y ( getY ( d , i ) ) ) } )
) ;
//store old scales for use in transitions on update
x0 = x . copy ( ) ;
y0 = y . copy ( ) ;
} ) ;
renderWatch . renderEnd ( 'line immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . scatter = scatter ;
// Pass through events
scatter . dispatch . on ( 'elementClick' , function ( ) { dispatch . elementClick . apply ( this , arguments ) ; } ) ;
scatter . dispatch . on ( 'elementMouseover' , function ( ) { dispatch . elementMouseover . apply ( this , arguments ) ; } ) ;
scatter . dispatch . on ( 'elementMouseout' , function ( ) { dispatch . elementMouseout . apply ( this , arguments ) ; } ) ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
defined : { get : function ( ) { return defined ; } , set : function ( _ ) { defined = _ ; } } ,
interpolate : { get : function ( ) { return interpolate ; } , set : function ( _ ) { interpolate = _ ; } } ,
clipEdge : { get : function ( ) { return clipEdge ; } , set : function ( _ ) { clipEdge = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
scatter . duration ( duration ) ;
} } ,
isArea : { get : function ( ) { return isArea ; } , set : function ( _ ) {
isArea = d3 . functor ( _ ) ;
} } ,
x : { get : function ( ) { return getX ; } , set : function ( _ ) {
getX = _ ;
scatter . x ( _ ) ;
} } ,
y : { get : function ( ) { return getY ; } , set : function ( _ ) {
getY = _ ;
scatter . y ( _ ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
scatter . color ( color ) ;
} }
} ) ;
nv . utils . inheritOptions ( chart , scatter ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . discreteBarChart = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var discretebar = nv . models . discreteBar ( )
, xAxis = nv . models . axis ( )
, yAxis = nv . models . axis ( )
, legend = nv . models . legend ( )
, tooltip = nv . models . tooltip ( )
;
var margin = { top : 15 , right : 10 , bottom : 50 , left : 60 }
, width = null
, height = null
, color = nv . utils . getColor ( )
, showLegend = false
, showXAxis = true
, showYAxis = true
, rightAlignYAxis = false
, staggerLabels = false
, wrapLabels = false
, rotateLabels = 0
, x
, y
, noData = null
, dispatch = d3 . dispatch ( 'beforeUpdate' , 'renderEnd' )
, duration = 250
;
xAxis
. orient ( 'bottom' )
. showMaxMin ( false )
. tickFormat ( function ( d ) { return d } )
;
yAxis
. orient ( ( rightAlignYAxis ) ? 'right' : 'left' )
. tickFormat ( d3 . format ( ',.1f' ) )
;
tooltip
. duration ( 0 )
. headerEnabled ( false )
. valueFormatter ( function ( d , i ) {
return yAxis . tickFormat ( ) ( d , i ) ;
} )
. keyFormatter ( function ( d , i ) {
return xAxis . tickFormat ( ) ( d , i ) ;
} ) ;
//============================================================
// Private Variables
//------------------------------------------------------------
var renderWatch = nv . utils . renderWatch ( dispatch , duration ) ;
function chart ( selection ) {
renderWatch . reset ( ) ;
renderWatch . models ( discretebar ) ;
if ( showXAxis ) renderWatch . models ( xAxis ) ;
if ( showYAxis ) renderWatch . models ( yAxis ) ;
selection . each ( function ( data ) {
var container = d3 . select ( this ) ,
that = this ;
nv . utils . initSVG ( container ) ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
chart . update = function ( ) {
dispatch . beforeUpdate ( ) ;
container . transition ( ) . duration ( duration ) . call ( chart ) ;
} ;
chart . container = this ;
// Display No Data message if there's nothing to show.
if ( ! data || ! data . length || ! data . filter ( function ( d ) { return d . values . length } ) . length ) {
nv . utils . noData ( chart , container ) ;
return chart ;
} else {
container . selectAll ( '.nv-noData' ) . remove ( ) ;
}
// Setup Scales
x = discretebar . xScale ( ) ;
y = discretebar . yScale ( ) . clamp ( true ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-discreteBarWithAxes' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-discreteBarWithAxes' ) . append ( 'g' ) ;
var defsEnter = gEnter . append ( 'defs' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-x nv-axis' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-y nv-axis' )
. append ( 'g' ) . attr ( 'class' , 'nv-zeroLine' )
. append ( 'line' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-barsWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-legendWrap' ) ;
g . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
// Legend
if ( ! showLegend ) {
g . select ( '.nv-legendWrap' ) . selectAll ( '*' ) . remove ( ) ;
} else {
legend . width ( availableWidth ) ;
g . select ( '.nv-legendWrap' )
. datum ( data )
. call ( legend ) ;
if ( margin . top != legend . height ( ) ) {
margin . top = legend . height ( ) ;
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
}
wrap . select ( '.nv-legendWrap' )
. attr ( 'transform' , 'translate(0,' + ( - margin . top ) + ')' )
}
if ( rightAlignYAxis ) {
g . select ( ".nv-y.nv-axis" )
. attr ( "transform" , "translate(" + availableWidth + ",0)" ) ;
}
// Main Chart Component(s)
discretebar
. width ( availableWidth )
. height ( availableHeight ) ;
var barsWrap = g . select ( '.nv-barsWrap' )
. datum ( data . filter ( function ( d ) { return ! d . disabled } ) ) ;
barsWrap . transition ( ) . call ( discretebar ) ;
defsEnter . append ( 'clipPath' )
. attr ( 'id' , 'nv-x-label-clip-' + discretebar . id ( ) )
. append ( 'rect' ) ;
g . select ( '#nv-x-label-clip-' + discretebar . id ( ) + ' rect' )
. attr ( 'width' , x . rangeBand ( ) * ( staggerLabels ? 2 : 1 ) )
. attr ( 'height' , 16 )
. attr ( 'x' , - x . rangeBand ( ) / ( staggerLabels ? 1 : 2 ) ) ;
// Setup Axes
if ( showXAxis ) {
xAxis
. scale ( x )
. _ticks ( nv . utils . calcTicksX ( availableWidth / 100 , data ) )
. tickSize ( - availableHeight , 0 ) ;
g . select ( '.nv-x.nv-axis' )
. attr ( 'transform' , 'translate(0,' + ( y . range ( ) [ 0 ] + ( ( discretebar . showValues ( ) && y . domain ( ) [ 0 ] < 0 ) ? 16 : 0 ) ) + ')' ) ;
g . select ( '.nv-x.nv-axis' ) . call ( xAxis ) ;
var xTicks = g . select ( '.nv-x.nv-axis' ) . selectAll ( 'g' ) ;
if ( staggerLabels ) {
xTicks
. selectAll ( 'text' )
. attr ( 'transform' , function ( d , i , j ) { return 'translate(0,' + ( j % 2 == 0 ? '5' : '17' ) + ')' } )
}
if ( rotateLabels ) {
xTicks
. selectAll ( '.tick text' )
. attr ( 'transform' , 'rotate(' + rotateLabels + ' 0,0)' )
. style ( 'text-anchor' , rotateLabels > 0 ? 'start' : 'end' ) ;
}
if ( wrapLabels ) {
g . selectAll ( '.tick text' )
. call ( nv . utils . wrapTicks , chart . xAxis . rangeBand ( ) )
}
}
if ( showYAxis ) {
yAxis
. scale ( y )
. _ticks ( nv . utils . calcTicksY ( availableHeight / 36 , data ) )
. tickSize ( - availableWidth , 0 ) ;
g . select ( '.nv-y.nv-axis' ) . call ( yAxis ) ;
}
// Zero line
g . select ( ".nv-zeroLine line" )
. attr ( "x1" , 0 )
. attr ( "x2" , ( rightAlignYAxis ) ? - availableWidth : availableWidth )
. attr ( "y1" , y ( 0 ) )
. attr ( "y2" , y ( 0 ) )
;
} ) ;
renderWatch . renderEnd ( 'discreteBar chart immediate' ) ;
return chart ;
}
//============================================================
// Event Handling/Dispatching (out of chart's scope)
//------------------------------------------------------------
discretebar . dispatch . on ( 'elementMouseover.tooltip' , function ( evt ) {
evt [ 'series' ] = {
key : chart . x ( ) ( evt . data ) ,
value : chart . y ( ) ( evt . data ) ,
color : evt . color
} ;
tooltip . data ( evt ) . hidden ( false ) ;
} ) ;
discretebar . dispatch . on ( 'elementMouseout.tooltip' , function ( evt ) {
tooltip . hidden ( true ) ;
} ) ;
discretebar . dispatch . on ( 'elementMousemove.tooltip' , function ( evt ) {
tooltip ( ) ;
} ) ;
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . discretebar = discretebar ;
chart . legend = legend ;
chart . xAxis = xAxis ;
chart . yAxis = yAxis ;
chart . tooltip = tooltip ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
showLegend : { get : function ( ) { return showLegend ; } , set : function ( _ ) { showLegend = _ ; } } ,
staggerLabels : { get : function ( ) { return staggerLabels ; } , set : function ( _ ) { staggerLabels = _ ; } } ,
rotateLabels : { get : function ( ) { return rotateLabels ; } , set : function ( _ ) { rotateLabels = _ ; } } ,
wrapLabels : { get : function ( ) { return wrapLabels ; } , set : function ( _ ) { wrapLabels = ! ! _ ; } } ,
showXAxis : { get : function ( ) { return showXAxis ; } , set : function ( _ ) { showXAxis = _ ; } } ,
showYAxis : { get : function ( ) { return showYAxis ; } , set : function ( _ ) { showYAxis = _ ; } } ,
noData : { get : function ( ) { return noData ; } , set : function ( _ ) { noData = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
discretebar . duration ( duration ) ;
xAxis . duration ( duration ) ;
yAxis . duration ( duration ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
discretebar . color ( color ) ;
legend . color ( color ) ;
} } ,
rightAlignYAxis : { get : function ( ) { return rightAlignYAxis ; } , set : function ( _ ) {
rightAlignYAxis = _ ;
yAxis . orient ( ( _ ) ? 'right' : 'left' ) ;
} }
} ) ;
nv . utils . inheritOptions ( chart , discretebar ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
}
nv . models . pieChart = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var pie = nv . models . pie ( ) ;
var legend = nv . models . legend ( ) ;
var tooltip = nv . models . tooltip ( ) ;
var margin = { top : 30 , right : 20 , bottom : 20 , left : 20 }
, width = null
, height = null
, showLegend = true
, legendPosition = "top"
, color = nv . utils . defaultColor ( )
, state = nv . utils . state ( )
, defaultState = null
, noData = null
, duration = 250
, dispatch = d3 . dispatch ( 'stateChange' , 'changeState' , 'renderEnd' )
;
tooltip
. duration ( 0 )
. headerEnabled ( false )
. valueFormatter ( function ( d , i ) {
return pie . valueFormat ( ) ( d , i ) ;
} ) ;
//============================================================
// Private Variables
//------------------------------------------------------------
var renderWatch = nv . utils . renderWatch ( dispatch ) ;
var stateGetter = function ( data ) {
return function ( ) {
return {
active : data . map ( function ( d ) { return ! d . disabled } )
} ;
}
} ;
var stateSetter = function ( data ) {
return function ( state ) {
if ( state . active !== undefined ) {
data . forEach ( function ( series , i ) {
series . disabled = ! state . active [ i ] ;
} ) ;
}
}
} ;
//============================================================
// Chart function
//------------------------------------------------------------
function chart ( selection ) {
renderWatch . reset ( ) ;
renderWatch . models ( pie ) ;
selection . each ( function ( data ) {
var container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
var that = this ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
chart . update = function ( ) { container . transition ( ) . call ( chart ) ; } ;
chart . container = this ;
state . setter ( stateSetter ( data ) , chart . update )
. getter ( stateGetter ( data ) )
. update ( ) ;
//set state.disabled
state . disabled = data . map ( function ( d ) { return ! ! d . disabled } ) ;
if ( ! defaultState ) {
var key ;
defaultState = { } ;
for ( key in state ) {
if ( state [ key ] instanceof Array )
defaultState [ key ] = state [ key ] . slice ( 0 ) ;
else
defaultState [ key ] = state [ key ] ;
}
}
// Display No Data message if there's nothing to show.
if ( ! data || ! data . length ) {
nv . utils . noData ( chart , container ) ;
return chart ;
} else {
container . selectAll ( '.nv-noData' ) . remove ( ) ;
}
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-pieChart' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-pieChart' ) . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-pieWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-legendWrap' ) ;
// Legend
if ( ! showLegend ) {
g . select ( '.nv-legendWrap' ) . selectAll ( '*' ) . remove ( ) ;
} else {
if ( legendPosition === "top" ) {
legend . width ( availableWidth ) . key ( pie . x ( ) ) ;
wrap . select ( '.nv-legendWrap' )
. datum ( data )
. call ( legend ) ;
if ( margin . top != legend . height ( ) ) {
margin . top = legend . height ( ) ;
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
}
wrap . select ( '.nv-legendWrap' )
. attr ( 'transform' , 'translate(0,' + ( - margin . top ) + ')' ) ;
} else if ( legendPosition === "right" ) {
var legendWidth = nv . models . legend ( ) . width ( ) ;
if ( availableWidth / 2 < legendWidth ) {
legendWidth = ( availableWidth / 2 )
}
legend . height ( availableHeight ) . key ( pie . x ( ) ) ;
legend . width ( legendWidth ) ;
availableWidth -= legend . width ( ) ;
wrap . select ( '.nv-legendWrap' )
. datum ( data )
. call ( legend )
. attr ( 'transform' , 'translate(' + ( availableWidth ) + ',0)' ) ;
}
}
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
// Main Chart Component(s)
pie . width ( availableWidth ) . height ( availableHeight ) ;
var pieWrap = g . select ( '.nv-pieWrap' ) . datum ( [ data ] ) ;
d3 . transition ( pieWrap ) . call ( pie ) ;
//============================================================
// Event Handling/Dispatching (in chart's scope)
//------------------------------------------------------------
legend . dispatch . on ( 'stateChange' , function ( newState ) {
for ( var key in newState ) {
state [ key ] = newState [ key ] ;
}
dispatch . stateChange ( state ) ;
chart . update ( ) ;
} ) ;
// Update chart from a state object passed to event handler
dispatch . on ( 'changeState' , function ( e ) {
if ( typeof e . disabled !== 'undefined' ) {
data . forEach ( function ( series , i ) {
series . disabled = e . disabled [ i ] ;
} ) ;
state . disabled = e . disabled ;
}
chart . update ( ) ;
} ) ;
} ) ;
renderWatch . renderEnd ( 'pieChart immediate' ) ;
return chart ;
}
//============================================================
// Event Handling/Dispatching (out of chart's scope)
//------------------------------------------------------------
pie . dispatch . on ( 'elementMouseover.tooltip' , function ( evt ) {
evt [ 'series' ] = {
key : chart . x ( ) ( evt . data ) ,
value : chart . y ( ) ( evt . data ) ,
color : evt . color
} ;
tooltip . data ( evt ) . hidden ( false ) ;
} ) ;
pie . dispatch . on ( 'elementMouseout.tooltip' , function ( evt ) {
tooltip . hidden ( true ) ;
} ) ;
pie . dispatch . on ( 'elementMousemove.tooltip' , function ( evt ) {
tooltip ( ) ;
} ) ;
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart . legend = legend ;
chart . dispatch = dispatch ;
chart . pie = pie ;
chart . tooltip = tooltip ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
// use Object get/set functionality to map between vars and chart functions
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
noData : { get : function ( ) { return noData ; } , set : function ( _ ) { noData = _ ; } } ,
showLegend : { get : function ( ) { return showLegend ; } , set : function ( _ ) { showLegend = _ ; } } ,
legendPosition : { get : function ( ) { return legendPosition ; } , set : function ( _ ) { legendPosition = _ ; } } ,
defaultState : { get : function ( ) { return defaultState ; } , set : function ( _ ) { defaultState = _ ; } } ,
// options that require extra logic in the setter
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = _ ;
legend . color ( color ) ;
pie . color ( color ) ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
} } ,
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} }
} ) ;
nv . utils . inheritOptions ( chart , pie ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
//TODO: consider deprecating by adding necessary features to multiBar model
nv . models . discreteBar = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = 960
, height = 500
, id = Math . floor ( Math . random ( ) * 10000 ) //Create semi-unique ID in case user doesn't select one
, container
, x = d3 . scale . ordinal ( )
, y = d3 . scale . linear ( )
, getX = function ( d ) { return d . x }
, getY = function ( d ) { return d . y }
, forceY = [ 0 ] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
, color = nv . utils . defaultColor ( )
, showValues = false
, valueFormat = d3 . format ( ',.2f' )
, xDomain
, yDomain
, xRange
, yRange
, dispatch = d3 . dispatch ( 'chartClick' , 'elementClick' , 'elementDblClick' , 'elementMouseover' , 'elementMouseout' , 'elementMousemove' , 'renderEnd' )
, rectClass = 'discreteBar'
, duration = 250
;
//============================================================
// Private Variables
//------------------------------------------------------------
var x0 , y0 ;
var renderWatch = nv . utils . renderWatch ( dispatch , duration ) ;
function chart ( selection ) {
renderWatch . reset ( ) ;
selection . each ( function ( data ) {
var availableWidth = width - margin . left - margin . right ,
availableHeight = height - margin . top - margin . bottom ;
container = d3 . select ( this ) ;
nv . utils . initSVG ( container ) ;
//add series index to each data point for reference
data . forEach ( function ( series , i ) {
series . values . forEach ( function ( point ) {
point . series = i ;
} ) ;
} ) ;
// Setup Scales
// remap and flatten the data for use in calculating the scales' domains
var seriesData = ( xDomain && yDomain ) ? [ ] : // if we know xDomain and yDomain, no need to calculate
data . map ( function ( d ) {
return d . values . map ( function ( d , i ) {
return { x : getX ( d , i ) , y : getY ( d , i ) , y0 : d . y0 }
} )
} ) ;
x . domain ( xDomain || d3 . merge ( seriesData ) . map ( function ( d ) { return d . x } ) )
. rangeBands ( xRange || [ 0 , availableWidth ] , . 1 ) ;
y . domain ( yDomain || d3 . extent ( d3 . merge ( seriesData ) . map ( function ( d ) { return d . y } ) . concat ( forceY ) ) ) ;
// If showValues, pad the Y axis range to account for label height
if ( showValues ) y . range ( yRange || [ availableHeight - ( y . domain ( ) [ 0 ] < 0 ? 12 : 0 ) , y . domain ( ) [ 1 ] > 0 ? 12 : 0 ] ) ;
else y . range ( yRange || [ availableHeight , 0 ] ) ;
//store old scales if they exist
x0 = x0 || x ;
y0 = y0 || y . copy ( ) . range ( [ y ( 0 ) , y ( 0 ) ] ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-discretebar' ) . data ( [ data ] ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-discretebar' ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-groups' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
//TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
var groups = wrap . select ( '.nv-groups' ) . selectAll ( '.nv-group' )
. data ( function ( d ) { return d } , function ( d ) { return d . key } ) ;
groups . enter ( ) . append ( 'g' )
. style ( 'stroke-opacity' , 1e-6 )
. style ( 'fill-opacity' , 1e-6 ) ;
groups . exit ( )
. watchTransition ( renderWatch , 'discreteBar: exit groups' )
. style ( 'stroke-opacity' , 1e-6 )
. style ( 'fill-opacity' , 1e-6 )
. remove ( ) ;
groups
. attr ( 'class' , function ( d , i ) { return 'nv-group nv-series-' + i } )
. classed ( 'hover' , function ( d ) { return d . hover } ) ;
groups
. watchTransition ( renderWatch , 'discreteBar: groups' )
. style ( 'stroke-opacity' , 1 )
. style ( 'fill-opacity' , . 75 ) ;
var bars = groups . selectAll ( 'g.nv-bar' )
. data ( function ( d ) { return d . values } ) ;
bars . exit ( ) . remove ( ) ;
var barsEnter = bars . enter ( ) . append ( 'g' )
. attr ( 'transform' , function ( d , i , j ) {
return 'translate(' + ( x ( getX ( d , i ) ) + x . rangeBand ( ) * . 05 ) + ', ' + y ( 0 ) + ')'
} )
. on ( 'mouseover' , function ( d , i ) { //TODO: figure out why j works above, but not here
d3 . select ( this ) . classed ( 'hover' , true ) ;
dispatch . elementMouseover ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'mouseout' , function ( d , i ) {
d3 . select ( this ) . classed ( 'hover' , false ) ;
dispatch . elementMouseout ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'mousemove' , function ( d , i ) {
dispatch . elementMousemove ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} )
. on ( 'click' , function ( d , i ) {
var element = this ;
dispatch . elementClick ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" ) ,
event : d3 . event ,
element : element
} ) ;
d3 . event . stopPropagation ( ) ;
} )
. on ( 'dblclick' , function ( d , i ) {
dispatch . elementDblClick ( {
data : d ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
d3 . event . stopPropagation ( ) ;
} ) ;
barsEnter . append ( 'rect' )
. attr ( 'height' , 0 )
. attr ( 'width' , x . rangeBand ( ) * . 9 / data . length )
if ( showValues ) {
barsEnter . append ( 'text' )
. attr ( 'text-anchor' , 'middle' )
;
bars . select ( 'text' )
. text ( function ( d , i ) { return valueFormat ( getY ( d , i ) ) } )
. watchTransition ( renderWatch , 'discreteBar: bars text' )
. attr ( 'x' , x . rangeBand ( ) * . 9 / 2 )
. attr ( 'y' , function ( d , i ) { return getY ( d , i ) < 0 ? y ( getY ( d , i ) ) - y ( 0 ) + 12 : - 4 } )
;
} else {
bars . selectAll ( 'text' ) . remove ( ) ;
}
bars
. attr ( 'class' , function ( d , i ) { return getY ( d , i ) < 0 ? 'nv-bar negative' : 'nv-bar positive' } )
. style ( 'fill' , function ( d , i ) { return d . color || color ( d , i ) } )
. style ( 'stroke' , function ( d , i ) { return d . color || color ( d , i ) } )
. select ( 'rect' )
. attr ( 'class' , rectClass )
. watchTransition ( renderWatch , 'discreteBar: bars rect' )
. attr ( 'width' , x . rangeBand ( ) * . 9 / data . length ) ;
bars . watchTransition ( renderWatch , 'discreteBar: bars' )
//.delay(function(d,i) { return i * 1200 / data[0].values.length })
. attr ( 'transform' , function ( d , i ) {
var left = x ( getX ( d , i ) ) + x . rangeBand ( ) * . 05 ,
top = getY ( d , i ) < 0 ?
y ( 0 ) :
y ( 0 ) - y ( getY ( d , i ) ) < 1 ?
y ( 0 ) - 1 : //make 1 px positive bars show up above y=0
y ( getY ( d , i ) ) ;
return 'translate(' + left + ', ' + top + ')'
} )
. select ( 'rect' )
. attr ( 'height' , function ( d , i ) {
return Math . max ( Math . abs ( y ( getY ( d , i ) ) - y ( 0 ) ) , 1 )
} ) ;
//store old scales for use in transitions on update
x0 = x . copy ( ) ;
y0 = y . copy ( ) ;
} ) ;
renderWatch . renderEnd ( 'discreteBar immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
forceY : { get : function ( ) { return forceY ; } , set : function ( _ ) { forceY = _ ; } } ,
showValues : { get : function ( ) { return showValues ; } , set : function ( _ ) { showValues = _ ; } } ,
x : { get : function ( ) { return getX ; } , set : function ( _ ) { getX = _ ; } } ,
y : { get : function ( ) { return getY ; } , set : function ( _ ) { getY = _ ; } } ,
xScale : { get : function ( ) { return x ; } , set : function ( _ ) { x = _ ; } } ,
yScale : { get : function ( ) { return y ; } , set : function ( _ ) { y = _ ; } } ,
xDomain : { get : function ( ) { return xDomain ; } , set : function ( _ ) { xDomain = _ ; } } ,
yDomain : { get : function ( ) { return yDomain ; } , set : function ( _ ) { yDomain = _ ; } } ,
xRange : { get : function ( ) { return xRange ; } , set : function ( _ ) { xRange = _ ; } } ,
yRange : { get : function ( ) { return yRange ; } , set : function ( _ ) { yRange = _ ; } } ,
valueFormat : { get : function ( ) { return valueFormat ; } , set : function ( _ ) { valueFormat = _ ; } } ,
id : { get : function ( ) { return id ; } , set : function ( _ ) { id = _ ; } } ,
rectClass : { get : function ( ) { return rectClass ; } , set : function ( _ ) { rectClass = _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
renderWatch . reset ( duration ) ;
} }
} ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . multiBarChart = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var multibar = nv . models . multiBar ( )
, xAxis = nv . models . axis ( )
, yAxis = nv . models . axis ( )
, interactiveLayer = nv . interactiveGuideline ( )
, legend = nv . models . legend ( )
, controls = nv . models . legend ( )
, tooltip = nv . models . tooltip ( )
;
var margin = { top : 30 , right : 20 , bottom : 50 , left : 60 }
, width = null
, height = null
, color = nv . utils . defaultColor ( )
, showControls = true
, controlLabels = { }
, showLegend = true
, showXAxis = true
, showYAxis = true
, rightAlignYAxis = false
, reduceXTicks = true // if false a tick will show for every data point
, staggerLabels = false
, wrapLabels = false
, rotateLabels = 0
, x //can be accessed via chart.xScale()
, y //can be accessed via chart.yScale()
, state = nv . utils . state ( )
, defaultState = null
, noData = null
, dispatch = d3 . dispatch ( 'stateChange' , 'changeState' , 'renderEnd' )
, controlWidth = function ( ) { return showControls ? 180 : 0 }
, duration = 250
, useInteractiveGuideline = false
;
state . stacked = false // DEPRECATED Maintained for backward compatibility
multibar . stacked ( false ) ;
xAxis
. orient ( 'bottom' )
. tickPadding ( 7 )
. showMaxMin ( false )
. tickFormat ( function ( d ) { return d } )
;
yAxis
. orient ( ( rightAlignYAxis ) ? 'right' : 'left' )
. tickFormat ( d3 . format ( ',.1f' ) )
;
tooltip
. duration ( 0 )
. valueFormatter ( function ( d , i ) {
return yAxis . tickFormat ( ) ( d , i ) ;
} )
. headerFormatter ( function ( d , i ) {
return xAxis . tickFormat ( ) ( d , i ) ;
} ) ;
controls . updateState ( false ) ;
//============================================================
// Private Variables
//------------------------------------------------------------
var renderWatch = nv . utils . renderWatch ( dispatch ) ;
var stacked = false ;
var stateGetter = function ( data ) {
return function ( ) {
return {
active : data . map ( function ( d ) { return ! d . disabled } ) ,
stacked : stacked
} ;
}
} ;
var stateSetter = function ( data ) {
return function ( state ) {
if ( state . stacked !== undefined )
stacked = state . stacked ;
if ( state . active !== undefined )
data . forEach ( function ( series , i ) {
series . disabled = ! state . active [ i ] ;
} ) ;
}
} ;
function chart ( selection ) {
renderWatch . reset ( ) ;
renderWatch . models ( multibar ) ;
if ( showXAxis ) renderWatch . models ( xAxis ) ;
if ( showYAxis ) renderWatch . models ( yAxis ) ;
selection . each ( function ( data ) {
var container = d3 . select ( this ) ,
that = this ;
nv . utils . initSVG ( container ) ;
var availableWidth = nv . utils . availableWidth ( width , container , margin ) ,
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
chart . update = function ( ) {
if ( duration === 0 )
container . call ( chart ) ;
else
container . transition ( )
. duration ( duration )
. call ( chart ) ;
} ;
chart . container = this ;
state
. setter ( stateSetter ( data ) , chart . update )
. getter ( stateGetter ( data ) )
. update ( ) ;
// DEPRECATED set state.disableddisabled
state . disabled = data . map ( function ( d ) { return ! ! d . disabled } ) ;
if ( ! defaultState ) {
var key ;
defaultState = { } ;
for ( key in state ) {
if ( state [ key ] instanceof Array )
defaultState [ key ] = state [ key ] . slice ( 0 ) ;
else
defaultState [ key ] = state [ key ] ;
}
}
// Display noData message if there's nothing to show.
if ( ! data || ! data . length || ! data . filter ( function ( d ) { return d . values . length } ) . length ) {
nv . utils . noData ( chart , container )
return chart ;
} else {
container . selectAll ( '.nv-noData' ) . remove ( ) ;
}
// Setup Scales
x = multibar . xScale ( ) ;
y = multibar . yScale ( ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( 'g.nv-wrap.nv-multiBarWithLegend' ) . data ( [ data ] ) ;
var gEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-multiBarWithLegend' ) . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-x nv-axis' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-y nv-axis' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-barsWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-legendWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-controlsWrap' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-interactive' ) ;
// Legend
if ( ! showLegend ) {
g . select ( '.nv-legendWrap' ) . selectAll ( '*' ) . remove ( ) ;
} else {
legend . width ( availableWidth - controlWidth ( ) ) ;
g . select ( '.nv-legendWrap' )
. datum ( data )
. call ( legend ) ;
if ( margin . top != legend . height ( ) ) {
margin . top = legend . height ( ) ;
availableHeight = nv . utils . availableHeight ( height , container , margin ) ;
}
g . select ( '.nv-legendWrap' )
. attr ( 'transform' , 'translate(' + controlWidth ( ) + ',' + ( - margin . top ) + ')' ) ;
}
// Controls
if ( ! showControls ) {
g . select ( '.nv-controlsWrap' ) . selectAll ( '*' ) . remove ( ) ;
} else {
var controlsData = [
{ key : controlLabels . grouped || 'Grouped' , disabled : multibar . stacked ( ) } ,
{ key : controlLabels . stacked || 'Stacked' , disabled : ! multibar . stacked ( ) }
] ;
controls . width ( controlWidth ( ) ) . color ( [ '#444' , '#444' , '#444' ] ) ;
g . select ( '.nv-controlsWrap' )
. datum ( controlsData )
. attr ( 'transform' , 'translate(0,' + ( - margin . top ) + ')' )
. call ( controls ) ;
}
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
if ( rightAlignYAxis ) {
g . select ( ".nv-y.nv-axis" )
. attr ( "transform" , "translate(" + availableWidth + ",0)" ) ;
}
// Main Chart Component(s)
multibar
. disabled ( data . map ( function ( series ) { return series . disabled } ) )
. width ( availableWidth )
. height ( availableHeight )
. color ( data . map ( function ( d , i ) {
return d . color || color ( d , i ) ;
} ) . filter ( function ( d , i ) { return ! data [ i ] . disabled } ) ) ;
var barsWrap = g . select ( '.nv-barsWrap' )
. datum ( data . filter ( function ( d ) { return ! d . disabled } ) ) ;
barsWrap . call ( multibar ) ;
// Setup Axes
if ( showXAxis ) {
xAxis
. scale ( x )
. _ticks ( nv . utils . calcTicksX ( availableWidth / 100 , data ) )
. tickSize ( - availableHeight , 0 ) ;
g . select ( '.nv-x.nv-axis' )
. attr ( 'transform' , 'translate(0,' + y . range ( ) [ 0 ] + ')' ) ;
g . select ( '.nv-x.nv-axis' )
. call ( xAxis ) ;
var xTicks = g . select ( '.nv-x.nv-axis > g' ) . selectAll ( 'g' ) ;
xTicks
. selectAll ( 'line, text' )
. style ( 'opacity' , 1 )
if ( staggerLabels ) {
var getTranslate = function ( x , y ) {
return "translate(" + x + "," + y + ")" ;
} ;
var staggerUp = 5 , staggerDown = 17 ; //pixels to stagger by
// Issue #140
xTicks
. selectAll ( "text" )
. attr ( 'transform' , function ( d , i , j ) {
return getTranslate ( 0 , ( j % 2 == 0 ? staggerUp : staggerDown ) ) ;
} ) ;
var totalInBetweenTicks = d3 . selectAll ( ".nv-x.nv-axis .nv-wrap g g text" ) [ 0 ] . length ;
g . selectAll ( ".nv-x.nv-axis .nv-axisMaxMin text" )
. attr ( "transform" , function ( d , i ) {
return getTranslate ( 0 , ( i === 0 || totalInBetweenTicks % 2 !== 0 ) ? staggerDown : staggerUp ) ;
} ) ;
}
if ( wrapLabels ) {
g . selectAll ( '.tick text' )
. call ( nv . utils . wrapTicks , chart . xAxis . rangeBand ( ) )
}
if ( reduceXTicks )
xTicks
. filter ( function ( d , i ) {
return i % Math . ceil ( data [ 0 ] . values . length / ( availableWidth / 100 ) ) !== 0 ;
} )
. selectAll ( 'text, line' )
. style ( 'opacity' , 0 ) ;
if ( rotateLabels )
xTicks
. selectAll ( '.tick text' )
. attr ( 'transform' , 'rotate(' + rotateLabels + ' 0,0)' )
. style ( 'text-anchor' , rotateLabels > 0 ? 'start' : 'end' ) ;
g . select ( '.nv-x.nv-axis' ) . selectAll ( 'g.nv-axisMaxMin text' )
. style ( 'opacity' , 1 ) ;
}
if ( showYAxis ) {
yAxis
. scale ( y )
. _ticks ( nv . utils . calcTicksY ( availableHeight / 36 , data ) )
. tickSize ( - availableWidth , 0 ) ;
g . select ( '.nv-y.nv-axis' )
. call ( yAxis ) ;
}
//Set up interactive layer
if ( useInteractiveGuideline ) {
interactiveLayer
. width ( availableWidth )
. height ( availableHeight )
. margin ( { left : margin . left , top : margin . top } )
. svgContainer ( container )
. xScale ( x ) ;
wrap . select ( ".nv-interactive" ) . call ( interactiveLayer ) ;
}
//============================================================
// Event Handling/Dispatching (in chart's scope)
//------------------------------------------------------------
legend . dispatch . on ( 'stateChange' , function ( newState ) {
for ( var key in newState )
state [ key ] = newState [ key ] ;
dispatch . stateChange ( state ) ;
chart . update ( ) ;
} ) ;
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' :
case controlLabels . grouped :
multibar . stacked ( false ) ;
break ;
case 'Stacked' :
case controlLabels . stacked :
multibar . stacked ( true ) ;
break ;
}
state . stacked = multibar . stacked ( ) ;
dispatch . stateChange ( state ) ;
chart . update ( ) ;
} ) ;
// Update chart from a state object passed to event handler
dispatch . on ( 'changeState' , function ( e ) {
if ( typeof e . disabled !== 'undefined' ) {
data . forEach ( function ( series , i ) {
series . disabled = e . disabled [ i ] ;
} ) ;
state . disabled = e . disabled ;
}
if ( typeof e . stacked !== 'undefined' ) {
multibar . stacked ( e . stacked ) ;
state . stacked = e . stacked ;
stacked = e . stacked ;
}
chart . update ( ) ;
} ) ;
if ( useInteractiveGuideline ) {
interactiveLayer . dispatch . on ( 'elementMousemove' , function ( e ) {
if ( e . pointXValue == undefined ) return ;
var singlePoint , pointIndex , pointXLocation , xValue , allData = [ ] ;
data
. filter ( function ( series , i ) {
series . seriesIndex = i ;
return ! series . disabled ;
} )
. forEach ( function ( series , i ) {
pointIndex = x . domain ( ) . indexOf ( e . pointXValue )
var point = series . values [ pointIndex ] ;
if ( point === undefined ) return ;
xValue = point . x ;
if ( singlePoint === undefined ) singlePoint = point ;
if ( pointXLocation === undefined ) pointXLocation = e . mouseX
allData . push ( {
key : series . key ,
value : chart . y ( ) ( point , pointIndex ) ,
color : color ( series , series . seriesIndex ) ,
data : series . values [ pointIndex ]
} ) ;
} ) ;
interactiveLayer . tooltip
. chartContainer ( that . parentNode )
. data ( {
value : xValue ,
index : pointIndex ,
series : allData
} ) ( ) ;
interactiveLayer . renderGuideLine ( pointXLocation ) ;
} ) ;
interactiveLayer . dispatch . on ( "elementMouseout" , function ( e ) {
interactiveLayer . tooltip . hidden ( true ) ;
} ) ;
}
else {
multibar . dispatch . on ( 'elementMouseover.tooltip' , function ( evt ) {
evt . value = chart . x ( ) ( evt . data ) ;
evt [ 'series' ] = {
key : evt . data . key ,
value : chart . y ( ) ( evt . data ) ,
color : evt . color
} ;
tooltip . data ( evt ) . hidden ( false ) ;
} ) ;
multibar . dispatch . on ( 'elementMouseout.tooltip' , function ( evt ) {
tooltip . hidden ( true ) ;
} ) ;
multibar . dispatch . on ( 'elementMousemove.tooltip' , function ( evt ) {
tooltip ( ) ;
} ) ;
}
} ) ;
renderWatch . renderEnd ( 'multibarchart immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart . dispatch = dispatch ;
chart . multibar = multibar ;
chart . legend = legend ;
chart . controls = controls ;
chart . xAxis = xAxis ;
chart . yAxis = yAxis ;
chart . state = state ;
chart . tooltip = tooltip ;
chart . interactiveLayer = interactiveLayer ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
showLegend : { get : function ( ) { return showLegend ; } , set : function ( _ ) { showLegend = _ ; } } ,
showControls : { get : function ( ) { return showControls ; } , set : function ( _ ) { showControls = _ ; } } ,
controlLabels : { get : function ( ) { return controlLabels ; } , set : function ( _ ) { controlLabels = _ ; } } ,
showXAxis : { get : function ( ) { return showXAxis ; } , set : function ( _ ) { showXAxis = _ ; } } ,
showYAxis : { get : function ( ) { return showYAxis ; } , set : function ( _ ) { showYAxis = _ ; } } ,
defaultState : { get : function ( ) { return defaultState ; } , set : function ( _ ) { defaultState = _ ; } } ,
noData : { get : function ( ) { return noData ; } , set : function ( _ ) { noData = _ ; } } ,
reduceXTicks : { get : function ( ) { return reduceXTicks ; } , set : function ( _ ) { reduceXTicks = _ ; } } ,
rotateLabels : { get : function ( ) { return rotateLabels ; } , set : function ( _ ) { rotateLabels = _ ; } } ,
staggerLabels : { get : function ( ) { return staggerLabels ; } , set : function ( _ ) { staggerLabels = _ ; } } ,
wrapLabels : { get : function ( ) { return wrapLabels ; } , set : function ( _ ) { wrapLabels = ! ! _ ; } } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = _ . top !== undefined ? _ . top : margin . top ;
margin . right = _ . right !== undefined ? _ . right : margin . right ;
margin . bottom = _ . bottom !== undefined ? _ . bottom : margin . bottom ;
margin . left = _ . left !== undefined ? _ . left : margin . left ;
} } ,
duration : { get : function ( ) { return duration ; } , set : function ( _ ) {
duration = _ ;
multibar . duration ( duration ) ;
xAxis . duration ( duration ) ;
yAxis . duration ( duration ) ;
renderWatch . reset ( duration ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
legend . color ( color ) ;
} } ,
rightAlignYAxis : { get : function ( ) { return rightAlignYAxis ; } , set : function ( _ ) {
rightAlignYAxis = _ ;
yAxis . orient ( rightAlignYAxis ? 'right' : 'left' ) ;
} } ,
useInteractiveGuideline : { get : function ( ) { return useInteractiveGuideline ; } , set : function ( _ ) {
useInteractiveGuideline = _ ;
} } ,
barColor : { get : function ( ) { return multibar . barColor ; } , set : function ( _ ) {
multibar . barColor ( _ ) ;
legend . color ( function ( d , i ) { return d3 . rgb ( '#ccc' ) . darker ( i * 1.5 ) . toString ( ) ; } )
} }
} ) ;
nv . utils . inheritOptions ( chart , multibar ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . models . pie = function ( ) {
"use strict" ;
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var margin = { top : 0 , right : 0 , bottom : 0 , left : 0 }
, width = 500
, height = 500
, getX = function ( d ) { return d . x }
, getY = function ( d ) { return d . y }
, id = Math . floor ( Math . random ( ) * 10000 ) //Create semi-unique ID in case user doesn't select one
, container = null
, color = nv . utils . defaultColor ( )
, valueFormat = d3 . format ( ',.2f' )
, showLabels = true
, labelsOutside = false
, labelType = "key"
, labelThreshold = . 02 //if slice percentage is under this, don't show label
, donut = false
, title = false
, growOnHover = true
, titleOffset = 0
, labelSunbeamLayout = false
, startAngle = false
, padAngle = false
, endAngle = false
, cornerRadius = 0
, donutRatio = 0.5
, arcsRadius = [ ]
, dispatch = d3 . dispatch ( 'chartClick' , 'elementClick' , 'elementDblClick' , 'elementMouseover' , 'elementMouseout' , 'elementMousemove' , 'renderEnd' )
;
var arcs = [ ] ;
var arcsOver = [ ] ;
//============================================================
// chart function
//------------------------------------------------------------
var renderWatch = nv . utils . renderWatch ( dispatch ) ;
function chart ( selection ) {
renderWatch . reset ( ) ;
selection . each ( function ( data ) {
var availableWidth = width - margin . left - margin . right
, availableHeight = height - margin . top - margin . bottom
, radius = Math . min ( availableWidth , availableHeight ) / 2
, arcsRadiusOuter = [ ]
, arcsRadiusInner = [ ]
;
container = d3 . select ( this )
if ( arcsRadius . length === 0 ) {
var outer = radius - radius / 5 ;
var inner = donutRatio * radius ;
for ( var i = 0 ; i < data [ 0 ] . length ; i ++ ) {
arcsRadiusOuter . push ( outer ) ;
arcsRadiusInner . push ( inner ) ;
}
} else {
if ( growOnHover ) {
arcsRadiusOuter = arcsRadius . map ( function ( d ) { return ( d . outer - d . outer / 5 ) * radius ; } ) ;
arcsRadiusInner = arcsRadius . map ( function ( d ) { return ( d . inner - d . inner / 5 ) * radius ; } ) ;
donutRatio = d3 . min ( arcsRadius . map ( function ( d ) { return ( d . inner - d . inner / 5 ) ; } ) ) ;
} else {
arcsRadiusOuter = arcsRadius . map ( function ( d ) { return d . outer * radius ; } ) ;
arcsRadiusInner = arcsRadius . map ( function ( d ) { return d . inner * radius ; } ) ;
donutRatio = d3 . min ( arcsRadius . map ( function ( d ) { return d . inner ; } ) ) ;
}
}
nv . utils . initSVG ( container ) ;
// Setup containers and skeleton of chart
var wrap = container . selectAll ( '.nv-wrap.nv-pie' ) . data ( data ) ;
var wrapEnter = wrap . enter ( ) . append ( 'g' ) . attr ( 'class' , 'nvd3 nv-wrap nv-pie nv-chart-' + id ) ;
var gEnter = wrapEnter . append ( 'g' ) ;
var g = wrap . select ( 'g' ) ;
var g _pie = gEnter . append ( 'g' ) . attr ( 'class' , 'nv-pie' ) ;
gEnter . append ( 'g' ) . attr ( 'class' , 'nv-pieLabels' ) ;
wrap . attr ( 'transform' , 'translate(' + margin . left + ',' + margin . top + ')' ) ;
g . select ( '.nv-pie' ) . attr ( 'transform' , 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')' ) ;
g . select ( '.nv-pieLabels' ) . attr ( 'transform' , 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')' ) ;
//
container . on ( 'click' , function ( d , i ) {
dispatch . chartClick ( {
data : d ,
index : i ,
pos : d3 . event ,
id : id
} ) ;
} ) ;
arcs = [ ] ;
arcsOver = [ ] ;
for ( var i = 0 ; i < data [ 0 ] . length ; i ++ ) {
var arc = d3 . svg . arc ( ) . outerRadius ( arcsRadiusOuter [ i ] ) ;
var arcOver = d3 . svg . arc ( ) . outerRadius ( arcsRadiusOuter [ i ] + 5 ) ;
if ( startAngle !== false ) {
arc . startAngle ( startAngle ) ;
arcOver . startAngle ( startAngle ) ;
}
if ( endAngle !== false ) {
arc . endAngle ( endAngle ) ;
arcOver . endAngle ( endAngle ) ;
}
if ( donut ) {
arc . innerRadius ( arcsRadiusInner [ i ] ) ;
arcOver . innerRadius ( arcsRadiusInner [ i ] ) ;
}
if ( arc . cornerRadius && cornerRadius ) {
arc . cornerRadius ( cornerRadius ) ;
arcOver . cornerRadius ( cornerRadius ) ;
}
arcs . push ( arc ) ;
arcsOver . push ( arcOver ) ;
}
// Setup the Pie chart and choose the data element
var pie = d3 . layout . pie ( )
. sort ( null )
. value ( function ( d ) { return d . disabled ? 0 : getY ( d ) } ) ;
// padAngle added in d3 3.5
if ( pie . padAngle && padAngle ) {
pie . padAngle ( padAngle ) ;
}
// if title is specified and donut, put it in the middle
if ( donut && title ) {
g _pie . append ( "text" ) . attr ( 'class' , 'nv-pie-title' ) ;
wrap . select ( '.nv-pie-title' )
. style ( "text-anchor" , "middle" )
. text ( function ( d ) {
return title ;
} )
. style ( "font-size" , ( Math . min ( availableWidth , availableHeight ) ) * donutRatio * 2 / ( title . length + 2 ) + "px" )
. attr ( "dy" , "0.35em" ) // trick to vertically center text
. attr ( 'transform' , function ( d , i ) {
return 'translate(0, ' + titleOffset + ')' ;
} ) ;
}
var slices = wrap . select ( '.nv-pie' ) . selectAll ( '.nv-slice' ) . data ( pie ) ;
var pieLabels = wrap . select ( '.nv-pieLabels' ) . selectAll ( '.nv-label' ) . data ( pie ) ;
slices . exit ( ) . remove ( ) ;
pieLabels . exit ( ) . remove ( ) ;
var ae = slices . enter ( ) . append ( 'g' ) ;
ae . attr ( 'class' , 'nv-slice' ) ;
ae . on ( 'mouseover' , function ( d , i ) {
d3 . select ( this ) . classed ( 'hover' , true ) ;
if ( growOnHover ) {
d3 . select ( this ) . select ( "path" ) . transition ( )
. duration ( 70 )
. attr ( "d" , arcsOver [ i ] ) ;
}
dispatch . elementMouseover ( {
data : d . data ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} ) ;
ae . on ( 'mouseout' , function ( d , i ) {
d3 . select ( this ) . classed ( 'hover' , false ) ;
if ( growOnHover ) {
d3 . select ( this ) . select ( "path" ) . transition ( )
. duration ( 50 )
. attr ( "d" , arcs [ i ] ) ;
}
dispatch . elementMouseout ( { data : d . data , index : i } ) ;
} ) ;
ae . on ( 'mousemove' , function ( d , i ) {
dispatch . elementMousemove ( { data : d . data , index : i } ) ;
} ) ;
ae . on ( 'click' , function ( d , i ) {
var element = this ;
dispatch . elementClick ( {
data : d . data ,
index : i ,
color : d3 . select ( this ) . style ( "fill" ) ,
event : d3 . event ,
element : element
} ) ;
} ) ;
ae . on ( 'dblclick' , function ( d , i ) {
dispatch . elementDblClick ( {
data : d . data ,
index : i ,
color : d3 . select ( this ) . style ( "fill" )
} ) ;
} ) ;
slices . attr ( 'fill' , function ( d , i ) { return color ( d . data , i ) ; } ) ;
slices . attr ( 'stroke' , function ( d , i ) { return color ( d . data , i ) ; } ) ;
var paths = ae . append ( 'path' ) . each ( function ( d ) {
this . _current = d ;
} ) ;
slices . select ( 'path' )
. transition ( )
. attr ( 'd' , function ( d , i ) { return arcs [ i ] ( d ) ; } )
. attrTween ( 'd' , arcTween ) ;
if ( showLabels ) {
// This does the normal label
var labelsArc = [ ] ;
for ( var i = 0 ; i < data [ 0 ] . length ; i ++ ) {
labelsArc . push ( arcs [ i ] ) ;
if ( labelsOutside ) {
if ( donut ) {
labelsArc [ i ] = d3 . svg . arc ( ) . outerRadius ( arcs [ i ] . outerRadius ( ) ) ;
if ( startAngle !== false ) labelsArc [ i ] . startAngle ( startAngle ) ;
if ( endAngle !== false ) labelsArc [ i ] . endAngle ( endAngle ) ;
}
} else if ( ! donut ) {
labelsArc [ i ] . innerRadius ( 0 ) ;
}
}
pieLabels . enter ( ) . append ( "g" ) . classed ( "nv-label" , true ) . each ( function ( d , i ) {
var group = d3 . select ( this ) ;
group . attr ( 'transform' , function ( d , i ) {
if ( labelSunbeamLayout ) {
d . outerRadius = arcsRadiusOuter [ i ] + 10 ; // Set Outer Coordinate
d . innerRadius = arcsRadiusOuter [ i ] + 15 ; // Set Inner Coordinate
var rotateAngle = ( d . startAngle + d . endAngle ) / 2 * ( 180 / Math . PI ) ;
if ( ( d . startAngle + d . endAngle ) / 2 < Math . PI ) {
rotateAngle -= 90 ;
} else {
rotateAngle += 90 ;
}
return 'translate(' + labelsArc [ i ] . centroid ( d ) + ') rotate(' + rotateAngle + ')' ;
} else {
d . outerRadius = radius + 10 ; // Set Outer Coordinate
d . innerRadius = radius + 15 ; // Set Inner Coordinate
return 'translate(' + labelsArc [ i ] . centroid ( d ) + ')'
}
} ) ;
group . append ( 'rect' )
. style ( 'stroke' , '#fff' )
. style ( 'fill' , '#fff' )
. attr ( "rx" , 3 )
. attr ( "ry" , 3 ) ;
group . append ( 'text' )
. style ( 'text-anchor' , labelSunbeamLayout ? ( ( d . startAngle + d . endAngle ) / 2 < Math . PI ? 'start' : 'end' ) : 'middle' ) //center the text on it's origin or begin/end if orthogonal aligned
. style ( 'fill' , '#000' )
} ) ;
var labelLocationHash = { } ;
var avgHeight = 14 ;
var avgWidth = 140 ;
var createHashKey = function ( coordinates ) {
return Math . floor ( coordinates [ 0 ] / avgWidth ) * avgWidth + ',' + Math . floor ( coordinates [ 1 ] / avgHeight ) * avgHeight ;
} ;
var getSlicePercentage = function ( d ) {
return ( d . endAngle - d . startAngle ) / ( 2 * Math . PI ) ;
} ;
pieLabels . watchTransition ( renderWatch , 'pie labels' ) . attr ( 'transform' , function ( d , i ) {
if ( labelSunbeamLayout ) {
d . outerRadius = arcsRadiusOuter [ i ] + 10 ; // Set Outer Coordinate
d . innerRadius = arcsRadiusOuter [ i ] + 15 ; // Set Inner Coordinate
var rotateAngle = ( d . startAngle + d . endAngle ) / 2 * ( 180 / Math . PI ) ;
if ( ( d . startAngle + d . endAngle ) / 2 < Math . PI ) {
rotateAngle -= 90 ;
} else {
rotateAngle += 90 ;
}
return 'translate(' + labelsArc [ i ] . centroid ( d ) + ') rotate(' + rotateAngle + ')' ;
} else {
d . outerRadius = radius + 10 ; // Set Outer Coordinate
d . innerRadius = radius + 15 ; // Set Inner Coordinate
/ *
Overlapping pie labels are not good . What this attempts to do is , prevent overlapping .
Each label location is hashed , and if a hash collision occurs , we assume an overlap .
Adjust the label ' s y - position to remove the overlap .
* /
var center = labelsArc [ i ] . centroid ( d ) ;
var percent = getSlicePercentage ( d ) ;
if ( d . value && percent >= labelThreshold ) {
var hashKey = createHashKey ( center ) ;
if ( labelLocationHash [ hashKey ] ) {
center [ 1 ] -= avgHeight ;
}
labelLocationHash [ createHashKey ( center ) ] = true ;
}
return 'translate(' + center + ')'
}
} ) ;
pieLabels . select ( ".nv-label text" )
. style ( 'text-anchor' , function ( d , i ) {
//center the text on it's origin or begin/end if orthogonal aligned
return labelSunbeamLayout ? ( ( d . startAngle + d . endAngle ) / 2 < Math . PI ? 'start' : 'end' ) : 'middle' ;
} )
. text ( function ( d , i ) {
var percent = getSlicePercentage ( d ) ;
var label = '' ;
if ( ! d . value || percent < labelThreshold ) return '' ;
if ( typeof labelType === 'function' ) {
label = labelType ( d , i , {
'key' : getX ( d . data ) ,
'value' : getY ( d . data ) ,
'percent' : valueFormat ( percent )
} ) ;
} else {
switch ( labelType ) {
case 'key' :
label = getX ( d . data ) ;
break ;
case 'value' :
label = valueFormat ( getY ( d . data ) ) ;
break ;
case 'percent' :
label = d3 . format ( '%' ) ( percent ) ;
break ;
}
}
return label ;
} )
;
}
// Computes the angle of an arc, converting from radians to degrees.
function angle ( d ) {
var a = ( d . startAngle + d . endAngle ) * 90 / Math . PI - 90 ;
return a > 90 ? a - 180 : a ;
}
function arcTween ( a , idx ) {
a . endAngle = isNaN ( a . endAngle ) ? 0 : a . endAngle ;
a . startAngle = isNaN ( a . startAngle ) ? 0 : a . startAngle ;
if ( ! donut ) a . innerRadius = 0 ;
var i = d3 . interpolate ( this . _current , a ) ;
this . _current = i ( 0 ) ;
return function ( t ) {
return arcs [ idx ] ( i ( t ) ) ;
} ;
}
} ) ;
renderWatch . renderEnd ( 'pie immediate' ) ;
return chart ;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
chart . dispatch = dispatch ;
chart . options = nv . utils . optionsFunc . bind ( chart ) ;
chart . _options = Object . create ( { } , {
// simple options, just get/set the necessary values
arcsRadius : { get : function ( ) { return arcsRadius ; } , set : function ( _ ) { arcsRadius = _ ; } } ,
width : { get : function ( ) { return width ; } , set : function ( _ ) { width = _ ; } } ,
height : { get : function ( ) { return height ; } , set : function ( _ ) { height = _ ; } } ,
showLabels : { get : function ( ) { return showLabels ; } , set : function ( _ ) { showLabels = _ ; } } ,
title : { get : function ( ) { return title ; } , set : function ( _ ) { title = _ ; } } ,
titleOffset : { get : function ( ) { return titleOffset ; } , set : function ( _ ) { titleOffset = _ ; } } ,
labelThreshold : { get : function ( ) { return labelThreshold ; } , set : function ( _ ) { labelThreshold = _ ; } } ,
valueFormat : { get : function ( ) { return valueFormat ; } , set : function ( _ ) { valueFormat = _ ; } } ,
x : { get : function ( ) { return getX ; } , set : function ( _ ) { getX = _ ; } } ,
id : { get : function ( ) { return id ; } , set : function ( _ ) { id = _ ; } } ,
endAngle : { get : function ( ) { return endAngle ; } , set : function ( _ ) { endAngle = _ ; } } ,
startAngle : { get : function ( ) { return startAngle ; } , set : function ( _ ) { startAngle = _ ; } } ,
padAngle : { get : function ( ) { return padAngle ; } , set : function ( _ ) { padAngle = _ ; } } ,
cornerRadius : { get : function ( ) { return cornerRadius ; } , set : function ( _ ) { cornerRadius = _ ; } } ,
donutRatio : { get : function ( ) { return donutRatio ; } , set : function ( _ ) { donutRatio = _ ; } } ,
labelsOutside : { get : function ( ) { return labelsOutside ; } , set : function ( _ ) { labelsOutside = _ ; } } ,
labelSunbeamLayout : { get : function ( ) { return labelSunbeamLayout ; } , set : function ( _ ) { labelSunbeamLayout = _ ; } } ,
donut : { get : function ( ) { return donut ; } , set : function ( _ ) { donut = _ ; } } ,
growOnHover : { get : function ( ) { return growOnHover ; } , set : function ( _ ) { growOnHover = _ ; } } ,
// depreciated after 1.7.1
pieLabelsOutside : { get : function ( ) { return labelsOutside ; } , set : function ( _ ) {
labelsOutside = _ ;
nv . deprecated ( 'pieLabelsOutside' , 'use labelsOutside instead' ) ;
} } ,
// depreciated after 1.7.1
donutLabelsOutside : { get : function ( ) { return labelsOutside ; } , set : function ( _ ) {
labelsOutside = _ ;
nv . deprecated ( 'donutLabelsOutside' , 'use labelsOutside instead' ) ;
} } ,
// deprecated after 1.7.1
labelFormat : { get : function ( ) { return valueFormat ; } , set : function ( _ ) {
valueFormat = _ ;
nv . deprecated ( 'labelFormat' , 'use valueFormat instead' ) ;
} } ,
// options that require extra logic in the setter
margin : { get : function ( ) { return margin ; } , set : function ( _ ) {
margin . top = typeof _ . top != 'undefined' ? _ . top : margin . top ;
margin . right = typeof _ . right != 'undefined' ? _ . right : margin . right ;
margin . bottom = typeof _ . bottom != 'undefined' ? _ . bottom : margin . bottom ;
margin . left = typeof _ . left != 'undefined' ? _ . left : margin . left ;
} } ,
y : { get : function ( ) { return getY ; } , set : function ( _ ) {
getY = d3 . functor ( _ ) ;
} } ,
color : { get : function ( ) { return color ; } , set : function ( _ ) {
color = nv . utils . getColor ( _ ) ;
} } ,
labelType : { get : function ( ) { return labelType ; } , set : function ( _ ) {
labelType = _ || 'key' ;
} }
} ) ;
nv . utils . initOptions ( chart ) ;
return chart ;
} ;
nv . version = "1.8.2-dev" ;
2018-04-05 10:25:40 +02:00
} ) ( ) ;