vendor/assets/javascripts/nvd3.js in chart-0.1.5.0.pre vs vendor/assets/javascripts/nvd3.js in chart-0.1.5.1

- old
+ new

@@ -1,10859 +1,10954 @@ -/* nvd3 version 1.8.4 (https://github.com/novus/nvd3) 2016-07-03 */ +/* nvd3 version 1.8.5 (https://github.com/novus/nvd3) 2016-12-01 */ (function(){ // set up main nv object -var nv = {}; + 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 + 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'); -} + if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') { + d3 = require('d3'); + } -nv.dispatch = d3.dispatch('render_start', 'render_end'); + 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"); - } + 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))); - }; + 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; - }; -} + 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(); - }); + 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 - }); -} + 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]; -}; + 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 || ''); - } -}; + 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 = function render(step) { + // number of graphs to generate in each timeout loop + step = step || 1; - nv.render.active = true; - nv.dispatch.render_start(); + nv.render.active = true; + nv.dispatch.render_start(); - var renderLoop = function() { - var chart, graph; + 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); - } + 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); + nv.render.queue.splice(0, i); - if (nv.render.queue.length) { - setTimeout(renderLoop); - } - else { - nv.dispatch.render_end(); - nv.render.active = false; - } + if (nv.render.queue.length) { + setTimeout(renderLoop); + } + else { + nv.dispatch.render_end(); + nv.render.active = false; + } + }; + + setTimeout(renderLoop); }; - setTimeout(renderLoop); -}; + nv.render.active = false; + nv.render.queue = []; -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> + }) -/* -Adds a chart to the async rendering queue. This method can take arguments in two forms: -nv.addGraph({ - generate: <Function> - callback: <Function> -}) + or -or + nv.addGraph(<generate Function>, <callback Function>) -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 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]}; + } -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); - nv.render.queue.push(obj); + if (!nv.render.active) { + nv.render(); + } + }; - if (!nv.render.active) { - nv.render(); +// Node/CommonJS exports + if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { + module.exports = nv; } -}; -// Node/CommonJS exports -if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { - module.exports = nv; -} + if (typeof(window) !== 'undefined') { + window.nv = nv; + } + /* Facade for queueing DOM write operations + * 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.mutate(callback); + } + return callback(); + }; -if (typeof(window) !== 'undefined') { - window.nv = nv; -} -/* Facade for queueing DOM write operations - * 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.mutate(callback); - } - return callback(); -}; + /* Facade for queueing DOM read operations + * 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.measure(callback); + } + return callback(); + }; + /* Utility class to handle creation of an interactive layer. + 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. -/* Facade for queueing DOM read operations - * 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.measure(callback); - } - return callback(); -}; -/* Utility class to handle creation of an interactive layer. - 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"; - 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 = window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11) + ; - 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 = window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11) - ; + tooltip + .duration(0) + .hideDelay(0) + .hidden(false); - 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"); - 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; + } - 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; - 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 result, 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; + } - /* - 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 result, 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 (d3.event.target.className.baseVal.match("nv-legend")) { - mouseOutAnyReason = true; + if(subtractMargin) { + mouseX -= margin.left; + mouseY -= margin.top; } - } - - if(subtractMargin) { - mouseX -= margin.left; - mouseY -= margin.top; - } - - /* If mouseX/Y is outside of the chart's bounds, - 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 mouseX/Y is outside of the chart's bounds, + 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 + if (isMSIE) { + if (d3.event.relatedTarget + && d3.event.relatedTarget.ownerSVGElement === undefined + && (d3.event.relatedTarget.className === undefined || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) { - return; + 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 { + tooltip.hidden(false); } - } - 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 - }); - } + var scaleIsOrdinal = typeof xScale.rangeBands === 'function'; + var pointXValue = undefined; - // if user single clicks the layer, fire elementClick - if (d3.event.type === 'click') { - dispatch.elementClick({ + // 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 presses mouse down the layer, fire elementMouseDown - if (d3.event.type === 'mousedown') { - dispatch.elementMouseDown({ - 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 + }); + } } - // 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(); + }); } - } + }); + } - 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.dispatch = dispatch; + layer.tooltip = tooltip; - 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.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.dispatch = dispatch; - layer.tooltip = tooltip; + layer.width = function(_) { + if (!arguments.length) return width; + width = _; + return layer; + }; - 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.height = function(_) { + if (!arguments.length) return height; + height = _; + return layer; + }; - layer.width = function(_) { - if (!arguments.length) return width; - width = _; - return layer; - }; + layer.xScale = function(_) { + if (!arguments.length) return xScale; + xScale = _; + return layer; + }; - layer.height = function(_) { - if (!arguments.length) return height; - height = _; - return layer; - }; + layer.showGuideLine = function(_) { + if (!arguments.length) return showGuideLine; + showGuideLine = _; + return layer; + }; - layer.xScale = function(_) { - if (!arguments.length) return xScale; - xScale = _; - return layer; - }; + layer.svgContainer = function(_) { + if (!arguments.length) return svgContainer; + svgContainer = _; + return layer; + }; - layer.showGuideLine = function(_) { - if (!arguments.length) return showGuideLine; - showGuideLine = _; return layer; }; - layer.svgContainer = function(_) { - if (!arguments.length) return svgContainer; - svgContainer = _; - return layer; - }; + /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. + This is different from normal bisectLeft; this function finds the nearest index to insert the search value. - return layer; -}; + 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. -/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. - This is different from normal bisectLeft; this function finds the nearest index to insert the search value. + Unit tests can be found in: interactiveBisectTest.html - 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. + 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; + }; - Unit tests can be found in: interactiveBisectTest.html + var bisect = d3.bisector(_cmp).left; + var index = d3.max([0, bisect(values,searchVal) - 1]); + var currentValue = _xAccessor(values[index]); - 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; + if (typeof currentValue === 'undefined') { + currentValue = index; } - } 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 (currentValue === searchVal) { + return index; //found exact match + } - if (typeof currentValue === 'undefined') { - currentValue = index; - } + var nextIndex = d3.min([index+1, values.length - 1]); + var nextValue = _xAccessor(values[nextIndex]); - if (currentValue === searchVal) { - return index; //found exact match - } + if (typeof nextValue === 'undefined') { + nextValue = nextIndex; + } - var nextIndex = d3.min([index+1, values.length - 1]); - var nextValue = _xAccessor(values[nextIndex]); + if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) { + return index; + } else { + return nextIndex + } + }; - if (typeof nextValue === 'undefined') { - nextValue = 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; + }; - if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) { - return index; - } else { - return nextIndex - } -}; + /* Model which can be instantiated to handle tooltip rendering. + Example usage: + var tip = nv.models.tooltip().gravity('w').distance(23) + .data(myDataObject); -/* - 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; -}; + tip(); //just invoke the returned function to render tooltip. + */ + nv.models.tooltip = function() { + "use strict"; -/* Model which can be instantiated to handle tooltip rendering. - Example usage: - var tip = nv.models.tooltip().gravity('w').distance(23) - .data(myDataObject); + /* + 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. + , 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. + ; - tip(); //just invoke the returned function to render tooltip. - */ -nv.models.tooltip = function() { - "use strict"; + // Format function for the tooltip values column. + var valueFormatter = function(d, i) { + return d; + }; - /* - 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. - , 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. - ; + // Format function for the tooltip header value. + var headerFormatter = function(d) { + return d; + }; - // Format function for the tooltip values column. - var valueFormatter = function(d, i) { - return d; - }; + var keyFormatter = function(d, i) { + return d; + }; - // Format function for the tooltip header value. - var headerFormatter = function(d) { - 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 keyFormatter = function(d, i) { - return d; - }; + var table = d3.select(document.createElement("table")); + if (headerEnabled) { + var theadEnter = table.selectAll("thead") + .data([d]) + .enter().append("thead"); - // 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 ''; - } + theadEnter.append("tr") + .append("td") + .attr("colspan", 3) + .append("strong") + .classed("x-value", true) + .html(headerFormatter(d.value)); + } - var table = d3.select(document.createElement("table")); - if (headerEnabled) { - var theadEnter = table.selectAll("thead") + var tbodyEnter = table.selectAll("tbody") .data([d]) - .enter().append("thead"); + .enter().append("tbody"); - 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") + 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("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("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.append("td") + .classed("value",true) + .html(function(p, i) { return valueFormatter(p.value, i) }); - trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td") - .classed("percent", true) - .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" }); + trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td") + .classed("percent", true) + .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" }); - 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)) - ; - } - }); + 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 html = table.node().outerHTML; + if (d.footer !== undefined) + html += "<div class='footer'>" + d.footer + "</div>"; + return html; - }; - - /* - Function that returns the position (relative to the viewport/document.body) - the tooltip should be placed in. - Should return: { - left: <leftPos>, - top: <topPos> - } - */ - var position = function() { - var pos = { - left: d3.event !== null ? d3.event.clientX : 0, - top: d3.event !== null ? d3.event.clientY : 0 }; - if(getComputedStyle(document.body).transform != 'none') { - // Take the offset into account, as now the tooltip is relative - // to document.body. - var client = document.body.getBoundingClientRect(); - pos.left -= client.left; - pos.top -= client.top; - } + /* + Function that returns the position (relative to the viewport/document.body) + the tooltip should be placed in. + Should return: { + left: <leftPos>, + top: <topPos> + } + */ + var position = function() { + var pos = { + left: d3.event !== null ? d3.event.clientX : 0, + top: d3.event !== null ? d3.event.clientY : 0 + }; - return pos; - }; + if(getComputedStyle(document.body).transform != 'none') { + // Take the offset into account, as now the tooltip is relative + // to document.body. + var client = document.body.getBoundingClientRect(); + pos.left -= client.left; + pos.top -= client.top; + } - var dataSeriesExists = function(d) { - if (d && d.series) { - if (nv.utils.isArray(d.series)) { - return true; + return pos; + }; + + 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; + } } - // 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; - }; + 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; + // 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; - } + // 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 }; - }; + 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; + /* + 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(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)'; - var translateInterpolator = d3.interpolateString(old_translate, new_translate); - var is_hidden = tooltip.style('opacity') < 0.1; + // 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(' + Math.round(left) + 'px, ' + Math.round(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); - } + 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; - }); - }; + lastPosition.left = left; + lastPosition.top = top; + }); + }; - // Creates new tooltip container, or uses existing one on DOM. - function initTooltip() { - if (!tooltip || !tooltip.node()) { - // Create new tooltip div if it doesn't exist on DOM. + // Creates new tooltip container, or uses existing one on DOM. + function initTooltip() { + if (!tooltip || !tooltip.node()) { + // Create new tooltip div if it doesn't exist on DOM. - var data = [1]; - tooltip = d3.select(document.body).selectAll('.nvtooltip').data(data); + var data = [1]; + tooltip = d3.select(document.body).select('#'+id).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.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() + tooltip.exit().remove() + } } - } - // Draw the tooltip onto the DOM. - function nvtooltip() { - if (!enabled) return; - if (!dataSeriesExists(data)) return; + // 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; - } + 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(); - }); + positionTooltip(); + }); - return nvtooltip; - } + return nvtooltip; + } - nvtooltip.nvPointerEventsClass = nvPointerEventsClass; - nvtooltip.options = nv.utils.optionsFunc.bind(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=_;}}, - 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=_;}}, + 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=_;}}, + 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 - chartContainer: {get: function(){return document.body;}, set: function(_){ - // deprecated after 1.8.3 - nv.deprecated('chartContainer', 'feature removed after 1.8.3'); - }}, - 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'); - }}, + // Deprecated options + chartContainer: {get: function(){return document.body;}, set: function(_){ + // deprecated after 1.8.3 + nv.deprecated('chartContainer', 'feature removed after 1.8.3'); + }}, + 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 = _; - }}, + // 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(_){}} - }); + // read only properties + node: {get: function(){return tooltip.node();}, set: function(_){}}, + id: {get: function(){return id;}, set: function(_){}} + }); - nv.utils.initOptions(nvtooltip); - return nvtooltip; -}; + nv.utils.initOptions(nvtooltip); + return nvtooltip; + }; -/* -Gets the browser window size + /* + Gets the browser window size -Returns object with height and width properties - */ -nv.utils.windowSize = function() { - // Sane defaults - var size = {width: 640, height: 480}; + 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); - } + // 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 ) { + // 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); - } + 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; + // 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); - } + }; - return (size); -}; + /* handle dumb browser quirks... isinstance breaks if you use frames + 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'; + }; -/* handle dumb browser quirks... isinstance breaks if you use frames -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); + /* + 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/scale -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(); + /* + Backwards compatible way to implement more d3-like coloring of graphs. + Can take in nothing, an array, or a function/scale + 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 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; - } -}; + //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()); -}; + /* + 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(); + /* + 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; + // 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; + 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]; } - 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) { + /* + 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); + 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); + } + }); }; - 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; + }; -/* -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; + }; -/* -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) { + /* + 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); + }; - 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; -/* -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); - } + 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); - var _duration = duration !== undefined ? duration : 250; - var renderStack = []; - var self = this; + if (renderStack.indexOf(model) < 0) { + renderStack.push(model); + } + }); + return 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); + this.reset = function(duration) { + if (duration !== undefined) { + _duration = duration; } - }); - return this; - }; + renderStack = []; + }; - this.reset = function(duration) { - if (duration !== undefined) { - _duration = duration; - } - renderStack = []; - }; + this.transition = function(selection, args, duration) { + args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; - 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 (args.length > 1) { - duration = args.pop(); - } else { - duration = _duration !== undefined ? _duration : 250; - } - selection.__rendered = false; + if (renderStack.indexOf(selection) < 0) { + renderStack.push(selection); + } - 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) { + if (duration === 0) { selection.__rendered = true; - } else if (selection.every( function(d){ return !d.length; } )) { - selection.__rendered = true; + selection.delay = function() { return this; }; + selection.duration = function() { return this; }; + return selection; } else { - selection.__rendered = false; + 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); + } + }); } + }; - 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); + } } + }; - 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]; + } + } + }); + }; -/* -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'); -/* -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.on('set', function(state){ + _setState(state, true); + }); - this.dispatch = d3.dispatch('change', 'set'); + this.getter = function(fn){ + _getState = fn; + return this; + }; - 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(); + this.setter = function(fn, callback) { + if (!callback) { + callback = function(){}; } + _setState = function(state, update){ + fn(state); + if (update) { + callback(); + } + }; + return this; }; - return this; - }; - this.init = function(state){ - init = init || {}; - nv.utils.deepExtend(init, state); - }; + this.init = function(state){ + init = init || {}; + nv.utils.deepExtend(init, state); + }; - var _set = function(){ - var settings = _getState(); + var _set = function(){ + var settings = _getState(); - if (JSON.stringify(settings) === JSON.stringify(state)) { - return false; - } + if (JSON.stringify(settings) === JSON.stringify(state)) { + return false; + } - for (var key in settings) { - if (state[key] === undefined) { - state[key] = {}; + for (var key in settings) { + if (state[key] === undefined) { + state[key] = {}; + } + state[key] = settings[key]; + changed = true; } - state[key] = settings[key]; - changed = true; - } - return true; - }; + return true; + }; - this.update = function(){ - if (init) { - _setState(init, false); - init = null; - } - if (_set.call(this)) { - this.dispatch.change(state); - } + 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 + }); -/* -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; + }; -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 -/* -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 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); + }; -/* -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'); -/* -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]) { + 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; } - 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]); - } -}; + /* + 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); -}; + /* + 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]; - }); -}; + /* + 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(); + /* + 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); + /* + 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(_); + 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; }; - 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 || [])); -}; + /* + 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}); -}; + /* + 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 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); -}; + /* + 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 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); -}; + /* + 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; + /* + 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(); + //Remove any previously created chart components + container.selectAll('g').remove(); - var noDataText = container.selectAll('.nv-noData').data(data); + 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.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; }); -}; + 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(); + /* + 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(" ")); - line = [word]; - tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); + 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; + /* + Check equality of 2 array + */ + nv.utils.arrayEquals = function (array1, array2) { + if (array1 === array2) + return true; - if (!array1 || !array2) - return false; + if (!array1 || !array2) + return false; - // compare lengths - can save a lot of time - if (array1.length != array2.length) - 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])) + 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; - } 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"; + return true; + }; + nv.models.axis = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var axis = d3.svg.axis(); - var scale = d3.scale.linear(); + 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') + 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 }) ; - axis - .scale(scale) - .orient('bottom') - .tickFormat(function(d) { return d }) - ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var scale0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + 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); + 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'); + // 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); + 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); + //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(); + scale0 = scale0 || axis.scale(); - var fmt = axis.tickFormat(); - if (fmt == null) { - fmt = scale0.tickFormat(); - } + var fmt = axis.tickFormat(); + if (fmt == null) { + fmt = scale0.tickFormat(); + } - var axisLabel = g.selectAll('text.nv-axislabel') - .data([axisLabelText || null]); - axisLabel.exit().remove(); + 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); - } + //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()) + 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') - .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('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(0,' + (i % 2 == 0 ? '0' : '12') + ')' + 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 { - xTicks.attr('transform', "translate(0,0)"); + 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') + 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){ + .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)' - }); - } + }).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 - (axisLabelDistance || 0)) : -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){ + 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 - (axisLabelDistance || 0)) : -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){ + }).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 }); + }).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); + 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!! - } - }); + 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 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); + 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!! } }); - // 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); + //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(); + //store old scales for use in transitions on update + scale0 = scale.copy(); - }); + }); - renderWatch.renderEnd('axis immediate'); - return chart; - } + renderWatch.renderEnd('axis immediate'); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - // expose chart's sub-components - chart.axis = axis; - chart.dispatch = dispatch; + // 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=_;}}, + 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']); - }} - }); + // 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']); + 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.boxPlot = function() { - "use strict"; + return chart; + }; + nv.models.boxPlot = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 - xScale = d3.scale.ordinal(), - yScale = d3.scale.linear(), - getX = function(d) { return d.label }, // Default data model selectors. - getQ1 = function(d) { return d.values.Q1 }, - getQ2 = function(d) { return d.values.Q2 }, - getQ3 = function(d) { return d.values.Q3 }, - getWl = function(d) { return d.values.whisker_low }, - getWh = function(d) { return d.values.whisker_high }, - getColor = function(d) { return d.color }, - getOlItems = function(d) { return d.values.outliers }, - getOlValue = function(d, i, j) { return d }, - getOlLabel = function(d, i, j) { return d }, - getOlColor = function(d, i, j) { return undefined }, - color = nv.utils.defaultColor(), - container = null, - xDomain, xRange, - yDomain, yRange, - dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'), - duration = 250, - maxBoxWidth = null; + 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 + xScale = d3.scale.ordinal(), + yScale = d3.scale.linear(), + getX = function(d) { return d.label }, // Default data model selectors. + getQ1 = function(d) { return d.values.Q1 }, + getQ2 = function(d) { return d.values.Q2 }, + getQ3 = function(d) { return d.values.Q3 }, + getWl = function(d) { return d.values.whisker_low }, + getWh = function(d) { return d.values.whisker_high }, + getColor = function(d) { return d.color }, + getOlItems = function(d) { return d.values.outliers }, + getOlValue = function(d, i, j) { return d }, + getOlLabel = function(d, i, j) { return d }, + getOlColor = function(d, i, j) { return undefined }, + color = nv.utils.defaultColor(), + container = null, + xDomain, xRange, + yDomain, yRange, + dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'), + duration = 250, + maxBoxWidth = null; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var xScale0, yScale0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + var xScale0, yScale0; + 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; + 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); + container = d3.select(this); + nv.utils.initSVG(container); - // Setup Scales - xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); })) - .rangeBands(xRange || [0, availableWidth], 0.1); + // Setup Scales + xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); })) + .rangeBands(xRange || [0, availableWidth], 0.1); - // if we know yDomain, no need to calculate - var yData = [] - if (!yDomain) { - // (y-range is based on quartiles, whiskers and outliers) - var values = [], yMin, yMax; - data.forEach(function (d, i) { - var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d); - var olItems = getOlItems(d); - if (olItems) { - olItems.forEach(function (e, i) { - values.push(getOlValue(e, i, undefined)); - }); - } - if (wl) { values.push(wl) } - if (q1) { values.push(q1) } - if (q3) { values.push(q3) } - if (wh) { values.push(wh) } - }); - yMin = d3.min(values); - yMax = d3.max(values); - yData = [ yMin, yMax ] ; - } + // if we know yDomain, no need to calculate + var yData = [] + if (!yDomain) { + // (y-range is based on quartiles, whiskers and outliers) + var values = [], yMin, yMax; + data.forEach(function (d, i) { + var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d); + var olItems = getOlItems(d); + if (olItems) { + olItems.forEach(function (e, i) { + values.push(getOlValue(e, i, undefined)); + }); + } + if (wl) { values.push(wl) } + if (q1) { values.push(q1) } + if (q3) { values.push(q3) } + if (wh) { values.push(wh) } + }); + yMin = d3.min(values); + yMax = d3.max(values); + yData = [ yMin, yMax ] ; + } - yScale.domain(yDomain || yData); - yScale.range(yRange || [availableHeight, 0]); + yScale.domain(yDomain || yData); + yScale.range(yRange || [availableHeight, 0]); - //store old scales if they exist - xScale0 = xScale0 || xScale; - yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]); + //store old scales if they exist + xScale0 = xScale0 || xScale; + yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); - var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); - boxplots - .attr('class', 'nv-boxplot') - .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }) - .classed('hover', function(d) { return d.hover }); - boxplots - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .style('stroke-opacity', 1) - .style('fill-opacity', 0.75) - .delay(function(d,i) { return i * duration / data.length }) - .attr('transform', function(d,i) { - return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; + var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); + var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); + boxplots + .attr('class', 'nv-boxplot') + .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }) + .classed('hover', function(d) { return d.hover }); + boxplots + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .style('stroke-opacity', 1) + .style('fill-opacity', 0.75) + .delay(function(d,i) { return i * duration / data.length }) + .attr('transform', function(d,i) { + return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; + }); + boxplots.exit().remove(); + + // ----- add the SVG elements for each boxPlot ----- + + // conditionally append whisker lines + boxEnter.each(function(d,i) { + var box = d3.select(this); + [getWl, getWh].forEach(function (f) { + if (f(d) !== undefined && f(d) !== null) { + var key = (f === getWl) ? 'low' : 'high'; + box.append('line') + .style('stroke', getColor(d) || color(d,i)) + .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); + box.append('line') + .style('stroke', getColor(d) || color(d,i)) + .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); + } + }); }); - boxplots.exit().remove(); - // ----- add the SVG elements for each boxPlot ----- + var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); }; + var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; }; + var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; }; - // conditionally append whisker lines - boxEnter.each(function(d,i) { - var box = d3.select(this); + // update whisker lines and ticks [getWl, getWh].forEach(function (f) { - if (f(d)) { - var key = (f === getWl) ? 'low' : 'high'; - box.append('line') - .style('stroke', getColor(d) || color(d,i)) - .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); - box.append('line') - .style('stroke', getColor(d) || color(d,i)) - .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); - } + var key = (f === getWl) ? 'low' : 'high'; + var endpoint = (f === getWl) ? getQ1 : getQ3; + boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', xScale.rangeBand() * 0.45 ) + .attr('y1', function(d,i) { return yScale(f(d)); }) + .attr('x2', xScale.rangeBand() * 0.45 ) + .attr('y2', function(d,i) { return yScale(endpoint(d)); }); + boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', box_left ) + .attr('y1', function(d,i) { return yScale(f(d)); }) + .attr('x2', box_right ) + .attr('y2', function(d,i) { return yScale(f(d)); }); }); - }); - var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); }; - var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; }; - var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; }; + [getWl, getWh].forEach(function (f) { + var key = (f === getWl) ? 'low' : 'high'; + boxEnter.selectAll('.nv-boxplot-' + key) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: f(d), color: getColor(d) || color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: f(d), color: getColor(d) || color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + }); - // update whisker lines and ticks - [getWl, getWh].forEach(function (f) { - var key = (f === getWl) ? 'low' : 'high'; - var endpoint = (f === getWl) ? getQ1 : getQ3; - boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', xScale.rangeBand() * 0.45 ) - .attr('y1', function(d,i) { return yScale(f(d)); }) - .attr('x2', xScale.rangeBand() * 0.45 ) - .attr('y2', function(d,i) { return yScale(endpoint(d)); }); - boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', box_left ) - .attr('y1', function(d,i) { return yScale(f(d)); }) - .attr('x2', box_right ) - .attr('y2', function(d,i) { return yScale(f(d)); }); - }); - - [getWl, getWh].forEach(function (f) { - var key = (f === getWl) ? 'low' : 'high'; - boxEnter.selectAll('.nv-boxplot-' + key) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: f(d), color: getColor(d) || color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: f(d), color: getColor(d) || color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); - }); - - // boxes - boxEnter.append('rect') - .attr('class', 'nv-boxplot-box') - // tooltip events - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - key: getX(d), - value: getX(d), - series: [ - { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, - { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, - { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } - ], - data: d, - index: i, - e: d3.event + // boxes + boxEnter.append('rect') + .attr('class', 'nv-boxplot-box') + // tooltip events + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + key: getX(d), + value: getX(d), + series: [ + { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, + { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, + { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } + ], + data: d, + index: i, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + key: getX(d), + value: getX(d), + series: [ + { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, + { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, + { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } + ], + data: d, + index: i, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - key: getX(d), - value: getX(d), - series: [ - { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, - { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, - { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } - ], - data: d, - index: i, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); - // box transitions - boxplots.select('rect.nv-boxplot-box') - .watchTransition(renderWatch, 'nv-boxplot: boxes') - .attr('y', function(d,i) { return yScale(getQ3(d)); }) - .attr('width', box_width) - .attr('x', box_left ) - .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 }) - .style('fill', function(d,i) { return getColor(d) || color(d,i) }) - .style('stroke', function(d,i) { return getColor(d) || color(d,i) }); + // box transitions + boxplots.select('rect.nv-boxplot-box') + .watchTransition(renderWatch, 'nv-boxplot: boxes') + .attr('y', function(d,i) { return yScale(getQ3(d)); }) + .attr('width', box_width) + .attr('x', box_left ) + .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 }) + .style('fill', function(d,i) { return getColor(d) || color(d,i) }) + .style('stroke', function(d,i) { return getColor(d) || color(d,i) }); - // median line - boxEnter.append('line').attr('class', 'nv-boxplot-median'); + // median line + boxEnter.append('line').attr('class', 'nv-boxplot-median'); - boxplots.select('line.nv-boxplot-median') - .watchTransition(renderWatch, 'nv-boxplot: boxplots line') - .attr('x1', box_left) - .attr('y1', function(d,i) { return yScale(getQ2(d)); }) - .attr('x2', box_right) - .attr('y2', function(d,i) { return yScale(getQ2(d)); }); + boxplots.select('line.nv-boxplot-median') + .watchTransition(renderWatch, 'nv-boxplot: boxplots line') + .attr('x1', box_left) + .attr('y1', function(d,i) { return yScale(getQ2(d)); }) + .attr('x2', box_right) + .attr('y2', function(d,i) { return yScale(getQ2(d)); }); - // outliers - var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { - return getOlItems(d) || []; - }); - outliers.enter().append('circle') - .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) - .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) - .style('z-index', 9000) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); + // outliers + var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { + return getOlItems(d) || []; }); - outliers.attr('class', 'nv-boxplot-outlier'); - outliers - .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') - .attr('cx', xScale.rangeBand() * 0.45) - .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); }) - .attr('r', '3'); - outliers.exit().remove(); + outliers.enter().append('circle') + .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) + .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) + .style('z-index', 9000) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + outliers.attr('class', 'nv-boxplot-outlier'); + outliers + .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') + .attr('cx', xScale.rangeBand() * 0.45) + .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); }) + .attr('r', '3'); + outliers.exit().remove(); - //store old scales for use in transitions on update - xScale0 = xScale.copy(); - yScale0 = yScale.copy(); - }); + //store old scales for use in transitions on update + xScale0 = xScale.copy(); + yScale0 = yScale.copy(); + }); - renderWatch.renderEnd('nv-boxplot immediate'); - return chart; - } + renderWatch.renderEnd('nv-boxplot immediate'); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}}, - q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}}, - q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}}, - wl: {get: function(){return getWl;}, set: function(_){getWl=_;}}, - wh: {get: function(){return getWh;}, set: function(_){getWh=_;}}, - itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}}, - outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}}, - outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}}, - outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}}, - outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}}, - xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}}, - yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}}, - 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=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, - y: { - get: function() { - console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); - return {}; + 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=_;}}, + maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}}, + q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}}, + q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}}, + wl: {get: function(){return getWl;}, set: function(_){getWl=_;}}, + wh: {get: function(){return getWh;}, set: function(_){getWh=_;}}, + itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}}, + outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}}, + outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}}, + outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}}, + outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}}, + xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}}, + yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}}, + 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=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, + y: { + get: function() { + console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); + return {}; + }, + set: function(_) { + console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); + } }, - set: function(_) { - console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); - } - }, - // 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); - }} - }); + // 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); + nv.utils.initOptions(chart); - return chart; -}; -nv.models.boxPlotChart = function() { - "use strict"; + return chart; + }; + nv.models.boxPlotChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var boxplot = nv.models.boxPlot(), - xAxis = nv.models.axis(), - yAxis = nv.models.axis(); + var boxplot = nv.models.boxPlot(), + xAxis = nv.models.axis(), + yAxis = nv.models.axis(); - var margin = {top: 15, right: 10, bottom: 50, left: 60}, - width = null, - height = null, - color = nv.utils.getColor(), - showXAxis = true, - showYAxis = true, - rightAlignYAxis = false, - staggerLabels = false, - tooltip = nv.models.tooltip(), - x, y, - noData = 'No Data Available.', - dispatch = d3.dispatch('beforeUpdate', 'renderEnd'), - duration = 250; + var margin = {top: 15, right: 10, bottom: 50, left: 60}, + width = null, + height = null, + color = nv.utils.getColor(), + showXAxis = true, + showYAxis = true, + rightAlignYAxis = false, + staggerLabels = false, + tooltip = nv.models.tooltip(), + x, y, + noData = 'No Data Available.', + 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')) - ; + xAxis + .orient('bottom') + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; - tooltip.duration(0); + tooltip.duration(0); - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch, duration); + var renderWatch = nv.utils.renderWatch(dispatch, duration); - function chart(selection) { - renderWatch.reset(); - renderWatch.models(boxplot); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(boxplot); + 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 = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right; - var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom; + selection.each(function(data) { + var container = d3.select(this), that = this; + nv.utils.initSVG(container); + var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right; + var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom; - chart.update = function() { - dispatch.beforeUpdate(); - container.transition().duration(duration).call(chart); - }; - chart.container = this; + chart.update = function() { + dispatch.beforeUpdate(); + container.transition().duration(duration).call(chart); + }; + chart.container = this; - // TODO still need to find a way to validate quartile data presence using boxPlot callbacks. - // Display No Data message if there's nothing to show. (quartiles required at minimum). - if (!data || !data.length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); + // TODO still need to find a way to validate quartile data presence using boxPlot callbacks. + // Display No Data message if there's nothing to show. (quartiles required at minimum). + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } - // Setup Scales - x = boxplot.xScale(); - y = boxplot.yScale().clamp(true); + // Setup Scales + x = boxplot.xScale(); + y = boxplot.yScale().clamp(true); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g'); - var defsEnter = gEnter.append('defs'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').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-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'); - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - if (rightAlignYAxis) { - g.select('.nv-y.nv-axis') - .attr('transform', 'translate(' + availableWidth + ',0)'); - } + if (rightAlignYAxis) { + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(' + availableWidth + ',0)'); + } - // Main Chart Component(s) - boxplot.width(availableWidth).height(availableHeight); + // Main Chart Component(s) + boxplot.width(availableWidth).height(availableHeight); - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })) + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) - barsWrap.transition().call(boxplot); + barsWrap.transition().call(boxplot); - defsEnter.append('clipPath') - .attr('id', 'nv-x-label-clip-' + boxplot.id()) - .append('rect'); + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + boxplot.id()) + .append('rect'); - g.select('#nv-x-label-clip-' + boxplot.id() + ' rect') - .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) - .attr('height', 16) - .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + g.select('#nv-x-label-clip-' + boxplot.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); + // 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); + 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').selectAll('g'); - if (staggerLabels) { - xTicks - .selectAll('text') - .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' }) + 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 (showYAxis) { - yAxis - .scale(y) - .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data - .tickSize( -availableWidth, 0); + if (showYAxis) { + yAxis + .scale(y) + .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data + .tickSize( -availableWidth, 0); - g.select('.nv-y.nv-axis').call(yAxis); - } + g.select('.nv-y.nv-axis').call(yAxis); + } - // Zero line - g.select('.nv-zeroLine line') - .attr('x1',0) - .attr('x2',availableWidth) - .attr('y1', y(0)) - .attr('y2', y(0)) - ; + // Zero line + g.select('.nv-zeroLine line') + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', y(0)) + .attr('y2', y(0)) + ; - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - }); + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + }); - renderWatch.renderEnd('nv-boxplot chart immediate'); - return chart; - } + renderWatch.renderEnd('nv-boxplot chart immediate'); + return chart; + } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - boxplot.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip.data(evt).hidden(false); - }); + boxplot.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip.data(evt).hidden(false); + }); - boxplot.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.data(evt).hidden(true); - }); + boxplot.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.data(evt).hidden(true); + }); - boxplot.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + boxplot.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.boxplot = boxplot; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.tooltip = tooltip; + chart.dispatch = dispatch; + chart.boxplot = boxplot; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + 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=_;}}, + staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}}, + 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); - boxplot.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - boxplot.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }} - }); + // 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); + boxplot.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + boxplot.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }} + }); - nv.utils.inheritOptions(chart, boxplot); - nv.utils.initOptions(chart); + nv.utils.inheritOptions(chart, boxplot); + nv.utils.initOptions(chart); - return chart; -} + return chart; + } // Chart design based on the recommendations of Stephen Few. Implementation // based on the work of Clint Ivy, Jamie Love, and Jason Davies. // http://projects.instantcognition.com/protovis/bulletchart/ -nv.models.bullet = function() { - "use strict"; + nv.models.bullet = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , orient = 'left' // TODO top & bottom - , reverse = false - , ranges = function(d) { return d.ranges } - , markers = function(d) { return d.markers ? d.markers : [] } - , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] } - , measures = function(d) { return d.measures } - , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } - , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } - , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : [] } - , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } - , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) - , width = 380 - , height = 30 - , container = null - , tickFormat = null - , color = nv.utils.getColor(['#1f77b4']) - , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') - , defaultRangeLabels = ["Maximum", "Mean", "Minimum"] - , legacyRangeClassNames = ["Max", "Avg", "Min"] - ; + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers ? d.markers : [] } + , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] } + , measures = function(d) { return d.measures } + , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } + , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } + , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : [] } + , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , container = null + , tickFormat = null + , color = nv.utils.getColor(['#1f77b4']) + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') + , defaultRangeLabels = ["Maximum", "Mean", "Minimum"] + , legacyRangeClassNames = ["Max", "Avg", "Min"] + , duration = 1000 + ; - function sortLabels(labels, values){ - var lz = labels.slice(); - labels.sort(function(a, b){ - var iA = lz.indexOf(a); - var iB = lz.indexOf(b); - return d3.descending(values[iA], values[iB]); - }); - }; + function sortLabels(labels, values){ + var lz = labels.slice(); + labels.sort(function(a, b){ + var iA = lz.indexOf(a); + var iB = lz.indexOf(b); + return d3.descending(values[iA], values[iB]); + }); + }; - function chart(selection) { - selection.each(function(d, i) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; - container = d3.select(this); - nv.utils.initSVG(container); + container = d3.select(this); + nv.utils.initSVG(container); - var rangez = ranges.call(this, d, i).slice(), - markerz = markers.call(this, d, i).slice(), - markerLinez = markerLines.call(this, d, i).slice().sort(d3.descending), - measurez = measures.call(this, d, i).slice(), - rangeLabelz = rangeLabels.call(this, d, i).slice(), - markerLabelz = markerLabels.call(this, d, i).slice(), - markerLineLabelz = markerLineLabels.call(this, d, i).slice(), - measureLabelz = measureLabels.call(this, d, i).slice(); + var rangez = ranges.call(this, d, i).slice(), + markerz = markers.call(this, d, i).slice(), + markerLinez = markerLines.call(this, d, i).slice(), + measurez = measures.call(this, d, i).slice(), + rangeLabelz = rangeLabels.call(this, d, i).slice(), + markerLabelz = markerLabels.call(this, d, i).slice(), + markerLineLabelz = markerLineLabels.call(this, d, i).slice(), + measureLabelz = measureLabels.call(this, d, i).slice(); - // Sort labels according to their sorted values - sortLabels(rangeLabelz, rangez); - sortLabels(markerLabelz, markerz); - sortLabels(markerLineLabelz, markerLinez); - sortLabels(measureLabelz, measurez); + // Sort labels according to their sorted values + sortLabels(rangeLabelz, rangez); + sortLabels(markerLabelz, markerz); + sortLabels(markerLineLabelz, markerLinez); + sortLabels(measureLabelz, measurez); - // sort values descending - rangez.sort(d3.descending); - markerz.sort(d3.descending); - measurez.sort(d3.descending); + // sort values descending + rangez.sort(d3.descending); + markerz.sort(d3.descending); + markerLinez.sort(d3.descending); + measurez.sort(d3.descending); - // Setup Scales - // Compute the new x-scale. - var x1 = d3.scale.linear() - .domain( d3.extent(d3.merge([forceX, rangez])) ) - .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + // Setup Scales + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain( d3.extent(d3.merge([forceX, rangez])) ) + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); - // Retrieve the old x-scale, if this is an update. - var x0 = this.__chart__ || d3.scale.linear() - .domain([0, Infinity]) - .range(x1.range()); + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); - // Stash the new scale. - this.__chart__ = x1; + // Stash the new scale. + this.__chart__ = x1; - var rangeMin = d3.min(rangez), //rangez[2] - rangeMax = d3.max(rangez), //rangez[0] - rangeAvg = rangez[1]; + var rangeMin = d3.min(rangez), //rangez[2] + rangeMax = d3.max(rangez), //rangez[0] + rangeAvg = rangez[1]; - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - for(var i=0,il=rangez.length; i<il; i++){ - var rangeClassNames = 'nv-range nv-range'+i; - if(i <= 2){ - rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i]; + for(var i=0,il=rangez.length; i<il; i++){ + var rangeClassNames = 'nv-range nv-range'+i; + if(i <= 2){ + rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i]; + } + gEnter.append('rect').attr('class', rangeClassNames); } - gEnter.append('rect').attr('class', rangeClassNames); - } - gEnter.append('rect').attr('class', 'nv-measure'); + gEnter.append('rect').attr('class', 'nv-measure'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) - w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; - var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, - xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, + xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; - for(var i=0,il=rangez.length; i<il; i++){ - var range = rangez[i]; - g.select('rect.nv-range'+i) - .attr('height', availableHeight) - .attr('width', w1(range)) - .attr('x', xp1(range)) - .datum(range) - } + for(var i=0,il=rangez.length; i<il; i++){ + var range = rangez[i]; + g.select('rect.nv-range'+i) + .datum(range) + .attr('height', availableHeight) + .transition() + .duration(duration) + .attr('width', w1(range)) + .attr('x', xp1(range)) + } - g.select('rect.nv-measure') - .style('fill', color) - .attr('height', availableHeight / 3) - .attr('y', availableHeight / 3) - .attr('width', measurez < 0 ? - x1(0) - x1(measurez[0]) - : x1(measurez[0]) - x1(0)) - .attr('x', xp1(measurez)) - .on('mouseover', function() { - dispatch.elementMouseover({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") + g.select('rect.nv-measure') + .style('fill', color) + .attr('height', availableHeight / 3) + .attr('y', availableHeight / 3) + .on('mouseover', function() { + dispatch.elementMouseover({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) }) - }) - .on('mousemove', function() { - dispatch.elementMousemove({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") + .on('mousemove', function() { + dispatch.elementMousemove({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) }) - }) - .on('mouseout', function() { - dispatch.elementMouseout({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") + .on('mouseout', function() { + dispatch.elementMouseout({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) }) - }); + .transition() + .duration(duration) + .attr('width', measurez < 0 ? + x1(0) - x1(measurez[0]) + : x1(measurez[0]) - x1(0)) + .attr('x', xp1(measurez)); - var h3 = availableHeight / 6; + var h3 = availableHeight / 6; - var markerData = markerz.map( function(marker, index) { - return {value: marker, label: markerLabelz[index]} - }); - gEnter - .selectAll("path.nv-markerTriangle") - .data(markerData) - .enter() - .append('path') - .attr('class', 'nv-markerTriangle') - .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') - .on('mouseover', function(d) { - dispatch.elementMouseover({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill"), - pos: [x1(d.value), availableHeight/2] - }) + var markerData = markerz.map( function(marker, index) { + return {value: marker, label: markerLabelz[index]} + }); + gEnter + .selectAll("path.nv-markerTriangle") + .data(markerData) + .enter() + .append('path') + .attr('class', 'nv-markerTriangle') + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill"), + pos: [x1(d.value), availableHeight/2] + }) - }) - .on('mousemove', function(d) { - dispatch.elementMousemove({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }) - .on('mouseout', function(d, i) { - dispatch.elementMouseout({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }); + }) + .on('mousemove', function(d) { + dispatch.elementMousemove({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function(d, i) { + dispatch.elementMouseout({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }); - g.selectAll("path.nv-markerTriangle") - .data(markerData) - .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }); + g.selectAll("path.nv-markerTriangle") + .data(markerData) + .transition() + .duration(duration) + .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }); - var markerLinesData = markerLinez.map( function(marker, index) { - return {value: marker, label: markerLineLabelz[index]} - }); - gEnter - .selectAll("path.nv-markerLine") - .data(markerLinesData) - .enter() - .append('line') - .attr('cursor', '') - .attr('class', 'nv-markerLine') - .attr('x1', function(d) { return x1(d.value) }) - .attr('y1', '2') - .attr('x2', function(d) { return x1(d.value) }) - .attr('y2', availableHeight - 2) - .on('mouseover', function(d) { - dispatch.elementMouseover({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill"), - pos: [x1(d.value), availableHeight/2] - }) + var markerLinesData = markerLinez.map( function(marker, index) { + return {value: marker, label: markerLineLabelz[index]} + }); + gEnter + .selectAll("line.nv-markerLine") + .data(markerLinesData) + .enter() + .append('line') + .attr('cursor', '') + .attr('class', 'nv-markerLine') + .attr('x1', function(d) { return x1(d.value) }) + .attr('y1', '2') + .attr('x2', function(d) { return x1(d.value) }) + .attr('y2', availableHeight - 2) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill"), + pos: [x1(d.value), availableHeight/2] + }) - }) - .on('mousemove', function(d) { - dispatch.elementMousemove({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }) - .on('mouseout', function(d, i) { - dispatch.elementMouseout({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }); + }) + .on('mousemove', function(d) { + dispatch.elementMousemove({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function(d, i) { + dispatch.elementMouseout({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }); - g.selectAll("path.nv-markerLines") - .data(markerLinesData) - .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }); + g.selectAll("line.nv-markerLine") + .data(markerLinesData) + .transition() + .duration(duration) + .attr('x1', function(d) { return x1(d.value) }) + .attr('x2', function(d) { return x1(d.value) }); - wrap.selectAll('.nv-range') - .on('mouseover', function(d,i) { - var label = rangeLabelz[i] || defaultRangeLabels[i]; - dispatch.elementMouseover({ - value: d, - label: label, - color: d3.select(this).style("fill") + wrap.selectAll('.nv-range') + .on('mouseover', function(d,i) { + var label = rangeLabelz[i] || defaultRangeLabels[i]; + dispatch.elementMouseover({ + value: d, + label: label, + color: d3.select(this).style("fill") + }) }) - }) - .on('mousemove', function() { - dispatch.elementMousemove({ - value: measurez[0], - label: measureLabelz[0] || 'Previous', - color: d3.select(this).style("fill") + .on('mousemove', function() { + dispatch.elementMousemove({ + value: measurez[0], + label: measureLabelz[0] || 'Previous', + color: d3.select(this).style("fill") + }) }) - }) - .on('mouseout', function(d,i) { - var label = rangeLabelz[i] || defaultRangeLabels[i]; - dispatch.elementMouseout({ - value: d, - label: label, - color: d3.select(this).style("fill") - }) - }); - }); + .on('mouseout', function(d,i) { + var label = rangeLabelz[i] || defaultRangeLabels[i]; + dispatch.elementMouseout({ + value: d, + label: label, + color: d3.select(this).style("fill") + }) + }); + }); - return chart; - } + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) - markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) - measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) + markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) + measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - // 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; - }}, - orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom - orient = _; - reverse = orient == 'right' || orient == 'bottom'; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); + // 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; + }}, + orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); - nv.utils.initOptions(chart); - return chart; -}; + nv.utils.initOptions(chart); + return chart; + }; // Chart design based on the recommendations of Stephen Few. Implementation // based on the work of Clint Ivy, Jamie Love, and Jason Davies. // http://projects.instantcognition.com/protovis/bulletchart/ -nv.models.bulletChart = function() { - "use strict"; + nv.models.bulletChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var bullet = nv.models.bullet(); - var tooltip = nv.models.tooltip(); + var bullet = nv.models.bullet(); + var tooltip = nv.models.tooltip(); - var orient = 'left' // TODO top & bottom - , reverse = false - , margin = {top: 5, right: 40, bottom: 20, left: 120} - , ranges = function(d) { return d.ranges } - , markers = function(d) { return d.markers ? d.markers : [] } - , measures = function(d) { return d.measures } - , width = null - , height = 55 - , tickFormat = null - , ticks = null - , noData = null - , dispatch = d3.dispatch() - ; + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers ? d.markers : [] } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , ticks = null + , noData = null + , dispatch = d3.dispatch() + ; - tooltip - .duration(0) - .headerEnabled(false); + tooltip + .duration(0) + .headerEnabled(false); - function chart(selection) { - selection.each(function(d, i) { - var container = d3.select(this); - nv.utils.initSVG(container); + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = height - margin.top - margin.bottom, - that = this; + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = height - margin.top - margin.bottom, + that = this; - chart.update = function() { chart(selection) }; - chart.container = this; + chart.update = function() { chart(selection) }; + chart.container = this; - // Display No Data message if there's nothing to show. - if (!d || !ranges.call(this, d, i)) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } + // Display No Data message if there's nothing to show. + if (!d || !ranges.call(this, d, i)) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } - var rangez = ranges.call(this, d, i).slice().sort(d3.descending), - markerz = markers.call(this, d, i).slice().sort(d3.descending), - measurez = measures.call(this, d, i).slice().sort(d3.descending); + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-bulletWrap'); - gEnter.append('g').attr('class', 'nv-titles'); + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // Compute the new x-scale. - var x1 = d3.scale.linear() - .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain - .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); - // Retrieve the old x-scale, if this is an update. - var x0 = this.__chart__ || d3.scale.linear() - .domain([0, Infinity]) - .range(x1.range()); + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); - // Stash the new scale. - this.__chart__ = x1; + // Stash the new scale. + this.__chart__ = x1; - var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) - w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; - var title = gEnter.select('.nv-titles').append('g') - .attr('text-anchor', 'end') - .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); - title.append('text') - .attr('class', 'nv-title') - .text(function(d) { return d.title; }); + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); - title.append('text') - .attr('class', 'nv-subtitle') - .attr('dy', '1em') - .text(function(d) { return d.subtitle; }); + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); - bullet - .width(availableWidth) - .height(availableHeight) + bullet + .width(availableWidth) + .height(availableHeight); - var bulletWrap = g.select('.nv-bulletWrap'); - d3.transition(bulletWrap).call(bullet); + var bulletWrap = g.select('.nv-bulletWrap'); + d3.transition(bulletWrap).call(bullet); - // Compute the tick format. - var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); - // Update the tick groups. - var tick = g.selectAll('g.nv-tick') - .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { - return this.textContent || format(d); - }); + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { + return this.textContent || format(d); + }); - // Initialize the ticks with the old scale, x0. - var tickEnter = tick.enter().append('g') - .attr('class', 'nv-tick') - .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) - .style('opacity', 1e-6); + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); - tickEnter.append('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); - tickEnter.append('text') - .attr('text-anchor', 'middle') - .attr('dy', '1em') - .attr('y', availableHeight * 7 / 6) - .text(format); + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); - // Transition the updating ticks to the new scale, x1. - var tickUpdate = d3.transition(tick) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1); + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .transition() + .duration(bullet.duration()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); - tickUpdate.select('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); - tickUpdate.select('text') - .attr('y', availableHeight * 7 / 6); + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); - // Transition the exiting ticks to the new scale, x1. - d3.transition(tick.exit()) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1e-6) - .remove(); - }); + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .transition() + .duration(bullet.duration()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + }); - d3.timer.flush(); - return chart; - } + d3.timer.flush(); + return chart; + } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - bullet.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: evt.label, - value: evt.value, - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); + bullet.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: evt.label, + value: evt.value, + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); - bullet.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + bullet.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - bullet.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + bullet.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.bullet = bullet; - chart.dispatch = dispatch; - chart.tooltip = tooltip; + chart.bullet = bullet; + chart.dispatch = dispatch; + chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); + chart.options = nv.utils.optionsFunc.bind(chart); - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) - markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) - measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, - ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) + markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) + measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, + ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, + 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; - }}, - orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom - orient = _; - reverse = orient == 'right' || orient == 'bottom'; - }} - }); + // 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; + }}, + orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + }} + }); - nv.utils.inheritOptions(chart, bullet); - nv.utils.initOptions(chart); + nv.utils.inheritOptions(chart, bullet); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; -nv.models.candlestickBar = function() { - "use strict"; + nv.models.candlestickBar = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getOpen = function(d) { return d.open } - , getClose = function(d) { return d.close } - , getHigh = function(d) { return d.high } - , getLow = function(d) { return d.low } - , forceX = [] - , forceY = [] - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , clipEdge = true - , color = nv.utils.defaultColor() - , interactive = false - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') - ; + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , interactive = false + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') + ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - function chart(selection) { - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + function chart(selection) { + 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); + nv.utils.initSVG(container); - // Width of the candlestick bars. - var barWidth = (availableWidth / data[0].values.length) * .45; + // Width of the candlestick bars. + var barWidth = (availableWidth / data[0].values.length) * .45; - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]); + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]); - y.domain(yDomain || [ - d3.min(data[0].values.map(getLow).concat(forceY)), - d3.max(data[0].values.map(getHigh).concat(forceY)) - ] - ).range(yRange || [availableHeight, 0]); + y.domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).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 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]); + 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]); - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-ticks'); + gEnter.append('g').attr('class', 'nv-ticks'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); }); - }); - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') - .data(function(d) { return d }); - ticks.exit().remove(); + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + ticks.exit().remove(); - var tickGroups = ticks.enter().append('g'); + var tickGroups = ticks.enter().append('g'); - // The colors are currently controlled by CSS. - ticks - .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i}); + // The colors are currently controlled by CSS. + ticks + .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i}); - var lines = tickGroups.append('line') - .attr('class', 'nv-candlestick-lines') - .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) - .attr('x1', 0) - .attr('y1', function(d, i) { return y(getHigh(d, i)); }) - .attr('x2', 0) - .attr('y2', function(d, i) { return y(getLow(d, i)); }); + var lines = tickGroups.append('line') + .attr('class', 'nv-candlestick-lines') + .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) + .attr('x1', 0) + .attr('y1', function(d, i) { return y(getHigh(d, i)); }) + .attr('x2', 0) + .attr('y2', function(d, i) { return y(getLow(d, i)); }); - var rects = tickGroups.append('rect') - .attr('class', 'nv-candlestick-rects nv-bars') - .attr('transform', function(d, i) { - return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' - + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) - + ')'; - }) - .attr('x', 0) - .attr('y', 0) - .attr('width', barWidth) - .attr('height', function(d, i) { - var open = getOpen(d, i); - var close = getClose(d, i); - return open > close ? y(close) - y(open) : y(open) - y(close); - }); + var rects = tickGroups.append('rect') + .attr('class', 'nv-candlestick-rects nv-bars') + .attr('transform', function(d, i) { + return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' + + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) + + ')'; + }) + .attr('x', 0) + .attr('y', 0) + .attr('width', barWidth) + .attr('height', function(d, i) { + var open = getOpen(d, i); + var close = getClose(d, i); + return open > close ? y(close) - y(open) : y(open) - y(close); + }); - ticks.select('.nv-candlestick-lines').transition() - .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) - .attr('x1', 0) - .attr('y1', function(d, i) { return y(getHigh(d, i)); }) - .attr('x2', 0) - .attr('y2', function(d, i) { return y(getLow(d, i)); }); + ticks.select('.nv-candlestick-lines').transition() + .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) + .attr('x1', 0) + .attr('y1', function(d, i) { return y(getHigh(d, i)); }) + .attr('x2', 0) + .attr('y2', function(d, i) { return y(getLow(d, i)); }); - ticks.select('.nv-candlestick-rects').transition() - .attr('transform', function(d, i) { - return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' - + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) - + ')'; - }) - .attr('x', 0) - .attr('y', 0) - .attr('width', barWidth) - .attr('height', function(d, i) { - var open = getOpen(d, i); - var close = getClose(d, i); - return open > close ? y(close) - y(open) : y(open) - y(close); - }); - }); + ticks.select('.nv-candlestick-rects').transition() + .attr('transform', function(d, i) { + return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' + + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) + + ')'; + }) + .attr('x', 0) + .attr('y', 0) + .attr('width', barWidth) + .attr('height', function(d, i) { + var open = getOpen(d, i); + var close = getClose(d, i); + return open > close ? y(close) - y(open) : y(open) - y(close); + }); + }); - return chart; - } + return chart; + } - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - chart.clearHighlights(); - container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + chart.clearHighlights(); + container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; - chart.clearHighlights = function() { - container.select(".nv-candlestickBar .nv-tick.hover") - .classed("hover", false) - ; - }; + chart.clearHighlights = function() { + container.select(".nv-candlestickBar .nv-tick.hover") + .classed("hover", false) + ; + }; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - 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=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + 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=_;}}, + 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=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, - close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, - high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, - low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, + close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, + high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, + low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, - // 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(_); - }} - }); + // 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.utils.initOptions(chart); + return chart; + }; -nv.models.cumulativeLineChart = function() { - "use strict"; + nv.models.cumulativeLineChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var lines = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; - var margin = {top: 30, right: 30, bottom: 50, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , showControls = true - , useInteractiveGuideline = false - , rescaleY = true - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , id = lines.id() - , state = nv.utils.state() - , defaultState = null - , noData = null - , average = function(d) { return d.average } - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') - , transitionDuration = 250 - , duration = 250 - , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function. - ; + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , marginTop = null + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , showControls = true + , useInteractiveGuideline = false + , rescaleY = true + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = nv.utils.state() + , defaultState = null + , noData = null + , average = function(d) { return d.average } + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') + , transitionDuration = 250 + , duration = 250 + , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function. + ; - state.index = 0; - state.rescaleY = rescaleY; + state.index = 0; + state.rescaleY = rescaleY; - xAxis.orient('bottom').tickPadding(7); - yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); + xAxis.orient('bottom').tickPadding(7); + yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); - tooltip.valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); + tooltip.valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - controls.updateState(false); + controls.updateState(false); - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var dx = d3.scale.linear() - , index = {i: 0, x: 0} - , renderWatch = nv.utils.renderWatch(dispatch, duration) - ; + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - index: index.i, - rescaleY: rescaleY - }; - } - }; + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + index: index.i, + rescaleY: rescaleY + }; + } + }; - var stateSetter = function(data) { - return function(state) { - if (state.index !== undefined) - index.i = state.index; - if (state.rescaleY !== undefined) - rescaleY = state.rescaleY; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; + var stateSetter = function(data) { + return function(state) { + if (state.index !== undefined) + index.i = state.index; + if (state.rescaleY !== undefined) + rescaleY = state.rescaleY; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; - function chart(selection) { - renderWatch.reset(); - renderWatch.models(lines); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - container.classed('nv-chart-' + id, true); - var that = this; + function chart(selection) { + renderWatch.reset(); + renderWatch.models(lines); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + container.classed('nv-chart-' + id, true); + var that = this; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + 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; + 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(); + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); + // 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]; + 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]; + } } - } - var indexDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); - function dragStart(d,i) { - d3.select(chart.container) - .style('cursor', 'ew-resize'); - } + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } - function dragMove(d,i) { - index.x = d3.event.x; - index.i = Math.round(dx.invert(index.x)); - updateZero(); - } + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } - function dragEnd(d,i) { - d3.select(chart.container) - .style('cursor', 'auto'); + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - } + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } - // 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(); - } + // 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 = lines.xScale(); - y = lines.yScale(); + // Setup Scales + x = lines.xScale(); + y = lines.yScale(); - if (!rescaleY) { - var seriesDomains = data - .filter(function(series) { return !series.disabled }) - .map(function(series,i) { - var initialDomain = d3.extent(series.values, lines.y()); + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); - //account for series being disabled when losing 95% or more - if (initialDomain[0] < -.95) initialDomain[0] = -.95; + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; - return [ + return [ (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) - ]; - }); + ]; + }); - var completeDomain = [ - d3.min(seriesDomains, function(d) { return d[0] }), - d3.max(seriesDomains, function(d) { return d[1] }) - ]; + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ]; - lines.yDomain(completeDomain); - } else { - lines.yDomain(null); - } + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } - dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length - .range([0, availableWidth]) - .clamp(true); + dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); - var data = indexify(index.i, data); + var data = indexify(index.i, data); - // Setup containers and skeleton of chart - var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; - var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-interactive'); - gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-background'); - gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); - gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); + gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') } - g.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } + // Controls + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; - // Controls - if (!showControls) { - g.select('.nv-controlsWrap').selectAll('*').remove(); - } else { - var controlsData = [ - { key: 'Re-scale y-axis', disabled: !rescaleY } - ]; + controls + .width(140) + .color(['#444', '#444', '#444']) + .rightAlign(false) + .margin({top: 5, right: 0, bottom: 5, left: 20}) + ; - controls - .width(140) - .color(['#444', '#444', '#444']) - .rightAlign(false) - .margin({top: 5, right: 0, bottom: 5, left: 20}) - ; + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); - // Show error if series goes below 100% - var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } - wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates - if (tempDisabled.length) { - wrap.append('text').attr('class', 'tempDisabled') - .attr('x', availableWidth / 2) - .attr('y', '-.71em') - .style('text-anchor', 'end') - .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); - } + //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); + } - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer + gEnter.select('.nv-background') + .append('rect'); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) .width(availableWidth) .height(availableHeight) - .margin({left:margin.left,top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); - gEnter.select('.nv-background') - .append('rect'); + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); - g.select('.nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + linesWrap.call(lines); - lines - //.x(function(d) { return d.x }) - .y(function(d) { return d.display.y }) - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); + //Store a series index number in the data array. + data.forEach(function(d,i) { + d.seriesIndex = i; + }); - var linesWrap = g.select('.nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + var avgLineData = data.filter(function(d) { + return !d.disabled && !!average(d); + }); - linesWrap.call(lines); + var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") + .data(avgLineData, function(d) { return d.key; }); - //Store a series index number in the data array. - data.forEach(function(d,i) { - d.seriesIndex = i; - }); + var getAvgLineY = function(d) { + //If average lines go off the svg element, clamp them to the svg bounds. + var yVal = y(average(d)); + if (yVal < 0) return 0; + if (yVal > availableHeight) return availableHeight; + return yVal; + }; - var avgLineData = data.filter(function(d) { - return !d.disabled && !!average(d); - }); + avgLines.enter() + .append('line') + .style('stroke-width',2) + .style('stroke-dasharray','10,10') + .style('stroke',function (d,i) { + return lines.color()(d,d.seriesIndex); + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); - var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") - .data(avgLineData, function(d) { return d.key; }); + avgLines + .style('stroke-opacity',function(d){ + //If average lines go offscreen, make them transparent + var yVal = y(average(d)); + if (yVal < 0 || yVal > availableHeight) return 0; + return 1; + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); - var getAvgLineY = function(d) { - //If average lines go off the svg element, clamp them to the svg bounds. - var yVal = y(average(d)); - if (yVal < 0) return 0; - if (yVal > availableHeight) return availableHeight; - return yVal; - }; + avgLines.exit().remove(); - avgLines.enter() - .append('line') - .style('stroke-width',2) - .style('stroke-dasharray','10,10') - .style('stroke',function (d,i) { - return lines.color()(d,d.seriesIndex); - }) - .attr('x1',0) - .attr('x2',availableWidth) - .attr('y1', getAvgLineY) - .attr('y2', getAvgLineY); + //Create index line + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .style("pointer-events","all") + .call(indexDrag); - avgLines - .style('stroke-opacity',function(d){ - //If average lines go offscreen, make them transparent - var yVal = y(average(d)); - if (yVal < 0 || yVal > availableHeight) return 0; - return 1; - }) - .attr('x1',0) - .attr('x2',availableWidth) - .attr('y1', getAvgLineY) - .attr('y2', getAvgLineY); + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight); - avgLines.exit().remove(); + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/70, data) ) + .tickSize(-availableHeight, 0); - //Create index line - var indexLine = linesWrap.selectAll('.nv-indexLine') - .data([index]); - indexLine.enter().append('rect').attr('class', 'nv-indexLine') - .attr('width', 3) - .attr('x', -2) - .attr('fill', 'red') - .attr('fill-opacity', .5) - .style("pointer-events","all") - .call(indexDrag); + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .call(xAxis); + } - indexLine - .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) - .attr('height', availableHeight); + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/70, data) ) - .tickSize(-availableHeight, 0); + g.select('.nv-y.nv-axis') + .call(yAxis); + } - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .call(xAxis); - } + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + function updateZero() { + indexLine + .data([index]); - g.select('.nv-y.nv-axis') - .call(yAxis); - } + //When dragging the index line, turn off line transitions. + // Then turn them back on when done dragging. + var oldDuration = chart.duration(); + chart.duration(0); + chart.update(); + chart.duration(oldDuration); + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); - function updateZero() { - indexLine - .data([index]); + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); - //When dragging the index line, turn off line transitions. - // Then turn them back on when done dragging. - var oldDuration = chart.duration(); - chart.duration(0); - chart.update(); - chart.duration(oldDuration); - } + updateZero(); + }); - g.select('.nv-background rect') - .on('click', function() { - index.x = d3.mouse(this)[0]; - index.i = Math.round(dx.invert(index.x)); + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); // update state and send stateChange with new index state.index = index.i; dispatch.stateChange(state); updateZero(); }); - lines.dispatch.on('elementClick', function(e) { - index.i = e.pointIndex; - index.x = dx(index.i); + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); + state.rescaleY = rescaleY; + dispatch.stateChange(state); + chart.update(); + }); - updateZero(); - }); + 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) { - d.disabled = !d.disabled; - rescaleY = !d.disabled; + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; - state.rescaleY = rescaleY; - dispatch.stateChange(state); - chart.update(); - }); + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + lines.highlightPoint(i, pointIndex, true); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex) + }); + }); - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); + //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; + } - interactiveLayer.dispatch.on('elementMousemove', function(e) { - lines.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); + interactiveLayer.tooltip + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data( + { + value: xValue, + series: allData + } + )(); - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - lines.highlightPoint(i, pointIndex, true); - var point = series.values[pointIndex]; - if (typeof point === 'undefined') return; - if (typeof singlePoint === 'undefined') singlePoint = point; - if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: chart.y()(point, pointIndex), - color: color(series,series.seriesIndex) - }); - }); + interactiveLayer.renderGuideLine(pointXLocation); + }); - //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; - } + interactiveLayer.dispatch.on("elementMouseout",function(e) { + lines.clearHighlights(); + }); - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); - interactiveLayer.tooltip - .valueFormatter(function(d,i) { - return yAxis.tickFormat()(d); - }) - .data( - { - value: xValue, - series: allData + // 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; } - )(); - interactiveLayer.renderGuideLine(pointXLocation); - }); + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); - interactiveLayer.dispatch.on("elementMouseout",function(e) { - lines.clearHighlights(); - }); + state.index = e.index; - // 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]; - }); + indexLine + .data([index]); + } - state.disabled = e.disabled; - } + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } - if (typeof e.index !== 'undefined') { - index.i = e.index; - index.x = dx(index.i); + chart.update(); + }); - state.index = e.index; + }); - indexLine - .data([index]); - } + renderWatch.renderEnd('cumulativeLineChart immediate'); - if (typeof e.rescaleY !== 'undefined') { - rescaleY = e.rescaleY; - } + return chart; + } - chart.update(); - }); + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + var point = { + x: chart.x()(evt.point), + y: chart.y()(evt.point), + color: evt.point.color + }; + evt.point = point; + tooltip.data(evt).hidden(false); }); - renderWatch.renderEnd('cumulativeLineChart immediate'); + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); - return chart; - } + //============================================================ + // Functions + //------------------------------------------------------------ - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + var indexifyYGetter = null; + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + if (!indexifyYGetter) indexifyYGetter = lines.y(); + return data.map(function(line, i) { + if (!line.values) { + return line; + } + var indexValue = line.values[idx]; + if (indexValue == null) { + return line; + } + var v = indexifyYGetter(indexValue, idx); - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - var point = { - x: chart.x()(evt.point), - y: chart.y()(evt.point), - color: evt.point.color - }; - evt.point = point; - tooltip.data(evt).hidden(false); - }); + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95 && !noErrorCheck) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); + line.tempDisabled = true; + return line; + } - //============================================================ - // Functions - //------------------------------------------------------------ + line.tempDisabled = false; - var indexifyYGetter = null; - /* Normalize the data according to an index point. */ - function indexify(idx, data) { - if (!indexifyYGetter) indexifyYGetter = lines.y(); - return data.map(function(line, i) { - if (!line.values) { - return line; - } - var indexValue = line.values[idx]; - if (indexValue == null) { - return line; - } - var v = indexifyYGetter(indexValue, idx); + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) }; + return point; + }); - //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue - if (v < -.95 && !noErrorCheck) { - //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) - - line.tempDisabled = true; return line; - } + }) + } - line.tempDisabled = false; + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - line.values = line.values.map(function(point, pointIndex) { - point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) }; - return point; - }); + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.state = state; + chart.tooltip = tooltip; - return line; - }) - } + chart.options = nv.utils.optionsFunc.bind(chart); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + 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=_;}}, + rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + average: {get: function(){return average;}, set: function(_){average=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}}, - // expose chart's sub-components - chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.state = state; - chart.tooltip = tooltip; + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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(_); + legend.color(color); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + chart.useVoronoi(false); + } + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + lines.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + renderWatch.reset(duration); + }} + }); - chart.options = nv.utils.optionsFunc.bind(chart); + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(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=_;}}, - rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - average: {get: function(){return average;}, set: function(_){average=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}}, - - // 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(_); - legend.color(color); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (_ === true) { - chart.interactive(false); - chart.useVoronoi(false); - } - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - lines.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - renderWatch.reset(duration); - }} - }); - - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); - - return chart; -}; + return chart; + }; //TODO: consider deprecating by adding necessary features to multiBar model -nv.models.discreteBar = function() { - "use strict"; + nv.models.discreteBar = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 - ; + 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 - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var x0, y0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + 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; + 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); + 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; + //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 } - }) - }); + // 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))); + 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]); + // 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)]); + //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'); + // 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 + ')'); + 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); + //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 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") + 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(); }); - }) - .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 ) + barsEnter.append('rect') + .attr('height', 0) + .attr('width', x.rangeBand() * .9 / data.length ) - if (showValues) { - barsEnter.append('text') - .attr('text-anchor', 'middle') - ; + 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 }) + 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(); - } + ; + } 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') + 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) : + .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)); + 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) - }); + 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(); + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); - }); + }); - renderWatch.renderEnd('discreteBar immediate'); - return chart; - } + renderWatch.renderEnd('discreteBar immediate'); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, + 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); - }} - }); + // 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); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; -nv.models.discreteBarChart = function() { - "use strict"; + nv.models.discreteBarChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 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 + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , marginTop = null + , 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')) + ; - 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); + }); - 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 + //------------------------------------------------------------ - //============================================================ - // Private Variables - //------------------------------------------------------------ + var renderWatch = nv.utils.renderWatch(dispatch, duration); - 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); - 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); - 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; - 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(); + } - // 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 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'); - // 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-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'); - gEnter.append('g').attr('class', 'nv-barsWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') } - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + // Main Chart Component(s) + discretebar + .width(availableWidth) + .height(availableHeight); - // Main Chart Component(s) - discretebar - .width(availableWidth) - .height(availableHeight); + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); + barsWrap.transition().call(discretebar); - barsWrap.transition().call(discretebar); + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); - 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 )); - 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); - // 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); - 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') + ')' }) + } - 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 (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 (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); } - } - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + // Zero line + g.select(".nv-zeroLine line") + .attr("x1",0) + .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth) + .attr("y1", y(0)) + .attr("y2", y(0)) + ; + }); - g.select('.nv-y.nv-axis').call(yAxis); - } + renderWatch.renderEnd('discreteBar chart immediate'); + return chart; + } - // Zero line - g.select(".nv-zeroLine line") - .attr("x1",0) - .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth) - .attr("y1", y(0)) - .attr("y2", y(0)) - ; + //============================================================ + // 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); }); - renderWatch.renderEnd('discreteBar chart immediate'); - return chart; - } + discretebar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + discretebar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - 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); - }); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - discretebar.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + chart.dispatch = dispatch; + chart.discretebar = discretebar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.tooltip = tooltip; - discretebar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + chart.options = nv.utils.optionsFunc.bind(chart); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + 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=_;}}, - chart.dispatch = dispatch; - chart.discretebar = discretebar; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.tooltip = tooltip; + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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'); + }} + }); - chart.options = nv.utils.optionsFunc.bind(chart); + nv.utils.inheritOptions(chart, discretebar); + nv.utils.initOptions(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=_;}}, + return chart; + } - // 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.models.distribution = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - nv.utils.inheritOptions(chart, discretebar); - nv.utils.initOptions(chart); + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain + , duration = 250 + , dispatch = d3.dispatch('renderEnd') + ; - return chart; -} + //============================================================ -nv.models.distribution = function() { - "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 400 //technically width or height depending on x or y.... - , size = 8 - , axis = 'x' // 'x' or 'y'... horizontal or vertical - , getData = function(d) { return d[axis] } // defaults d.x or d.y - , color = nv.utils.defaultColor() - , scale = d3.scale.linear() - , domain - , duration = 250 - , dispatch = d3.dispatch('renderEnd') - ; + //============================================================ + // Private Variables + //------------------------------------------------------------ - //============================================================ + var scale0; + var renderWatch = nv.utils.renderWatch(dispatch, duration); + //============================================================ - //============================================================ - // Private Variables - //------------------------------------------------------------ - var scale0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); + nv.utils.initSVG(container); - //============================================================ + //------------------------------------------------------------ + // Setup Scales + scale0 = scale0 || scale; - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), - naxis = axis == 'x' ? 'y' : 'x', - container = d3.select(this); - nv.utils.initSVG(container); + //------------------------------------------------------------ - //------------------------------------------------------------ - // Setup Scales - scale0 = scale0 || scale; + //------------------------------------------------------------ + // Setup containers and skeleton of chart - //------------------------------------------------------------ + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - //------------------------------------------------------------ - // Setup containers and skeleton of chart + //------------------------------------------------------------ - var wrap = container.selectAll('g.nv-distribution').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); - //------------------------------------------------------------ + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit') + // .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + renderWatch.transition(dist, 'dist') + // .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - var distWrap = g.selectAll('g.nv-dist') - .data(function(d) { return d }, function(d) { return d.key }); - distWrap.enter().append('g'); - distWrap - .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) - .style('stroke', function(d,i) { return color(d, i) }); + scale0 = scale.copy(); - var dist = distWrap.selectAll('line.nv-dist' + axis) - .data(function(d) { return d.values }) - dist.enter().append('line') - .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) - renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit') - // .transition() - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - .style('stroke-opacity', 0) - .remove(); - dist - .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) - .attr(naxis + '1', 0) - .attr(naxis + '2', size); - renderWatch.transition(dist, 'dist') - // .transition() - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + }); + renderWatch.renderEnd('distribution immediate'); + return chart; + } - scale0 = scale.copy(); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.options = nv.utils.optionsFunc.bind(chart); + chart.dispatch = dispatch; - }); - renderWatch.renderEnd('distribution immediate'); - return chart; - } + chart.margin = function(_) { + if (!arguments.length) return margin; + 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; + return chart; + }; + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.options = nv.utils.optionsFunc.bind(chart); - chart.dispatch = dispatch; + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; + return chart; + }; - chart.margin = function(_) { - if (!arguments.length) return margin; - 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; - return chart; - }; + chart.size = function(_) { + if (!arguments.length) return size; + size = _; + return chart; + }; - chart.width = function(_) { - if (!arguments.length) return width; - width = _; - return chart; - }; + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); + return chart; + }; - chart.axis = function(_) { - if (!arguments.length) return axis; - axis = _; - return chart; - }; + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + return chart; + }; - chart.size = function(_) { - if (!arguments.length) return size; - size = _; - return chart; - }; + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; - chart.getData = function(_) { - if (!arguments.length) return getData; - getData = d3.functor(_); - return chart; - }; + chart.duration = function(_) { + if (!arguments.length) return duration; + duration = _; + renderWatch.reset(duration); + return chart; + }; + //============================================================ - chart.scale = function(_) { - if (!arguments.length) return scale; - scale = _; - return chart; - }; - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); return chart; - }; + } + nv.models.focus = function(content) { + "use strict"; - chart.duration = function(_) { - if (!arguments.length) return duration; - duration = _; - renderWatch.reset(duration); - return chart; - }; - //============================================================ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + var content = content || nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , brush = d3.svg.brush() + ; - return chart; -} -nv.models.focus = function(content) { - "use strict"; + var margin = {top: 10, right: 0, bottom: 30, left: 0} + , color = nv.utils.defaultColor() + , width = null + , height = 70 + , showXAxis = true + , showYAxis = false + , rightAlignYAxis = false + , ticks = null + , x + , y + , brushExtent = null + , duration = 250 + , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd') + , syncBrushing = true + ; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + content.interactive(false); + content.pointActive(function(d) { return false; }); - var content = content || nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , brush = d3.svg.brush() - ; + //============================================================ + // Private Variables + //------------------------------------------------------------ - var margin = {top: 10, right: 0, bottom: 30, left: 0} - , color = nv.utils.defaultColor() - , width = null - , height = 70 - , showXAxis = true - , showYAxis = false - , rightAlignYAxis = false - , ticks = null - , x - , y - , brushExtent = null - , duration = 250 - , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd') - ; + var renderWatch = nv.utils.renderWatch(dispatch, duration); - content.interactive(false); - content.pointActive(function(d) { return false; }); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(content); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); - //============================================================ - // Private Variables - //------------------------------------------------------------ + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = height - margin.top - margin.bottom; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + chart.update = function() { + if( duration === 0 ) { + container.call( chart ); + } else { + container.transition().duration(duration).call(chart); + } + }; + chart.container = this; - function chart(selection) { - renderWatch.reset(); - renderWatch.models(content); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + // Setup Scales + x = content.xScale(); + y = content.yScale(); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = height - margin.top - margin.bottom; + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-focus').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g'); + var g = wrap.select('g'); - chart.update = function() { - if( duration === 0 ) { - container.call( chart ); - } else { - container.transition().duration(duration).call(chart); - } - }; - chart.container = this; + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // Setup Scales - x = content.xScale(); - y = content.yScale(); + gEnter.append('g').attr('class', 'nv-background').append('rect'); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-contentWrap'); + gEnter.append('g').attr('class', 'nv-brushBackground'); + gEnter.append('g').attr('class', 'nv-x nv-brush'); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-focus').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g'); - var g = wrap.select('g'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - gEnter.append('g').attr('class', 'nv-background').append('rect'); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-contentWrap'); - gEnter.append('g').attr('class', 'nv-brushBackground'); - gEnter.append('g').attr('class', 'nv-x nv-brush'); + content + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled; })); - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + var contentWrap = g.select('.nv-contentWrap') + .datum(data.filter(function(d) { return !d.disabled; })); - g.select('.nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + d3.transition(contentWrap).call(content); - content - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled; })); + // Setup Brush + brush + .x(x) + .on('brush', function() { + onBrush(syncBrushing); + }); - var contentWrap = g.select('.nv-contentWrap') - .datum(data.filter(function(d) { return !d.disabled; })); + brush.on('brushend', function () { + if (!syncBrushing) { + dispatch.onBrush(brush.empty() ? x.domain() : brush.extent()); + } + }); - d3.transition(contentWrap).call(content); + if (brushExtent) brush.extent(brushExtent); - // Setup Brush - brush - .x(x) - .on('brush', function() { - onBrush(); - }); + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]); - if (brushExtent) brush.extent(brushExtent); + var brushBGenter = brushBG.enter() + .append('g'); - var brushBG = g.select('.nv-brushBackground').selectAll('g') - .data([brushExtent || brush.extent()]); + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight); - var brushBGenter = brushBG.enter() - .append('g'); + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight); - brushBGenter.append('rect') - .attr('class', 'left') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight); + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + .attr('height', availableHeight); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); - brushBGenter.append('rect') - .attr('class', 'right') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight); + onBrush(true); - var gBrush = g.select('.nv-x.nv-brush') - .call(brush); - gBrush.selectAll('rect') - .attr('height', availableHeight); - gBrush.selectAll('.resize').append('path').attr('d', resizePath); + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - onBrush(); + if (showXAxis) { + xAxis.scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); - g.select('.nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + } - if (showXAxis) { - xAxis.scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + } + g.select('.nv-x.nv-axis') .attr('transform', 'translate(0,' + y.range()[0] + ')'); - d3.transition(g.select('.nv-x.nv-axis')) - .call(xAxis); - } - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - d3.transition(g.select('.nv-y.nv-axis')) - .call(yAxis); - } + //============================================================ + // Functions + //------------------------------------------------------------ - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight / 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); + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - //============================================================ - // Functions - //------------------------------------------------------------ + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x(d[0]) - x.range()[0], + rightWidth = availableWidth - x(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); - // Taken from crossfilter (http://square.github.com/crossfilter/) - function resizePath(d) { - var e = +(d == 'e'), - x = e ? 1 : -1, - y = availableHeight / 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); - } + d3.select(this).select('.right') + .attr('x', x(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } - function updateBrushBG() { - if (!brush.empty()) brush.extent(brushExtent); - brushBG - .data([brush.empty() ? x.domain() : brushExtent]) - .each(function(d,i) { - var leftWidth = x(d[0]) - x.range()[0], - rightWidth = availableWidth - x(d[1]); - d3.select(this).select('.left') - .attr('width', leftWidth < 0 ? 0 : leftWidth); + function onBrush(shouldDispatch) { + brushExtent = brush.empty() ? null : brush.extent(); + var extent = brush.empty() ? x.domain() : brush.extent(); + dispatch.brush({extent: extent, brush: brush}); + updateBrushBG(); + if (shouldDispatch) { + dispatch.onBrush(extent); + } + } + }); - d3.select(this).select('.right') - .attr('x', x(d[1])) - .attr('width', rightWidth < 0 ? 0 : rightWidth); - }); - } + renderWatch.renderEnd('focus immediate'); + return chart; + } - function onBrush() { - brushExtent = brush.empty() ? null : brush.extent(); - var extent = brush.empty() ? x.domain() : brush.extent(); + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - //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; - } + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - dispatch.brush({extent: extent, brush: brush}); + // expose chart's sub-components + chart.dispatch = dispatch; + chart.content = content; + chart.brush = brush; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.options = nv.utils.optionsFunc.bind(chart); - updateBrushBG(); - dispatch.onBrush(extent); - } + 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=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + syncBrushing: {get: function(){return syncBrushing;}, set: function(_){syncBrushing=_;}}, - + // 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); + content.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + content.color(color); + }}, + interpolate: {get: function(){return content.interpolate();}, set: function(_){ + content.interpolate(_); + }}, + xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ + xAxis.tickFormat(_); + }}, + yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ + yAxis.tickFormat(_); + }}, + x: {get: function(){return content.x();}, set: function(_){ + content.x(_); + }}, + y: {get: function(){return content.y();}, set: function(_){ + content.y(_); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }} }); - renderWatch.renderEnd('focus immediate'); + nv.utils.inheritOptions(chart, content); + nv.utils.initOptions(chart); + return chart; - } + }; + nv.models.forceDirectedGraph = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , container = null + , dispatch = d3.dispatch('renderEnd') + , color = nv.utils.getColor(['#000']) + , tooltip = nv.models.tooltip() + , noData = null + // Force directed graph specific parameters [default values] + , linkStrength = 0.1 + , friction = 0.9 + , linkDist = 30 + , charge = -120 + , gravity = 0.1 + , theta = 0.8 + , alpha = 0.1 + , radius = 5 + // These functions allow to add extra attributes to ndes and links + ,nodeExtras = function(nodes) { /* Do nothing */ } + ,linkExtras = function(links) { /* Do nothing */ } + ; - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.content = content; - chart.brush = brush; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.options = nv.utils.optionsFunc.bind(chart); + var renderWatch = nv.utils.renderWatch(dispatch); - 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=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + function chart(selection) { + renderWatch.reset(); - // 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); - content.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - content.color(color); - }}, - interpolate: {get: function(){return content.interpolate();}, set: function(_){ - content.interpolate(_); - }}, - xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ - xAxis.tickFormat(_); - }}, - yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ - yAxis.tickFormat(_); - }}, - x: {get: function(){return content.x();}, set: function(_){ - content.x(_); - }}, - y: {get: function(){return content.y();}, set: function(_){ - content.y(_); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( rightAlignYAxis ? 'right' : 'left'); - }}, - }); + selection.each(function(data) { + container = d3.select(this); + nv.utils.initSVG(container); - nv.utils.inheritOptions(chart, content); - nv.utils.initOptions(chart); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); - return chart; -}; -nv.models.forceDirectedGraph = function() { - "use strict"; + container + .attr("width", availableWidth) + .attr("height", availableHeight); - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - var margin = {top: 2, right: 0, bottom: 2, left: 0} - , width = 400 - , height = 32 - , container = null - , dispatch = d3.dispatch('renderEnd') - , color = nv.utils.getColor(['#000']) - , tooltip = nv.models.tooltip() - , noData = null - // Force directed graph specific parameters [default values] - , linkStrength = 0.1 - , friction = 0.9 - , linkDist = 30 - , charge = -120 - , gravity = 0.1 - , theta = 0.8 - , alpha = 0.1 - , radius = 5 - // These functions allow to add extra attributes to ndes and links - ,nodeExtras = function(nodes) { /* Do nothing */ } - ,linkExtras = function(links) { /* Do nothing */ } - ; + // Display No Data message if there's nothing to show. + if (!data || !data.links || !data.nodes) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + container.selectAll('*').remove(); + // Collect names of all fields in the nodes + var nodeFieldSet = new Set(); + data.nodes.forEach(function(node) { + var keys = Object.keys(node); + keys.forEach(function(key) { + nodeFieldSet.add(key); + }); + }); - //============================================================ - // Private Variables - //------------------------------------------------------------ + var force = d3.layout.force() + .nodes(data.nodes) + .links(data.links) + .size([availableWidth, availableHeight]) + .linkStrength(linkStrength) + .friction(friction) + .linkDistance(linkDist) + .charge(charge) + .gravity(gravity) + .theta(theta) + .alpha(alpha) + .start(); - var renderWatch = nv.utils.renderWatch(dispatch); + var link = container.selectAll(".link") + .data(data.links) + .enter().append("line") + .attr("class", "nv-force-link") + .style("stroke-width", function(d) { return Math.sqrt(d.value); }); - function chart(selection) { - renderWatch.reset(); + var node = container.selectAll(".node") + .data(data.nodes) + .enter() + .append("g") + .attr("class", "nv-force-node") + .call(force.drag); - selection.each(function(data) { - container = d3.select(this); - nv.utils.initSVG(container); + node + .append("circle") + .attr("r", radius) + .style("fill", function(d) { return color(d) } ) + .on("mouseover", function(evt) { + container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', evt.py); + container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', evt.px); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + // Add 'series' object to + var nodeColor = color(evt); + evt.series = []; + nodeFieldSet.forEach(function(field) { + evt.series.push({ + color: nodeColor, + key: field, + value: evt[field] + }); + }); + tooltip.data(evt).hidden(false); + }) + .on("mouseout", function(d) { + tooltip.hidden(true); + }); - container - .attr("width", availableWidth) - .attr("height", availableHeight); + tooltip.headerFormatter(function(d) {return "Node";}); - // Display No Data message if there's nothing to show. - if (!data || !data.links || !data.nodes) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - container.selectAll('*').remove(); + // Apply extra attributes to nodes and links (if any) + linkExtras(link); + nodeExtras(node); - // Collect names of all fields in the nodes - var nodeFieldSet = new Set(); - data.nodes.forEach(function(node) { - var keys = Object.keys(node); - keys.forEach(function(key) { - nodeFieldSet.add(key); + force.on("tick", function() { + link.attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + node.attr("transform", function(d) { + return "translate(" + d.x + ", " + d.y + ")"; + }); + }); }); - }); - var force = d3.layout.force() - .nodes(data.nodes) - .links(data.links) - .size([availableWidth, availableHeight]) - .linkStrength(linkStrength) - .friction(friction) - .linkDistance(linkDist) - .charge(charge) - .gravity(gravity) - .theta(theta) - .alpha(alpha) - .start(); + return chart; + } - var link = container.selectAll(".link") - .data(data.links) - .enter().append("line") - .attr("class", "nv-force-link") - .style("stroke-width", function(d) { return Math.sqrt(d.value); }); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - var node = container.selectAll(".node") - .data(data.nodes) - .enter() - .append("g") - .attr("class", "nv-force-node") - .call(force.drag); + chart.options = nv.utils.optionsFunc.bind(chart); - node - .append("circle") - .attr("r", radius) - .style("fill", function(d) { return color(d) } ) - .on("mouseover", function(evt) { - container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) - .attr('y1', evt.py); - container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) - .attr('x2', evt.px); + 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=_;}}, - // Add 'series' object to - var nodeColor = color(evt); - evt.series = []; - nodeFieldSet.forEach(function(field) { - evt.series.push({ - color: nodeColor, - key: field, - value: evt[field] - }); - }); - tooltip.data(evt).hidden(false); - }) - .on("mouseout", function(d) { - tooltip.hidden(true); - }); + // Force directed graph specific parameters + linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}}, + friction: {get: function(){return friction;}, set: function(_){friction=_;}}, + linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}}, + charge: {get: function(){return charge;}, set: function(_){charge=_;}}, + gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, + theta: {get: function(){return theta;}, set: function(_){theta=_;}}, + alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}}, + radius: {get: function(){return radius;}, set: function(_){radius=_;}}, - tooltip.headerFormatter(function(d) {return "Node";}); + //functor options + x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, - // Apply extra attributes to nodes and links (if any) - linkExtras(link); - nodeExtras(node); - - force.on("tick", function() { - link.attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }); - - node.attr("transform", function(d) { - return "translate(" + d.x + ", " + d.y + ")"; - }); - }); + // 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(_); + }}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + nodeExtras: {get: function(){return nodeExtras;}, set: function(_){ + nodeExtras = _; + }}, + linkExtras: {get: function(){return linkExtras;}, set: function(_){ + linkExtras = _; + }} }); + chart.dispatch = dispatch; + chart.tooltip = tooltip; + nv.utils.initOptions(chart); return chart; - } + }; + nv.models.furiousLegend = function() { + "use strict"; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - chart.options = nv.utils.optionsFunc.bind(chart); + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , keyFormatter = function (d) { return d } + , color = nv.utils.getColor() + , maxKeyLength = 20 //default value for key lengths + , align = true + , padding = 28 //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" + ; - 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=_;}}, + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + nv.utils.initSVG(container); - // Force directed graph specific parameters - linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}}, - friction: {get: function(){return friction;}, set: function(_){friction=_;}}, - linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}}, - charge: {get: function(){return charge;}, set: function(_){charge=_;}}, - gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, - theta: {get: function(){return theta;}, set: function(_){theta=_;}}, - alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}}, - radius: {get: function(){return radius;}, set: function(_){radius=_;}}, + // 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'); - //functor options - x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // 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(_); - }}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - nodeExtras: {get: function(){return nodeExtras;}, set: function(_){ - nodeExtras = _; - }}, - linkExtras: {get: function(){return linkExtras;}, set: function(_){ - linkExtras = _; - }} - }); + var series = g.selectAll('.nv-series') + .data(function(d) { + if(vers != 'furious') return d; - chart.dispatch = dispatch; - chart.tooltip = tooltip; - nv.utils.initOptions(chart); - return chart; -}; -nv.models.furiousLegend = function() { - "use strict"; + return d.filter(function(n) { + return expanded ? true : !n.disengaged; + }); + }); + var seriesEnter = series.enter().append('g').attr('class', 'nv-series') - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + var seriesShape; - var margin = {top: 5, right: 0, bottom: 5, left: 0} - , width = 400 - , height = 20 - , getKey = function(d) { return d.key } - , keyFormatter = function (d) { return d } - , color = nv.utils.getColor() - , maxKeyLength = 20 //default value for key lengths - , align = true - , padding = 28 //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" - ; + if(vers == 'classic') { + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('r', 5); - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - container = d3.select(this); - nv.utils.initSVG(container); + 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); - // 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'); + seriesShape = series.select('rect'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + 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 series = g.selectAll('.nv-series') - .data(function(d) { - if(vers != 'furious') return d; + var seriesCheckbox = series.select('.nv-check-box'); - return d.filter(function(n) { - return expanded ? true : !n.disengaged; + seriesCheckbox.each(function(d,i) { + d3.select(this).selectAll('path') + .attr('stroke', setTextColor(d,i)); }); - }); - var seriesEnter = series.enter().append('g').attr('class', 'nv-series') + } - var seriesShape; + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('class','nv-legend-text') + .attr('dy', '.32em') + .attr('dx', '8'); - if(vers == 'classic') { - seriesEnter.append('circle') - .style('stroke-width', 2) - .attr('class','nv-legend-symbol') - .attr('r', 5); + var seriesText = series.select('text.nv-legend-text'); - 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('rect'); - - 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}); + 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 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; - }); + 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 }) + }); + } - 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 }) + }); + } + }); - } - }) - .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(); - series.classed('nv-disabled', function(d) { return d.userDisabled }); - series.exit().remove(); + seriesText + .attr('fill', setTextColor) + .text(function (d) { return keyFormatter(getKey(d)) }); - seriesText - .attr('fill', setTextColor) - .text(function (d) { return keyFormatter(getKey(d)) }); + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + // NEW ALIGNING CODE, TODO: clean up - //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) - // NEW ALIGNING CODE, TODO: clean up + var versPadding; + switch(vers) { + case 'furious' : + versPadding = 23; + break; + case 'classic' : + versPadding = 20; + } - var versPadding; - switch(vers) { - case 'furious' : - versPadding = 23; - break; - case 'classic' : - versPadding = 20; - } + if (align) { - if (align) { + var seriesWidths = []; + series.each(function(d,i) { + var legendText; + if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { + var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); + legendText = d3.select(this).select('text').text(trimmedKey + "..."); + d3.select(this).append("svg:title").text(keyFormatter(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); + } - var seriesWidths = []; - series.each(function(d,i) { - var legendText; - if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { - var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); - legendText = d3.select(this).select('text').text(trimmedKey + "..."); - d3.select(this).append("svg:title").text(keyFormatter(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); + }); - seriesWidths.push(nodeTextLength + padding); - }); + var seriesPerRow = 0; + var legendWidth = 0; + var columnWidths = []; - var seriesPerRow = 0; - var legendWidth = 0; - var columnWidths = []; + 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 < 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--; - 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]; + } - 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; + }); } - 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]; + } - 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) + ')'; + }); - 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 + ')'); + } - //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); - height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); + } else { - } 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; - 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; + } - if (width < margin.left + margin.right + xpos + length) { - newxpos = xpos = 5; - ypos += versPadding; - } + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; - newxpos += length; - if (newxpos > maxwidth) maxwidth = newxpos; + return 'translate(' + xpos + ',' + ypos + ')'; + }); - 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 + ')'); - //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; + } - 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) + } - 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) - } + .style('fill', setBGColor) + .style('stroke', function(d,i) { return d.color || color(d, i) }); + }); - seriesShape - .style('fill', setBGColor) - .style('stroke', function(d,i) { return d.color || color(d, i) }); - }); - - function setTextColor(d,i) { - if(vers != 'furious') return '#000'; - if(expanded) { - return d.disengaged ? color(d,i) : '#fff'; - } else if (!expanded) { - return !!d.disabled ? color(d,i) : '#fff'; + function setTextColor(d,i) { + if(vers != 'furious') return '#000'; + if(expanded) { + return d.disengaged ? color(d,i) : '#fff'; + } else if (!expanded) { + return !!d.disabled ? color(d,i) : '#fff'; + } } - } - function setBGColor(d,i) { - if(expanded && vers == 'furious') { - return d.disengaged ? '#fff' : color(d,i); - } else { - return !!d.disabled ? '#fff' : color(d,i); + function setBGColor(d,i) { + if(expanded && vers == 'furious') { + return d.disengaged ? '#fff' : color(d,i); + } else { + return !!d.disabled ? '#fff' : color(d,i); + } } + + return chart; } - return chart; - } + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - 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=_;}}, + keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, + align: {get: function(){return align;}, set: function(_){align=_;}}, + rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, + maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}}, + 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=_;}}, - 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=_;}}, - keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, - align: {get: function(){return align;}, set: function(_){align=_;}}, - rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, - maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}}, - 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(_); + }} + }); - // 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); - nv.utils.initOptions(chart); - - return chart; -}; + return chart; + }; //TODO: consider deprecating and using multibar with single series for this -nv.models.historicalBar = function() { - "use strict"; + nv.models.historicalBar = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , forceX = [] - , forceY = [0] - , padData = false - , clipEdge = true - , color = nv.utils.defaultColor() - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - , interactive = true - ; + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + , interactive = true + ; - var renderWatch = nv.utils.renderWatch(dispatch, 0); + var renderWatch = nv.utils.renderWatch(dispatch, 0); - function chart(selection) { - selection.each(function(data) { - renderWatch.reset(); + function chart(selection) { + selection.each(function(data) { + renderWatch.reset(); - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); - nv.utils.initSVG(container); + nv.utils.initSVG(container); - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [0, availableWidth]); + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); - y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) - .range(yRange || [availableHeight, 0]); + y.domain(yDomain || d3.extent(data[0].values.map(getY).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 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]); + 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]); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-bars'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + gEnter.append('g').attr('class', 'nv-bars'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); }); - }); - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - var bars = wrap.select('.nv-bars').selectAll('.nv-bar') - .data(function(d) { return d }, function(d,i) {return getX(d,i)}); - bars.exit().remove(); + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }, function(d,i) {return getX(d,i)}); + bars.exit().remove(); - bars.enter().append('rect') - .attr('x', 0 ) - .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) - .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) - .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) - .on('mouseover', function(d,i) { - if (!interactive) return; - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); + bars.enter().append('rect') + .attr('x', 0 ) + .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + .on('mouseover', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); - }) - .on('mouseout', function(d,i) { - if (!interactive) return; - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") + }) + .on('mouseout', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mousemove', function(d,i) { + if (!interactive) return; + dispatch.elementMousemove({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('click', function(d,i) { + if (!interactive) return; + 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) { + if (!interactive) return; + dispatch.elementDblClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); }); - }) - .on('mousemove', function(d,i) { - if (!interactive) return; - dispatch.elementMousemove({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('click', function(d,i) { - if (!interactive) return; - dispatch.elementClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - if (!interactive) return; - dispatch.elementDblClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }); - bars - .attr('fill', function(d,i) { return color(d, i); }) - .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) - .watchTransition(renderWatch, 'bars') - .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) - //TODO: better width calculations that don't assume always uniform data spacing;w - .attr('width', (availableWidth / data[0].values.length) * .9 ); + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .watchTransition(renderWatch, 'bars') + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ); - bars.watchTransition(renderWatch, 'bars') - .attr('y', function(d,i) { - var rval = getY(d,i) < 0 ? - y(0) : + bars.watchTransition(renderWatch, 'bars') + .attr('y', function(d,i) { + var rval = getY(d,i) < 0 ? + y(0) : y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : - y(getY(d,i)); - return nv.utils.NaNtoZero(rval); - }) - .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); + y(0) - 1 : + y(getY(d,i)); + return nv.utils.NaNtoZero(rval); + }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); - }); + }); - renderWatch.renderEnd('historicalBar immediate'); - return chart; - } + renderWatch.renderEnd('historicalBar immediate'); + return chart; + } - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - container - .select(".nv-bars .nv-bar-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + container + .select(".nv-bars .nv-bar-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; - chart.clearHighlights = function() { - container - .select(".nv-bars .nv-bar.hover") - .classed("hover", false) - ; - }; + chart.clearHighlights = function() { + container + .select(".nv-bars .nv-bar.hover") + .classed("hover", false) + ; + }; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - 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=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + 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=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + 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=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - // 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(_); - }} - }); + // 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); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; -nv.models.historicalBarChart = function(bar_model) { - "use strict"; + nv.models.historicalBarChart = function(bar_model) { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var bars = bar_model || nv.models.historicalBar() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; + var bars = bar_model || nv.models.historicalBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; - var margin = {top: 30, right: 90, bottom: 50, left: 90} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = false - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , useInteractiveGuideline = false - , x - , y - , state = {} - , defaultState = null - , noData = null - , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd') - , transitionDuration = 250 - ; + var margin = {top: 30, right: 90, bottom: 50, left: 90} + , marginTop = null + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = false + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , x + , y + , state = {} + , defaultState = null + , noData = null + , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd') + , transitionDuration = 250 + ; - xAxis.orient('bottom').tickPadding(7); - yAxis.orient( (rightAlignYAxis) ? 'right' : 'left'); - tooltip - .duration(0) - .headerEnabled(false) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); + xAxis.orient('bottom').tickPadding(7); + yAxis.orient( (rightAlignYAxis) ? 'right' : 'left'); + tooltip + .duration(0) + .headerEnabled(false) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch, 0); + var renderWatch = nv.utils.renderWatch(dispatch, 0); - function chart(selection) { - selection.each(function(data) { - renderWatch.reset(); - renderWatch.models(bars); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + function chart(selection) { + selection.each(function(data) { + renderWatch.reset(); + renderWatch.models(bars); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); - 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); + 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() { container.transition().duration(transitionDuration).call(chart) }; - chart.container = this; + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; - //set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); + //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]; + 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(); - } + // 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 = bars.xScale(); - y = bars.yScale(); + // Setup Scales + x = bars.xScale(); + y = bars.yScale(); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').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-interactive'); + 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-interactive'); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - 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)"); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer + //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); + } + bars .width(availableWidth) .height(availableHeight) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - bars - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); + .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.transition().call(bars); + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); + barsWrap.transition().call(bars); - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); + // 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') - .transition() - .call(xAxis); - } + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .transition() + .call(xAxis); + } - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); - g.select('.nv-y.nv-axis') - .transition() - .call(yAxis); - } + g.select('.nv-y.nv-axis') + .transition() + .call(yAxis); + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - interactiveLayer.dispatch.on('elementMousemove', function(e) { - bars.clearHighlights(); + interactiveLayer.dispatch.on('elementMousemove', function(e) { + bars.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - bars.highlightPoint(pointIndex,true); - var point = series.values[pointIndex]; - 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: chart.y()(point, pointIndex), - color: color(series,series.seriesIndex), - data: series.values[pointIndex] + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + bars.highlightPoint(pointIndex,true); + var point = series.values[pointIndex]; + 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: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex), + data: series.values[pointIndex] + }); }); - }); - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); - interactiveLayer.tooltip - .valueFormatter(function(d,i) { - return yAxis.tickFormat()(d); - }) - .data({ - value: xValue, - index: pointIndex, - series: allData - })(); + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + interactiveLayer.tooltip + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data({ + value: xValue, + index: pointIndex, + series: allData + })(); - interactiveLayer.renderGuideLine(pointXLocation); + interactiveLayer.renderGuideLine(pointXLocation); - }); + }); - interactiveLayer.dispatch.on("elementMouseout",function(e) { - dispatch.tooltipHide(); - bars.clearHighlights(); - }); + interactiveLayer.dispatch.on("elementMouseout",function(e) { + dispatch.tooltipHide(); + bars.clearHighlights(); + }); - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.nv-series').classed('disabled', false); - return d; - }); - } + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); - selection.transition().call(chart); - }); - - legend.dispatch.on('legendDblclick', function(d) { - //Double clicking should always enable current series, and disabled all others. - data.forEach(function(d) { - d.disabled = true; + selection.transition().call(chart); }); - d.disabled = false; - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - chart.update(); - }); - - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; + legend.dispatch.on('legendDblclick', function(d) { + //Double clicking should always enable current series, and disabled all others. + data.forEach(function(d) { + d.disabled = true; }); + d.disabled = false; - state.disabled = e.disabled; - } + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + chart.update(); + }); - chart.update(); + 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('historicalBarChart immediate'); - return chart; - } + renderWatch.renderEnd('historicalBarChart immediate'); + return chart; + } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - bars.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); - }); + bars.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); + }); - bars.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + bars.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - bars.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + bars.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.bars = bars; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; + // expose chart's sub-components + chart.dispatch = dispatch; + chart.bars = bars; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - 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=_;}}, + 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=_;}}, + 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=_;}}, - // 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(_); - legend.color(color); - bars.color(color); - }}, - duration: {get: function(){return transitionDuration;}, set: function(_){ - transitionDuration=_; - renderWatch.reset(transitionDuration); - yAxis.duration(transitionDuration); - xAxis.duration(transitionDuration); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (_ === true) { - chart.interactive(false); - } - }} - }); + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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(_); + legend.color(color); + bars.color(color); + }}, + duration: {get: function(){return transitionDuration;}, set: function(_){ + transitionDuration=_; + renderWatch.reset(transitionDuration); + yAxis.duration(transitionDuration); + xAxis.duration(transitionDuration); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + } + }} + }); - nv.utils.inheritOptions(chart, bars); - nv.utils.initOptions(chart); + nv.utils.inheritOptions(chart, bars); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; // ohlcChart is just a historical chart with ohlc bars and some tweaks -nv.models.ohlcBarChart = function() { - var chart = nv.models.historicalBarChart(nv.models.ohlcBar()); + nv.models.ohlcBarChart = function() { + var chart = nv.models.historicalBarChart(nv.models.ohlcBar()); - // special default tooltip since we show multiple values per x - chart.useInteractiveGuideline(true); - chart.interactiveLayer.tooltip.contentGenerator(function(data) { - // we assume only one series exists for this chart - var d = data.series[0].data; - // match line colors as defined in nv.d3.css - var color = d.open < d.close ? "2ca02c" : "d62728"; - return '' + - '<h3 style="color: #' + color + '">' + data.value + '</h3>' + - '<table>' + - '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + - '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + - '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + - '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + - '</table>'; - }); - return chart; -}; + // special default tooltip since we show multiple values per x + chart.useInteractiveGuideline(true); + chart.interactiveLayer.tooltip.contentGenerator(function(data) { + // we assume only one series exists for this chart + var d = data.series[0].data; + // match line colors as defined in nv.d3.css + var color = d.open < d.close ? "2ca02c" : "d62728"; + return '' + + '<h3 style="color: #' + color + '">' + data.value + '</h3>' + + '<table>' + + '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + + '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + + '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + + '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + + '</table>'; + }); + return chart; + }; // candlestickChart is just a historical chart with candlestick bars and some tweaks -nv.models.candlestickBarChart = function() { - var chart = nv.models.historicalBarChart(nv.models.candlestickBar()); + nv.models.candlestickBarChart = function() { + var chart = nv.models.historicalBarChart(nv.models.candlestickBar()); - // special default tooltip since we show multiple values per x - chart.useInteractiveGuideline(true); - chart.interactiveLayer.tooltip.contentGenerator(function(data) { - // we assume only one series exists for this chart - var d = data.series[0].data; - // match line colors as defined in nv.d3.css - var color = d.open < d.close ? "2ca02c" : "d62728"; - return '' + - '<h3 style="color: #' + color + '">' + data.value + '</h3>' + - '<table>' + - '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + - '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + - '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + - '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + - '</table>'; - }); - return chart; -}; -nv.models.legend = function() { - "use strict"; + // special default tooltip since we show multiple values per x + chart.useInteractiveGuideline(true); + chart.interactiveLayer.tooltip.contentGenerator(function(data) { + // we assume only one series exists for this chart + var d = data.series[0].data; + // match line colors as defined in nv.d3.css + var color = d.open < d.close ? "2ca02c" : "d62728"; + return '' + + '<h3 style="color: #' + color + '">' + data.value + '</h3>' + + '<table>' + + '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + + '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + + '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + + '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + + '</table>'; + }); + return chart; + }; + nv.models.legend = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 } - , keyFormatter = function (d) { return d } - , 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" - ; + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , keyFormatter = function (d) { return d } + , 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); + 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'); + // 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 + ')'); + if (rightAlign) + wrap.attr('transform', 'translate(' + (- margin.right) + ',' + margin.top + ')'); + else + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - var series = g.selectAll('.nv-series') - .data(function(d) { - if(vers != 'furious') return d; + var series = g.selectAll('.nv-series') + .data(function(d) { + if(vers != 'furious') return d; - return d.filter(function(n) { - return expanded ? true : !n.disengaged; + return d.filter(function(n) { + return expanded ? true : !n.disengaged; + }); }); - }); - var seriesEnter = series.enter().append('g').attr('class', 'nv-series'); - var seriesShape; + 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; - } + 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); + if(vers == 'classic') { + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('r', 5); - seriesShape = series.select('.nv-legend-symbol'); - } 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'); + seriesShape = series.select('.nv-legend-symbol'); + } 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)'); + 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'); + var seriesCheckbox = series.select('.nv-check-box'); - seriesCheckbox.each(function(d,i) { - d3.select(this).selectAll('path') - .attr('stroke', setTextColor(d,i)); - }); - } + 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'); + 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'); + 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}); + 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 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; - }); + 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 }) + }); + } - 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 }) + }); + } + }); - } - }) - .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(); - series.classed('nv-disabled', function(d) { return d.userDisabled }); - series.exit().remove(); + seriesText + .attr('fill', setTextColor) + .text(function (d) { return keyFormatter(getKey(d)) }); - seriesText - .attr('fill', setTextColor) - .text(function (d) { return keyFormatter(getKey(d)) }); + //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) { - //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 (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { + var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); + legendText = d3.select(this).select('text').text(trimmedKey + "..."); + d3.select(this).append("svg:title").text(keyFormatter(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); + } - var seriesWidths = []; - series.each(function(d,i) { - var legendText; - if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) { - var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength); - legendText = d3.select(this).select('text').text(trimmedKey + "..."); - d3.select(this).append("svg:title").text(keyFormatter(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); + }); - seriesWidths.push(nodeTextLength + padding); - }); + var seriesPerRow = 0; + var columnWidths = []; + legendWidth = 0; - 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 < 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--; - 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]; + } - 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; + }); } - 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]; + } - 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) + ')'; + }); - 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 + ')'); + } - //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); - height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); + } else { - } 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; - 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; + } - if (width < margin.left + margin.right + xpos + length) { - newxpos = xpos = 5; - ypos += versPadding; - } + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; - newxpos += length; - if (newxpos > maxwidth) maxwidth = newxpos; + if(legendWidth < xpos + maxwidth) { + legendWidth = xpos + maxwidth; + } + return 'translate(' + xpos + ',' + ypos + ')'; + }); - 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 + ')'); - //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; + } - 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); - 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); - // 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'); - 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); - 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); + }); - 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 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 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; + function setBGOpacity(d,i) { + if(expanded && vers == 'furious') { + return 1; + } else { + return !!d.disabled ? 0 : 1; + } } + + return chart; } - return chart; - } + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - 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=_;}}, + keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, + 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=_;}}, - 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=_;}}, - keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, - 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(_); + }} + }); - // 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); - nv.utils.initOptions(chart); + return chart; + }; - return chart; -}; + nv.models.line = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ -nv.models.line = function() { - "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + var scatter = nv.models.scatter() + ; - 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') + ; - 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 ; - 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 + //------------------------------------------------------------ - //============================================================ - // Private Variables - //------------------------------------------------------------ + var x0, y0 //used to store previous scales + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; - 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); - 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(); - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); + x0 = x0 || x; + y0 = y0 || y; - 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'); - // 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'); - gEnter.append('g').attr('class', 'nv-groups'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + scatter + .width(availableWidth) + .height(availableHeight); - scatter - .width(availableWidth) - .height(availableHeight); + var scatterWrap = wrap.select('.nv-scatterWrap'); + scatterWrap.call(scatter); - var scatterWrap = wrap.select('.nv-scatterWrap'); - scatterWrap.call(scatter); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); - 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); - 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() + ')' : ''); - 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); - 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.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}); - 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(); - 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]) + }); - 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] }); - 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.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))) }) + ); - 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; + } - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - }); - renderWatch.renderEnd('line immediate'); - return chart; - } + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // 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.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 = 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=_;}}, - 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); + }} + }); - // 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); - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); + return chart; + }; + nv.models.lineChart = function() { + "use strict"; - return chart; -}; -nv.models.lineChart = function() { - "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - //============================================================ - // 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() + , focus = nv.models.focus(nv.models.line()) + ; - var lines = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - , focus = nv.models.focus(nv.models.line()) - ; + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , marginTop = null + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , legendPosition = 'top' + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , x + , y + , focusEnable = false + , state = nv.utils.state() + , defaultState = null + , noData = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') + , duration = 250 + ; - var margin = {top: 30, right: 20, bottom: 50, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = true - , legendPosition = 'top' - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , useInteractiveGuideline = false - , x - , y - , focusEnable = false - , state = nv.utils.state() - , defaultState = null - , noData = null - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') - , duration = 250 - ; + // set options on sub-objects for this chart + xAxis.orient('bottom').tickPadding(7); + yAxis.orient(rightAlignYAxis ? 'right' : 'left'); - // set options on sub-objects for this chart - xAxis.orient('bottom').tickPadding(7); - yAxis.orient(rightAlignYAxis ? 'right' : 'left'); + lines.clipEdge(true).duration(0); - lines.clipEdge(true).duration(0); + tooltip.valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - 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); + }); - interactiveLayer.tooltip.valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); + //============================================================ + // Private Variables + //------------------------------------------------------------ - //============================================================ - // Private Variables - //------------------------------------------------------------ + var renderWatch = nv.utils.renderWatch(dispatch, duration); - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled; }) + 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]; - }); + 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); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(lines); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); - chart.update = function() { - if( duration === 0 ) { - container.call( chart ); - } else { - container.transition().duration(duration).call(chart); - } - }; - chart.container = this; + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + 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(); + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - // DEPRECATED set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled; }); + // 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]; + 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(); - } + // 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(); + } - /* Update `main' graph on brush update. */ - focus.dispatch.on("onBrush", function(extent) { - onBrush(extent); - }); + /* Update `main' graph on brush update. */ + focus.dispatch.on("onBrush", function(extent) { + onBrush(extent); + }); - // Setup Scales - x = lines.xScale(); - y = lines.yScale(); + // Setup Scales + x = lines.xScale(); + y = lines.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'); + // 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'); + 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 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-focusWrap'); + var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap'); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - if (legendPosition === 'bottom') { - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + availableHeight +')'); - } else if (legendPosition === 'top') { - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + if (legendPosition === 'bottom') { + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + availableHeight +')'); + } else if (legendPosition === 'top') { + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); } + } - 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)"); } - } - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + //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); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + g.select('.nv-focus .nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer + lines .width(availableWidth) .height(availableHeight) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled; })); - g.select('.nv-focus .nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled; })); - lines - .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 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(-availableHeight, 0); + } + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + } - // Setup Main (Focus) Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks(nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - } + //============================================================ + // Update Axes + //============================================================ + function updateXAxis() { + if(showXAxis) { + g.select('.nv-focus .nv-x.nv-axis') + .transition() + .duration(duration) + .call(xAxis) + ; + } + } - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - } + function updateYAxis() { + if(showYAxis) { + g.select('.nv-focus .nv-y.nv-axis') + .transition() + .duration(duration) + .call(yAxis) + ; + } + } - //============================================================ - // Update Axes - //============================================================ - function updateXAxis() { - if(showXAxis) { g.select('.nv-focus .nv-x.nv-axis') - .transition() - .duration(duration) - .call(xAxis) - ; - } - } + .attr('transform', 'translate(0,' + availableHeight + ')'); - 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,' + availableHeight + ')'); - - //============================================================ - // Update Focus - //============================================================ - if(!focusEnable) { - linesWrap.call(lines); - updateXAxis(); - updateYAxis(); - } else { - focus.width(availableWidth); - g.select('.nv-focusWrap') - .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') - .datum(data.filter(function(d) { return !d.disabled; })) - .call(focus); - var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); - if(extent !== null){ - onBrush(extent); + //============================================================ + // Update Focus + //============================================================ + if(!focusEnable) { + linesWrap.call(lines); + updateXAxis(); + updateYAxis(); + } else { + focus.width(availableWidth); + g.select('.nv-focusWrap') + .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') + .datum(data.filter(function(d) { return !d.disabled; })) + .call(focus); + var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); + if(extent !== null){ + onBrush(extent); + } } - } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // 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(); - }); + 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 ? (focus.brush.empty() ? focus.xScale().domain() : focus.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]; - }); + 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 ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain(); + var currentValues = series.values.filter(function(d,i) { + // Checks if the x point is between the extents, handling case where extent[0] is greater than extent[1] + // (e.g. x domain is manually set to reverse the x-axis) + if(extent[0] <= extent[1]) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + } else { + return lines.x()(d,i) >= extent[1] && lines.x()(d,i) <= extent[0]; + } + }); - 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 + pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x()); + var point = currentValues[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue !== null) { + lines.highlightPoint(i, 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; - } + //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); - }; + var defaultValueFormatter = function(d,i) { + return d == null ? "N/A" : yAxis.tickFormat()(d); + }; - interactiveLayer.tooltip - .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) - .data({ - value: chart.x()( singlePoint,pointIndex ), - index: pointIndex, - series: allData - })(); + interactiveLayer.tooltip + .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) + .data({ + value: chart.x()( singlePoint,pointIndex ), + index: pointIndex, + series: allData + })(); - interactiveLayer.renderGuideLine(pointXLocation); + interactiveLayer.renderGuideLine(pointXLocation); - }); + }); - interactiveLayer.dispatch.on('elementClick', function(e) { - var pointXLocation, allData = []; + 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 + 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); }); - lines.dispatch.elementClick(allData); - }); + interactiveLayer.dispatch.on("elementMouseout",function(e) { + lines.clearHighlights(); + }); - 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]; + }); - 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(); + }); - state.disabled = e.disabled; + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight / 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); } - chart.update(); + + function onBrush(extent) { + // 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(); + } }); - //============================================================ - // Functions - //------------------------------------------------------------ + renderWatch.renderEnd('lineChart immediate'); + return chart; + } - // Taken from crossfilter (http://square.github.com/crossfilter/) - function resizePath(d) { - var e = +(d == 'e'), - x = e ? 1 : -1, - y = availableHeight / 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 onBrush(extent) { - // 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); + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - // Update Main (Focus) Axes - updateXAxis(); - updateYAxis(); + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + if(!evt.series.disableTooltip){ + tooltip.data(evt).hidden(false); } }); - renderWatch.renderEnd('lineChart immediate'); - return chart; - } + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.focus = focus; + chart.xAxis = xAxis; + chart.x2Axis = focus.xAxis + chart.yAxis = yAxis; + chart.y2Axis = focus.yAxis + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + chart.state = state; + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - if(!evt.series.disableTooltip){ - tooltip.data(evt).hidden(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=_;}}, + 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=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + // Focus options, mostly passed onto focus model. + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, + focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}}, + focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}}, + brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + // options that require extra logic in the setter + focusMargin: {get: function(){return focus.margin}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.top; + } + focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; + focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; + focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; + }}, + 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); + focus.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + lines.color(color); + focus.color(color); + }}, + interpolate: {get: function(){return lines.interpolate();}, set: function(_){ + lines.interpolate(_); + focus.interpolate(_); + }}, + xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ + xAxis.tickFormat(_); + focus.xTickFormat(_); + }}, + yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ + yAxis.tickFormat(_); + focus.yTickFormat(_); + }}, + x: {get: function(){return lines.x();}, set: function(_){ + lines.x(_); + focus.x(_); + }}, + y: {get: function(){return lines.y();}, set: function(_){ + lines.y(_); + focus.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); + } + }} + }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); - // expose chart's sub-components - chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; - chart.focus = focus; - chart.xAxis = xAxis; - chart.x2Axis = focus.xAxis - chart.yAxis = yAxis; - chart.y2Axis = focus.yAxis - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - chart.state = state; - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + return 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=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - // Focus options, mostly passed onto focus model. - focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, - focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, - focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}}, - focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}}, - brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, + nv.models.lineWithFocusChart = function() { + return nv.models.lineChart() + .margin({ bottom: 30 }) + .focusEnable( true ); + }; + nv.models.linePlusBarChart = function() { + "use strict"; - // options that require extra logic in the setter - focusMargin: {get: function(){return focus.margin}, set: function(_){ - focus.margin.top = _.top !== undefined ? _.top : focus.margin.top; - focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; - focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; - focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; - }}, - 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); - focus.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - lines.color(color); - focus.color(color); - }}, - interpolate: {get: function(){return lines.interpolate();}, set: function(_){ - lines.interpolate(_); - focus.interpolate(_); - }}, - xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ - xAxis.tickFormat(_); - focus.xTickFormat(_); - }}, - yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ - yAxis.tickFormat(_); - focus.yTickFormat(_); - }}, - x: {get: function(){return lines.x();}, set: function(_){ - lines.x(_); - focus.x(_); - }}, - y: {get: function(){return lines.y();}, set: function(_){ - lines.y(_); - focus.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); - } - }} - }); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); + var lines = nv.models.line() + , lines2 = nv.models.line() + , bars = nv.models.historicalBar() + , bars2 = nv.models.historicalBar() + , xAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , y3Axis = nv.models.axis() + , y4Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + , tooltip = nv.models.tooltip() + ; - return chart; -}; + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , marginTop = null + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , width = null + , height = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , focusEnable = true + , focusShowAxisY = false + , focusShowAxisX = true + , focusHeight = 50 + , extent + , brushExtent = null + , x + , x2 + , y1 + , y2 + , y3 + , y4 + , noData = null + , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') + , transitionDuration = 0 + , state = nv.utils.state() + , defaultState = null + , legendLeftAxisHint = ' (left axis)' + , legendRightAxisHint = ' (right axis)' + , switchYAxisOrder = false + ; -nv.models.lineWithFocusChart = function() { - return nv.models.lineChart() - .margin({ bottom: 30 }) - .focusEnable( true ); -}; -nv.models.linePlusBarChart = function() { - "use strict"; + lines.clipEdge(true); + lines2.interactive(false); + // We don't want any points emitted for the focus chart's scatter graph. + lines2.pointActive(function(d) { return false }); + xAxis.orient('bottom').tickPadding(5); + y1Axis.orient('left'); + y2Axis.orient('right'); + x2Axis.orient('bottom').tickPadding(5); + y3Axis.orient('left'); + y4Axis.orient('right'); - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + tooltip.headerEnabled(true).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - var lines = nv.models.line() - , lines2 = nv.models.line() - , bars = nv.models.historicalBar() - , bars2 = nv.models.historicalBar() - , xAxis = nv.models.axis() - , x2Axis = nv.models.axis() - , y1Axis = nv.models.axis() - , y2Axis = nv.models.axis() - , y3Axis = nv.models.axis() - , y4Axis = nv.models.axis() - , legend = nv.models.legend() - , brush = d3.svg.brush() - , tooltip = nv.models.tooltip() - ; + //============================================================ + // Private Variables + //------------------------------------------------------------ - var margin = {top: 30, right: 30, bottom: 30, left: 60} - , margin2 = {top: 0, right: 30, bottom: 20, left: 60} - , width = null - , height = null - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.defaultColor() - , showLegend = true - , focusEnable = true - , focusShowAxisY = false - , focusShowAxisX = true - , focusHeight = 50 - , extent - , brushExtent = null - , x - , x2 - , y1 - , y2 - , y3 - , y4 - , noData = null - , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') - , transitionDuration = 0 - , state = nv.utils.state() - , defaultState = null - , legendLeftAxisHint = ' (left axis)' - , legendRightAxisHint = ' (right axis)' - , switchYAxisOrder = false - ; + var getBarsAxis = function() { + return switchYAxisOrder + ? { main: y2Axis, focus: y4Axis } + : { main: y1Axis, focus: y3Axis } + } - lines.clipEdge(true); - lines2.interactive(false); - // We don't want any points emitted for the focus chart's scatter graph. - lines2.pointActive(function(d) { return false }); - xAxis.orient('bottom').tickPadding(5); - y1Axis.orient('left'); - y2Axis.orient('right'); - x2Axis.orient('bottom').tickPadding(5); - y3Axis.orient('left'); - y4Axis.orient('right'); + var getLinesAxis = function() { + return switchYAxisOrder + ? { main: y1Axis, focus: y3Axis } + : { main: y2Axis, focus: y4Axis } + } - tooltip.headerEnabled(true).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; - //============================================================ - // Private Variables - //------------------------------------------------------------ + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; - var getBarsAxis = function() { - return switchYAxisOrder - ? { main: y2Axis, focus: y4Axis } - : { main: y1Axis, focus: y3Axis } - } - - var getLinesAxis = function() { - return switchYAxisOrder - ? { main: y1Axis, focus: y3Axis } - : { main: y2Axis, focus: y4Axis } - } - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; + var allDisabled = function(data) { + return data.every(function(series) { + return series.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) { + selection.each(function(data) { + var container = d3.select(this), + that = 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; - var allDisabled = function(data) { - return data.every(function(series) { - return series.disabled; - }); - } + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = 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; + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; - chart.container = this; + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); + 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]; + } + } - // 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 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(); } - } - // 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 + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 - // Setup Scales - var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); - var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + if (dataBars.length && !switchYAxisOrder) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } - if (dataBars.length && !switchYAxisOrder) { - x = bars.xScale(); - } else { - x = lines.xScale(); - } + x2 = x2Axis.scale(); - x2 = x2Axis.scale(); + // select the scales and series based on the position of the yAxis + y1 = switchYAxisOrder ? lines.yScale() : bars.yScale(); + y2 = switchYAxisOrder ? bars.yScale() : lines.yScale(); + y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale(); + y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale(); - // select the scales and series based on the position of the yAxis - y1 = switchYAxisOrder ? lines.yScale() : bars.yScale(); - y2 = switchYAxisOrder ? bars.yScale() : lines.yScale(); - y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale(); - y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale(); + var series1 = data + .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); - var series1 = data - .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) }) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i) } - }) - }); + var series2 = data + .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); - var series2 = data - .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) }) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i) } - }) - }); + x.range([0, availableWidth]); - x.range([0, availableWidth]); + x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); - x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) - .range([0, availableWidth]); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); - var g = wrap.select('g'); + gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); + // this is the main chart + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); + focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); + focusEnter.append('g').attr('class', 'nv-barsWrap'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); - // this is the main chart - var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); - focusEnter.append('g').attr('class', 'nv-x nv-axis'); - focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); - focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); - focusEnter.append('g').attr('class', 'nv-barsWrap'); - focusEnter.append('g').attr('class', 'nv-linesWrap'); + // context chart is where you can focus in + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); + contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); + contextEnter.append('g').attr('class', 'nv-barsWrap'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); - // context chart is where you can focus in - var contextEnter = gEnter.append('g').attr('class', 'nv-context'); - contextEnter.append('g').attr('class', 'nv-x nv-axis'); - contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); - contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); - contextEnter.append('g').attr('class', 'nv-barsWrap'); - contextEnter.append('g').attr('class', 'nv-linesWrap'); - contextEnter.append('g').attr('class', 'nv-brushBackground'); - contextEnter.append('g').attr('class', 'nv-x nv-brush'); + //============================================================ + // Legend + //------------------------------------------------------------ - //============================================================ - // Legend - //------------------------------------------------------------ + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; + var legendXPosition = legend.align() ? legendWidth : 0; - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; - var legendXPosition = legend.align() ? legendWidth : 0; + legend.width(legendWidth); - legend.width(legendWidth); + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + if(switchYAxisOrder) { + series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint); + } else { + series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint); + } + return series; + })) + .call(legend); - g.select('.nv-legendWrap') - .datum(data.map(function(series) { - series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; - if(switchYAxisOrder) { - series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint); - } else { - series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint); - } - return series; - })) - .call(legend); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? + availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; + } - if (legend.height() > margin.top) { - margin.top = legend.height(); - // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? - availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); } - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + //============================================================ + // Context chart (focus chart) components + //------------------------------------------------------------ - //============================================================ - // Context chart (focus chart) components - //------------------------------------------------------------ + // hide or show the focus context chart + g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none'); - // hide or show the focus context chart - g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none'); + bars2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function (d, i) { + return d.color || color(d, i); + }).filter(function (d, i) { + return !data[i].disabled && data[i].bar + })); + lines2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function (d, i) { + return d.color || color(d, i); + }).filter(function (d, i) { + return !data[i].disabled && !data[i].bar + })); - bars2 - .width(availableWidth) - .height(availableHeight2) - .color(data.map(function (d, i) { - return d.color || color(d, i); - }).filter(function (d, i) { - return !data[i].disabled && data[i].bar - })); - lines2 - .width(availableWidth) - .height(availableHeight2) - .color(data.map(function (d, i) { - return d.color || color(d, i); - }).filter(function (d, i) { - return !data[i].disabled && !data[i].bar - })); + var bars2Wrap = g.select('.nv-context .nv-barsWrap') + .datum(dataBars.length ? dataBars : [ + {values: []} + ]); + var lines2Wrap = g.select('.nv-context .nv-linesWrap') + .datum(allDisabled(dataLines) ? + [{values: []}] : + dataLines.filter(function(dataLine) { + return !dataLine.disabled; + })); - var bars2Wrap = g.select('.nv-context .nv-barsWrap') - .datum(dataBars.length ? dataBars : [ - {values: []} - ]); - var lines2Wrap = g.select('.nv-context .nv-linesWrap') - .datum(allDisabled(dataLines) ? - [{values: []}] : - dataLines.filter(function(dataLine) { - return !dataLine.disabled; - })); + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')'); - g.select('.nv-context') - .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')'); + bars2Wrap.transition().call(bars2); + lines2Wrap.transition().call(lines2); - bars2Wrap.transition().call(bars2); - lines2Wrap.transition().call(lines2); + // context (focus chart) axis controls + if (focusShowAxisX) { + x2Axis + ._ticks( nv.utils.calcTicksX(availableWidth / 100, data)) + .tickSize(-availableHeight2, 0); + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y3.range()[0] + ')'); + g.select('.nv-context .nv-x.nv-axis').transition() + .call(x2Axis); + } - // context (focus chart) axis controls - if (focusShowAxisX) { - x2Axis - ._ticks( nv.utils.calcTicksX(availableWidth / 100, data)) - .tickSize(-availableHeight2, 0); - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y3.range()[0] + ')'); - g.select('.nv-context .nv-x.nv-axis').transition() - .call(x2Axis); - } + if (focusShowAxisY) { + y3Axis + .scale(y3) + ._ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + y4Axis + .scale(y4) + ._ticks( availableHeight2 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none - if (focusShowAxisY) { - y3Axis - .scale(y3) - ._ticks( availableHeight2 / 36 ) - .tickSize( -availableWidth, 0); - y4Axis - .scale(y4) - ._ticks( availableHeight2 / 36 ) - .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + g.select('.nv-context .nv-y3.nv-axis') + .style('opacity', dataBars.length ? 1 : 0) + .attr('transform', 'translate(0,' + x2.range()[0] + ')'); + g.select('.nv-context .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x2.range()[1] + ',0)'); - g.select('.nv-context .nv-y3.nv-axis') - .style('opacity', dataBars.length ? 1 : 0) - .attr('transform', 'translate(0,' + x2.range()[0] + ')'); - g.select('.nv-context .nv-y2.nv-axis') - .style('opacity', dataLines.length ? 1 : 0) - .attr('transform', 'translate(' + x2.range()[1] + ',0)'); + g.select('.nv-context .nv-y1.nv-axis').transition() + .call(y3Axis); + g.select('.nv-context .nv-y2.nv-axis').transition() + .call(y4Axis); + } - g.select('.nv-context .nv-y1.nv-axis').transition() - .call(y3Axis); - g.select('.nv-context .nv-y2.nv-axis').transition() - .call(y4Axis); - } + // Setup Brush + brush.x(x2).on('brush', onBrush); - // Setup Brush - brush.x(x2).on('brush', onBrush); + if (brushExtent) brush.extent(brushExtent); - if (brushExtent) brush.extent(brushExtent); + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]); - var brushBG = g.select('.nv-brushBackground').selectAll('g') - .data([brushExtent || brush.extent()]); + var brushBGenter = brushBG.enter() + .append('g'); - 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', '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); - 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') + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') //.attr('y', -5) - .attr('height', availableHeight2); - gBrush.selectAll('.resize').append('path').attr('d', resizePath); + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // 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(); - }); + 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(); - }); + // 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(); + }); - //============================================================ - // Functions - //------------------------------------------------------------ + //============================================================ + // 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' + (.5 * x) + ',' + y - + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) - + 'V' + (2 * y - 6) - + 'A6,6 0 0 ' + e + ' ' + (.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); - } + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.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]) - x2.range()[0], - rightWidth = x2.range()[1] - x2(d[1]); - d3.select(this).select('.left') - .attr('width', leftWidth < 0 ? 0 : leftWidth); + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x2.range()[0], + rightWidth = x2.range()[1] - 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); - }); - } + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } - function onBrush() { - brushExtent = brush.empty() ? null : brush.extent(); - extent = brush.empty() ? x2.domain() : brush.extent(); - dispatch.brush({extent: extent, brush: brush}); - updateBrushBG(); + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + dispatch.brush({extent: extent, brush: brush}); + updateBrushBG(); - // Prepare Main (Focus) Bars and Lines - bars - .width(availableWidth) - .height(availableHeight1) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + // Prepare Main (Focus) Bars and Lines + bars + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); - 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 && !data[i].bar })); + 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 && !data[i].bar })); - var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') - .datum(!dataBars.length ? [{values:[]}] : - dataBars - .map(function(d,i) { - return { - key: d.key, - values: d.values.filter(function(d,i) { - return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; - }) - } - }) - ); + var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') + .datum(!dataBars.length ? [{values:[]}] : + dataBars + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; + }) + } + }) + ); - var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') - .datum(allDisabled(dataLines) ? [{values:[]}] : - dataLines - .filter(function(dataLine) { return !dataLine.disabled; }) - .map(function(d,i) { - return { - area: d.area, - fillOpacity: d.fillOpacity, - strokeWidth: d.strokeWidth, - key: d.key, - values: d.values.filter(function(d,i) { - return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; - }) - } - }) - ); + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(allDisabled(dataLines) ? [{values:[]}] : + dataLines + .filter(function(dataLine) { return !dataLine.disabled; }) + .map(function(d,i) { + return { + area: d.area, + fillOpacity: d.fillOpacity, + strokeWidth: d.strokeWidth, + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); - // Update Main (Focus) X Axis - if (dataBars.length && !switchYAxisOrder) { - x = bars.xScale(); - } else { - x = lines.xScale(); - } + // Update Main (Focus) X Axis + if (dataBars.length && !switchYAxisOrder) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight1, 0); + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight1, 0); - xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); + xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); - g.select('.nv-x.nv-axis').transition().duration(transitionDuration) - .call(xAxis); + g.select('.nv-x.nv-axis').transition().duration(transitionDuration) + .call(xAxis); - // Update Main (Focus) Bars and Lines - focusBarsWrap.transition().duration(transitionDuration).call(bars); - focusLinesWrap.transition().duration(transitionDuration).call(lines); + // Update Main (Focus) Bars and Lines + focusBarsWrap.transition().duration(transitionDuration).call(bars); + focusLinesWrap.transition().duration(transitionDuration).call(lines); - // Setup and Update Main (Focus) Y Axes - g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + // Setup and Update Main (Focus) Y Axes + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); - y1Axis - .scale(y1) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) - .tickSize(-availableWidth, 0); - y2Axis - .scale(y2) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ); + y1Axis + .scale(y1) + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) + .tickSize(-availableWidth, 0); + y2Axis + .scale(y2) + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ); - // Show the y2 rules only if y1 has none - if(!switchYAxisOrder) { - y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0); - } else { - y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0); - } + // Show the y2 rules only if y1 has none + if(!switchYAxisOrder) { + y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0); + } else { + y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0); + } - // Calculate opacity of the axis - var barsOpacity = dataBars.length ? 1 : 0; - var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0; + // Calculate opacity of the axis + var barsOpacity = dataBars.length ? 1 : 0; + var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0; - var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity; - var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity; + var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity; + var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity; - g.select('.nv-focus .nv-y1.nv-axis') - .style('opacity', y1Opacity); - g.select('.nv-focus .nv-y2.nv-axis') - .style('opacity', y2Opacity) - .attr('transform', 'translate(' + x.range()[1] + ',0)'); + g.select('.nv-focus .nv-y1.nv-axis') + .style('opacity', y1Opacity); + g.select('.nv-focus .nv-y2.nv-axis') + .style('opacity', y2Opacity) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); - g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) - .call(y1Axis); - g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) - .call(y2Axis); - } + g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) + .call(y1Axis); + g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) + .call(y2Axis); + } - onBrush(); + onBrush(); - }); + }); - return chart; - } + return chart; + } - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip - .duration(100) - .valueFormatter(function(d, i) { - return getLinesAxis().main.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - }); + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip + .duration(100) + .valueFormatter(function(d, i) { + return getLinesAxis().main.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + }); - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); - bars.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.value = chart.x()(evt.data); - evt['series'] = { - value: chart.y()(evt.data), - color: evt.color - }; - tooltip - .duration(0) - .valueFormatter(function(d, i) { - return getBarsAxis().main.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - }); + bars.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.value = chart.x()(evt.data); + evt['series'] = { + value: chart.y()(evt.data), + color: evt.color + }; + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return getBarsAxis().main.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + }); - bars.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + bars.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - bars.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + bars.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ + //============================================================ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.legend = legend; - chart.lines = lines; - chart.lines2 = lines2; - chart.bars = bars; - chart.bars2 = bars2; - chart.xAxis = xAxis; - chart.x2Axis = x2Axis; - chart.y1Axis = y1Axis; - chart.y2Axis = y2Axis; - chart.y3Axis = y3Axis; - chart.y4Axis = y4Axis; - chart.tooltip = tooltip; + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.bars = bars; + chart.bars2 = bars2; + chart.xAxis = xAxis; + chart.x2Axis = x2Axis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + chart.y3Axis = y3Axis; + chart.y4Axis = y4Axis; + chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, - focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}}, - focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, - focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, - legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}}, - legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, + 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=_;}}, + brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}}, + focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, + focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, + legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}}, + legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, - // 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; - }}, - 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; - }}, - duration: {get: function(){return transitionDuration;}, set: function(_){ - transitionDuration = _; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - x: {get: function(){return getX;}, set: function(_){ - getX = _; - lines.x(_); - lines2.x(_); - bars.x(_); - bars2.x(_); - }}, - y: {get: function(){return getY;}, set: function(_){ - getY = _; - lines.y(_); - lines2.y(_); - bars.y(_); - bars2.y(_); - }}, - switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){ - // Switch the tick format for the yAxis - if(switchYAxisOrder !== _) { - var y1 = y1Axis; - y1Axis = y2Axis; - y2Axis = y1; + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.top; + } + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + 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; + }}, + duration: {get: function(){return transitionDuration;}, set: function(_){ + transitionDuration = _; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + x: {get: function(){return getX;}, set: function(_){ + getX = _; + lines.x(_); + lines2.x(_); + bars.x(_); + bars2.x(_); + }}, + y: {get: function(){return getY;}, set: function(_){ + getY = _; + lines.y(_); + lines2.y(_); + bars.y(_); + bars2.y(_); + }}, + switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){ + // Switch the tick format for the yAxis + if(switchYAxisOrder !== _) { + var y1 = y1Axis; + y1Axis = y2Axis; + y2Axis = y1; - var y3 = y3Axis; - y3Axis = y4Axis; - y4Axis = y3; - } - switchYAxisOrder=_; + var y3 = y3Axis; + y3Axis = y4Axis; + y4Axis = y3; + } + switchYAxisOrder=_; - y1Axis.orient('left'); - y2Axis.orient('right'); - y3Axis.orient('left'); - y4Axis.orient('right'); - }} - }); + y1Axis.orient('left'); + y2Axis.orient('right'); + y3Axis.orient('left'); + y4Axis.orient('right'); + }} + }); - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; -nv.models.multiBar = function() { - "use strict"; + nv.models.multiBar = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 - , fillOpacity = 0.75 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; + 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 + , fillOpacity = 0.75 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var x0, y0 //used to store previous scales - , renderWatch = nv.utils.renderWatch(dispatch, duration) - ; + var x0, y0 //used to store previous scales + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; - var last_datalength = 0; + 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; + 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; - }; + 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) { + 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); + 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.length > 0) { - 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; + 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; + }); }); - } - // 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); + // HACK for negative value stacking + if (stacked && data.length > 0) { + 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; + } + } - 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]); + // 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 } + }) + }); - // 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]); + x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableWidth], groupSpacing); - 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]); + 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]); - x0 = x0 || x; - y0 = y0 || y; + // 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]); - // 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'); + 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]); - gEnter.append('g').attr('class', 'nv-groups'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + x0 = x0 || x; + y0 = y0 || y; - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + // 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'); - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + gEnter.append('g').attr('class', 'nv-groups'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - 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); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - 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', fillOpacity); + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - var bars = groups.selectAll('rect.nv-bar') - .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); - bars.exit().remove(); + 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 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 ) + 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('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") + .remove(); + if (exitTransition.delay) + exitTransition.delay(function(d,i) { + var delay = i * (duration / (last_datalength + 1)) - i; + return delay; }); - }) - .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)'; }) + 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', fillOpacity); - 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 bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); + bars.exit().remove(); - var barSelection = - bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration)) - .delay(function(d,i) { - return i * duration / data[0].values.length; + 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(); }); - 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); + 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 (y(0) - y(getY(d,i)) < -1){ - yVal = y(0) - 1; + if (getY(d,i) < 0){ + yVal = y(0); } else { - yVal = y(getY(d, i)) || 0; + 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 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; } - } - 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); + }) + .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; - } - }); - } - 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) : + }) + .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; - }); - } + 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(); + //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; - } + // 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'); + renderWatch.renderEnd('multibar immediate'); - return chart; - } + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; + chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, + 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=_;}}, + fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, - // 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; - }} - }); + // 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); + nv.utils.initOptions(chart); - return chart; -}; -nv.models.multiBarChart = function() { - "use strict"; + return chart; + }; + nv.models.multiBarChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 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 - ; + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , marginTop = null + , 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 + 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')) - ; + 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); - }); + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - controls.updateState(false); + interactiveLayer.tooltip + .valueFormatter(function(d, i) { + return d == null ? "N/A" : yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - //============================================================ - // Private Variables - //------------------------------------------------------------ + interactiveLayer.tooltip + .valueFormatter(function (d, i) { + return d == null ? "N/A" : yAxis.tickFormat()(d, i); + }) + .headerFormatter(function (d, i) { + return xAxis.tickFormat()(d, i); + }); - var renderWatch = nv.utils.renderWatch(dispatch); - var stacked = false; + interactiveLayer.tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - stacked: stacked - }; - } - }; + controls.updateState(false); - 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]; - }); - } - }; + //============================================================ + // Private Variables + //------------------------------------------------------------ - function chart(selection) { - renderWatch.reset(); - renderWatch.models(multibar); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + var renderWatch = nv.utils.renderWatch(dispatch); + var stacked = false; - 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]; - } + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + stacked: stacked + }; } + }; - // 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(); + 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]; + }); } + }; - // Setup Scales - x = multibar.xScale(); - y = multibar.yScale(); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(multibar); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); - // 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'); + 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); - 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'); + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition() + .duration(duration) + .call(chart); + }; + chart.container = this; - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth - controlWidth()); + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - g.select('.nv-legendWrap') - .datum(data) - .call(legend); + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + 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]; + } } - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); - } + // 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(); + } - // 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() } - ]; + // Setup Scales + x = multibar.xScale(); + y = multibar.yScale(); - controls.width(controlWidth()).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } + // 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'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + 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'); - // 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 })); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth - controlWidth()); + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } - barsWrap.call(multibar); + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); + // 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() } + ]; - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .call(xAxis); + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } - var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - xTicks - .selectAll('line, text') - .style('opacity', 1) + // 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 })); - 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 barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); - 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); - }); - } + barsWrap.call(multibar); - if (wrapLabels) { - g.selectAll('.tick text') - .call(nv.utils.wrapTicks, chart.xAxis.rangeBand()) - } + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); - if (reduceXTicks) - xTicks - .filter(function(d,i) { - return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; - }) - .selectAll('text, line') - .style('opacity', 0); + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .call(xAxis); - if(rotateLabels) + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + xTicks - .selectAll('.tick text') - .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') - .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); + .selectAll('line, text') + .style('opacity', 1) - g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') - .style('opacity', 1); - } + if (staggerLabels) { + var getTranslate = function(x,y) { + return "translate(" + x + "," + y + ")"; + }; - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + 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)); + }); - g.select('.nv-y.nv-axis') - .call(yAxis); - } + 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); + }); + } - //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); - } + if (wrapLabels) { + g.selectAll('.tick text') + .call(nv.utils.wrapTicks, chart.xAxis.rangeBand()) + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); + if(rotateLabels) + xTicks + .selectAll('.tick text') + .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') + .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); - 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; + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); } - state.stacked = multibar.stacked(); - dispatch.stateChange(state); - chart.update(); - }); + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); - // 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; + g.select('.nv-y.nv-axis') + .call(yAxis); } - if (typeof e.stacked !== 'undefined') { - multibar.stacked(e.stacked); - state.stacked = e.stacked; - stacked = e.stacked; + + //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); } - chart.update(); - }); - if (useInteractiveGuideline) { - interactiveLayer.dispatch.on('elementMousemove', function(e) { - if (e.pointXValue == undefined) return; + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - 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) + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); - var point = series.values[pointIndex]; - if (point === undefined) return; + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; - 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] - }); - }); + switch (d.key) { + case 'Grouped': + case controlLabels.grouped: + multibar.stacked(false); + break; + case 'Stacked': + case controlLabels.stacked: + multibar.stacked(true); + break; + } - interactiveLayer.tooltip - .data({ - value: xValue, - index: pointIndex, - series: allData - })(); - - interactiveLayer.renderGuideLine(pointXLocation); + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + chart.update(); }); - interactiveLayer.dispatch.on("elementMouseout",function(e) { - interactiveLayer.tooltip.hidden(true); + // 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(); }); - } - 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); - }); + if (useInteractiveGuideline) { + interactiveLayer.dispatch.on('elementMousemove', function(e) { + if (e.pointXValue == undefined) return; - multibar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); - } - }); + 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) - renderWatch.renderEnd('multibarchart immediate'); - return chart; - } + var point = series.values[pointIndex]; + if (point === undefined) return; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + 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] + }); + }); - // 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; + interactiveLayer.tooltip + .data({ + value: xValue, + index: pointIndex, + series: allData + })(); - chart.options = nv.utils.optionsFunc.bind(chart); + interactiveLayer.renderGuideLine(pointXLocation); + }); - 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=!!_;}}, + 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); + }); - // 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();}) - }} - }); + multibar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - nv.utils.inheritOptions(chart, multibar); - nv.utils.initOptions(chart); + multibar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); + } + }); - return chart; -}; + renderWatch.renderEnd('multibarchart immediate'); + return chart; + } -nv.models.multiBarHorizontal = function() { - "use strict"; + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + // 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; - 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 = null - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getYerr = function(d) { return d.yErr } - , 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() - , 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 - , stacked = false - , showValues = false - , showBarLabels = false - , valuePadding = 60 - , groupSpacing = 0.1 - , fillOpacity = 0.75 - , valueFormat = d3.format(',.2f') - , delay = 1200 - , xDomain - , yDomain - , xRange - , yRange - , duration = 250 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; + chart.options = nv.utils.optionsFunc.bind(chart); - //============================================================ - // Private Variables - //------------------------------------------------------------ + 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=!!_;}}, - var x0, y0; //used to store previous scales - var renderWatch = nv.utils.renderWatch(dispatch, duration); + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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();}) + }} + }); - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; + nv.utils.inheritOptions(chart, multibar); + nv.utils.initOptions(chart); - container = d3.select(this); - nv.utils.initSVG(container); + return chart; + }; - if (stacked) - data = d3.layout.stack() - .offset('zero') - .values(function(d){ return d.values }) - .y(getY) - (data); + nv.models.multiBarHorizontal = function() { + "use strict"; - //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; - }); - }); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - // HACK for negative value stacking - if (stacked) - data[0].values.map(function(d,i) { - var posBase = 0, negBase = 0; - data.map(function(d) { - var f = d.values[i] - f.size = Math.abs(f.y); - if (f.y<0) { - f.y1 = negBase - f.size; - negBase = negBase - f.size; - } else - { - f.y1 = posBase; - posBase = posBase + f.size; - } - }); - }); + 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 = null + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getYerr = function(d) { return d.yErr } + , 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() + , 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 + , stacked = false + , showValues = false + , showBarLabels = false + , valuePadding = 60 + , groupSpacing = 0.1 + , fillOpacity = 0.75 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , xRange + , yRange + , duration = 250 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; - // 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, y1: d.y1 } - }) - }); + //============================================================ + // Private Variables + //------------------------------------------------------------ - x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands(xRange || [0, availableHeight], groupSpacing); + var x0, y0; //used to store previous scales + var renderWatch = nv.utils.renderWatch(dispatch, duration); - y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY))) + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; - if (showValues && !stacked) - y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); - else - y.range(yRange || [0, availableWidth]); + container = d3.select(this); + nv.utils.initSVG(container); - x0 = x0 || x; - y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + //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; + }); + }); - gEnter.append('g').attr('class', 'nv-groups'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase - f.size; + negBase = negBase - f.size; + } else + { + f.y1 = posBase; + posBase = posBase + f.size; + } + }); + }); - 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); - groups.exit().watchTransition(renderWatch, 'multibarhorizontal: 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 }) - .style('fill', function(d,i){ return color(d, i) }) - .style('stroke', function(d,i){ return color(d, i) }); - groups.watchTransition(renderWatch, 'multibarhorizontal: groups') - .style('stroke-opacity', 1) - .style('fill-opacity', fillOpacity); + // 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, y1: d.y1 } + }) + }); - var bars = groups.selectAll('g.nv-bar') - .data(function(d) { return d.values }); - bars.exit().remove(); + x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableHeight], groupSpacing); - var barsEnter = bars.enter().append('g') - .attr('transform', function(d,i,j) { - return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' - }); + y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY))) - barsEnter.append('rect') - .attr('width', 0) - .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + if (showValues && !stacked) + y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range(yRange || [0, availableWidth]); - bars - .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('mouseout', function(d,i) { - 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(); - }); + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); - if (getYerr(data[0],0)) { - barsEnter.append('polyline'); + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - bars.select('polyline') - .attr('fill', 'none') - .attr('points', function(d,i) { - var xerr = getYerr(d,i) - , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); - xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; - xerr = xerr.map(function(e) { return y(e) - y(0); }); - var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]]; - return a.map(function (path) { return path.join(',') }).join(' '); - }) - .attr('transform', function(d,i) { - var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); - return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')' - }); - } + gEnter.append('g').attr('class', 'nv-groups'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - barsEnter.append('text'); + 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); + groups.exit().watchTransition(renderWatch, 'multibarhorizontal: 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 }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + groups.watchTransition(renderWatch, 'multibarhorizontal: groups') + .style('stroke-opacity', 1) + .style('fill-opacity', fillOpacity); - if (showValues && !stacked) { - bars.select('text') - .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) - .attr('y', x.rangeBand() / (data.length * 2)) - .attr('dy', '.32em') - .text(function(d,i) { - var t = valueFormat(getY(d,i)) - , yerr = getYerr(d,i); - if (yerr === undefined) - return t; - if (!yerr.length) - return t + '±' + valueFormat(Math.abs(yerr)); - return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); + 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(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' }); - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .select('text') - .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) - } else { - bars.selectAll('text').text(''); - } - if (showBarLabels && !stacked) { - barsEnter.append('text').classed('nv-bar-label',true); - bars.select('text.nv-bar-label') - .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' }) - .attr('y', x.rangeBand() / (data.length * 2)) - .attr('dy', '.32em') - .text(function(d,i) { return getX(d,i) }); - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .select('text.nv-bar-label') - .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 }); - } - else { - bars.selectAll('text.nv-bar-label').text(''); - } + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - - 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(); }); - } - - if (stacked) - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .attr('transform', function(d,i) { - return 'translate(' + y(d.y1) + ',' + x(getX(d,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") + }); }) - .select('rect') - .attr('width', function(d,i) { - return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0 + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); }) - .attr('height', x.rangeBand() ); - else - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .attr('transform', function(d,i) { - //TODO: stacked must be all positive or all negative, not both? - return 'translate(' + - (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) - + ',' + - (d.series * x.rangeBand() / data.length - + - x(getX(d,i)) ) - + ')' + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); }) - .select('rect') - .attr('height', x.rangeBand() / data.length ) - .attr('width', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0 + .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(); }); - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); + if (getYerr(data[0],0)) { + barsEnter.append('polyline'); - }); + bars.select('polyline') + .attr('fill', 'none') + .attr('points', function(d,i) { + var xerr = getYerr(d,i) + , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); + xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; + xerr = xerr.map(function(e) { return y(e) - y(0); }); + var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]]; + return a.map(function (path) { return path.join(',') }).join(' '); + }) + .attr('transform', function(d,i) { + var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); + return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')' + }); + } - renderWatch.renderEnd('multibarHorizontal immediate'); - return chart; - } + barsEnter.append('text'); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + if (showValues && !stacked) { + bars.select('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { + var t = valueFormat(getY(d,i)) + , yerr = getYerr(d,i); + if (yerr === undefined) + return t; + if (!yerr.length) + return t + '±' + valueFormat(Math.abs(yerr)); + return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); + }); + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + bars.selectAll('text').text(''); + } - chart.dispatch = dispatch; + if (showBarLabels && !stacked) { + barsEnter.append('text').classed('nv-bar-label',true); + bars.select('text.nv-bar-label') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { return getX(d,i) }); + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .select('text.nv-bar-label') + .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 }); + } + else { + bars.selectAll('text.nv-bar-label').text(''); + } - chart.options = nv.utils.optionsFunc.bind(chart); + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - 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=_;}}, - yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, - 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=_;}}, - showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, - // this shows the group name, seems pointless? - //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, - disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, - valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, - groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, - fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, + 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(); }); + } - // 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; - }} - }); + if (stacked) + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .attr('transform', function(d,i) { + return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0 + }) + .attr('height', x.rangeBand() ); + else + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0 + }); - nv.utils.initOptions(chart); + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); - return chart; -}; + }); -nv.models.multiBarHorizontalChart = function() { - "use strict"; + renderWatch.renderEnd('multibarHorizontal immediate'); + return chart; + } - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - var multibar = nv.models.multiBarHorizontal() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend().height(30) - , controls = nv.models.legend().height(30) - , tooltip = nv.models.tooltip() - ; + chart.dispatch = dispatch; - 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 - , stacked = false - , 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 - ; + chart.options = nv.utils.optionsFunc.bind(chart); - state.stacked = false; // DEPRECATED Maintained for backward compatibility + 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=_;}}, + yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, + 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=_;}}, + showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, + // this shows the group name, seems pointless? + //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, + disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, + valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, + groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}}, - multibar.stacked(stacked); - - xAxis - .orient('left') - .tickPadding(5) - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient('bottom') - .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); + // 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; + }} }); - controls.updateState(false); + nv.utils.initOptions(chart); - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - stacked: stacked - }; - } + return chart; }; - 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]; - }); - } - }; + nv.models.multiBarHorizontalChart = function() { + "use strict"; - var renderWatch = nv.utils.renderWatch(dispatch, duration); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - function chart(selection) { - renderWatch.reset(); - renderWatch.models(multibar); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + var multibar = nv.models.multiBarHorizontal() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend().height(30) + , controls = nv.models.legend().height(30) + , tooltip = nv.models.tooltip() + ; - 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); + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , marginTop = null + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , controlLabels = {} + , showLegend = true + , showXAxis = true + , showYAxis = true + , stacked = false + , 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 + ; - chart.update = function() { container.transition().duration(duration).call(chart) }; - chart.container = this; + state.stacked = false; // DEPRECATED Maintained for backward compatibility - stacked = multibar.stacked(); + multibar.stacked(stacked); - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); + xAxis + .orient('left') + .tickPadding(5) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('bottom') + .tickFormat(d3.format(',.1f')) + ; - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); - 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]; - } + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + stacked: stacked + }; } + }; - // 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(); + 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]; + }); } + }; - // Setup Scales - x = multibar.xScale(); - y = multibar.yScale().clamp(true); + var renderWatch = nv.utils.renderWatch(dispatch, duration); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); - var g = wrap.select('g'); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(multibar); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); - 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'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - legend.width(availableWidth - controlWidth()); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if (legend.height() > margin.top) { - margin.top = legend.height(); + 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); - } - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); - } + chart.update = function() { container.transition().duration(duration).call(chart) }; + chart.container = this; - // 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() } - ]; + stacked = multibar.stacked(); - controls.width(controlWidth()).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); - // 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 })); + 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]; + } + } - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); + // 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(); + } - barsWrap.transition().call(multibar); + // Setup Scales + x = multibar.xScale(); + y = multibar.yScale().clamp(true); - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksY(availableHeight/24, data) ) - .tickSize(-availableWidth, 0); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); + var g = wrap.select('g'); - g.select('.nv-x.nv-axis').call(xAxis); + 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'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); - var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + legend.width(availableWidth - controlWidth()); - xTicks - .selectAll('line, text'); - } + g.select('.nv-legendWrap') + .datum(data) + .call(legend); - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight, 0); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } - g.select('.nv-y.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - g.select('.nv-y.nv-axis').call(yAxis); - } + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } - // Zero line - g.select(".nv-zeroLine line") - .attr("x1", y(0)) - .attr("x2", y(0)) - .attr("y1", 0) - .attr("y2", -availableHeight) - ; + // 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() } + ]; - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; + // 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 })); - switch (d.key) { - case 'Grouped': - case controlLabels.grouped: - multibar.stacked(false); - break; - case 'Stacked': - case controlLabels.stacked: - multibar.stacked(true); - break; - } + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); - state.stacked = multibar.stacked(); - dispatch.stateChange(state); - stacked = multibar.stacked(); + barsWrap.transition().call(multibar); - chart.update(); - }); + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksY(availableHeight/24, data) ) + .tickSize(-availableWidth, 0); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { + g.select('.nv-x.nv-axis').call(xAxis); - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - state.disabled = e.disabled; + xTicks + .selectAll('line, text'); } - if (typeof e.stacked !== 'undefined') { - multibar.stacked(e.stacked); - state.stacked = e.stacked; - stacked = e.stacked; + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize( -availableHeight, 0); + + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + g.select('.nv-y.nv-axis').call(yAxis); } - chart.update(); - }); - }); - renderWatch.renderEnd('multibar horizontal chart immediate'); - return chart; - } + // Zero line + g.select(".nv-zeroLine line") + .attr("x1", y(0)) + .attr("x2", y(0)) + .attr("y1", 0) + .attr("y2", -availableHeight) + ; - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - 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); - }); + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); - multibar.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; - multibar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + switch (d.key) { + case 'Grouped': + case controlLabels.grouped: + multibar.stacked(false); + break; + case 'Stacked': + case controlLabels.stacked: + multibar.stacked(true); + break; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + stacked = multibar.stacked(); - // 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.update(); + }); - chart.options = nv.utils.optionsFunc.bind(chart); + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { - 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=_;}}, + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); - // 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); - multibar.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - barColor: {get: function(){return multibar.barColor;}, set: function(_){ - multibar.barColor(_); - legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) - }} - }); + state.disabled = e.disabled; + } - nv.utils.inheritOptions(chart, multibar); - nv.utils.initOptions(chart); + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + stacked = e.stacked; + } - return chart; -}; -nv.models.multiChart = function() { - "use strict"; + chart.update(); + }); + }); + renderWatch.renderEnd('multibar horizontal chart immediate'); + return chart; + } - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - var margin = {top: 30, right: 20, bottom: 50, left: 60}, - color = nv.utils.defaultColor(), - width = null, - height = null, - showLegend = true, - noData = null, - yDomain1, - yDomain2, - getX = function(d) { return d.x }, - getY = function(d) { return d.y}, - interpolate = 'linear', - useVoronoi = true, - interactiveLayer = nv.interactiveGuideline(), - useInteractiveGuideline = false, - legendRightAxisHint = ' (right axis)' - ; + 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); + }); - //============================================================ - // Private Variables - //------------------------------------------------------------ + multibar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); - var x = d3.scale.linear(), - yScale1 = d3.scale.linear(), - yScale2 = d3.scale.linear(), + multibar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - lines1 = nv.models.line().yScale(yScale1), - lines2 = nv.models.line().yScale(yScale2), + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - scatters1 = nv.models.scatter().yScale(yScale1), - scatters2 = nv.models.scatter().yScale(yScale2), + // 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; - bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), - bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + chart.options = nv.utils.optionsFunc.bind(chart); - stack1 = nv.models.stackedArea().yScale(yScale1), - stack2 = nv.models.stackedArea().yScale(yScale2), + 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=_;}}, - xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), - yAxis1 = nv.models.axis().scale(yScale1).orient('left'), - yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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); + multibar.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + barColor: {get: function(){return multibar.barColor;}, set: function(_){ + multibar.barColor(_); + legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) + }} + }); - legend = nv.models.legend().height(30), - tooltip = nv.models.tooltip(), - dispatch = d3.dispatch(); + nv.utils.inheritOptions(chart, multibar); + nv.utils.initOptions(chart); - var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2]; + return chart; + }; + nv.models.multiChart = function() { + "use strict"; - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - chart.update = function() { container.transition().call(chart); }; - chart.container = this; + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + marginTop = null, + color = nv.utils.defaultColor(), + width = null, + height = null, + showLegend = true, + noData = null, + yDomain1, + yDomain2, + getX = function(d) { return d.x }, + getY = function(d) { return d.y}, + interpolate = 'linear', + useVoronoi = true, + interactiveLayer = nv.interactiveGuideline(), + useInteractiveGuideline = false, + legendRightAxisHint = ' (right axis)', + duration = 250 + ; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + //============================================================ + // Private Variables + //------------------------------------------------------------ - var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1}); - var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2}); - var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1}); - var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2}); - var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1}); - var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2}); - var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1}); - var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2}); + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), - // 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(); - } + lines1 = nv.models.line().yScale(yScale1).duration(duration), + lines2 = nv.models.line().yScale(yScale2).duration(duration), - var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d), y: getY(d) } - }) - }); + scatters1 = nv.models.scatter().yScale(yScale1).duration(duration), + scatters2 = nv.models.scatter().yScale(yScale2).duration(duration), - var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d), y: getY(d) } - }) - }); + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1).duration(duration), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2).duration(duration), - x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x })) - .range([0, availableWidth]); + stack1 = nv.models.stackedArea().yScale(yScale1).duration(duration), + stack2 = nv.models.stackedArea().yScale(yScale2).duration(duration), - var wrap = container.selectAll('g.wrap.multiChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5).duration(duration), + yAxis1 = nv.models.axis().scale(yScale1).orient('left').duration(duration), + yAxis2 = nv.models.axis().scale(yScale2).orient('right').duration(duration), - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y1 nv-axis'); - gEnter.append('g').attr('class', 'nv-y2 nv-axis'); - gEnter.append('g').attr('class', 'stack1Wrap'); - gEnter.append('g').attr('class', 'stack2Wrap'); - gEnter.append('g').attr('class', 'bars1Wrap'); - gEnter.append('g').attr('class', 'bars2Wrap'); - gEnter.append('g').attr('class', 'scatters1Wrap'); - gEnter.append('g').attr('class', 'scatters2Wrap'); - gEnter.append('g').attr('class', 'lines1Wrap'); - gEnter.append('g').attr('class', 'lines2Wrap'); - gEnter.append('g').attr('class', 'legendWrap'); - gEnter.append('g').attr('class', 'nv-interactive'); + legend = nv.models.legend().height(30), + tooltip = nv.models.tooltip(), + dispatch = d3.dispatch(); - var g = wrap.select('g'); + var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2]; - var color_array = data.map(function(d,i) { - return data[i].color || color(d, i); - }); + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); - // Legend - if (!showLegend) { - g.select('.legendWrap').selectAll('*').remove(); - } else { - var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; - var legendXPosition = legend.align() ? legendWidth : 0; + chart.update = function() { container.transition().call(chart); }; + chart.container = this; - legend.width(legendWidth); - legend.color(color_array); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); - g.select('.legendWrap') - .datum(data.map(function(series) { - series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; - series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint); - return series; - })) - .call(legend); + var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1}); + var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2}); + var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1}); + var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2}); + var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1}); + var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2}); + var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1}); + var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2}); - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + // 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(); } - g.select('.legendWrap') - .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); - } + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d), y: getY(d) } + }) + }); - lines1 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); - lines2 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); - scatters1 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'})); - scatters2 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'})); - bars1 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); - bars2 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); - stack1 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); - stack2 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d), y: getY(d) } + }) + }); - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x })) + .range([0, availableWidth]); - var lines1Wrap = g.select('.lines1Wrap') - .datum(dataLines1.filter(function(d){return !d.disabled})); - var scatters1Wrap = g.select('.scatters1Wrap') - .datum(dataScatters1.filter(function(d){return !d.disabled})); - var bars1Wrap = g.select('.bars1Wrap') - .datum(dataBars1.filter(function(d){return !d.disabled})); - var stack1Wrap = g.select('.stack1Wrap') - .datum(dataStack1.filter(function(d){return !d.disabled})); - var lines2Wrap = g.select('.lines2Wrap') - .datum(dataLines2.filter(function(d){return !d.disabled})); - var scatters2Wrap = g.select('.scatters2Wrap') - .datum(dataScatters2.filter(function(d){return !d.disabled})); - var bars2Wrap = g.select('.bars2Wrap') - .datum(dataBars2.filter(function(d){return !d.disabled})); - var stack2Wrap = g.select('.stack2Wrap') - .datum(dataStack2.filter(function(d){return !d.disabled})); + var wrap = container.selectAll('g.wrap.multiChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); - var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : []; - var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : []; + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y1 nv-axis'); + gEnter.append('g').attr('class', 'nv-y2 nv-axis'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'scatters1Wrap'); + gEnter.append('g').attr('class', 'scatters2Wrap'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); - yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) - .range([0, availableHeight]); + var g = wrap.select('g'); - yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) - .range([0, availableHeight]); + var color_array = data.map(function(d,i) { + return data[i].color || color(d, i); + }); - lines1.yDomain(yScale1.domain()); - scatters1.yDomain(yScale1.domain()); - bars1.yDomain(yScale1.domain()); - stack1.yDomain(yScale1.domain()); + // Legend + if (!showLegend) { + g.select('.legendWrap').selectAll('*').remove(); + } else { + var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; + var legendXPosition = legend.align() ? legendWidth : 0; - lines2.yDomain(yScale2.domain()); - scatters2.yDomain(yScale2.domain()); - bars2.yDomain(yScale2.domain()); - stack2.yDomain(yScale2.domain()); + legend.width(legendWidth); + legend.color(color_array); - if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} - if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint); + return series; + })) + .call(legend); - if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} - if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } - if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} - if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + g.select('.legendWrap') + .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); + } - if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);} - if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);} + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + scatters1 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'})); + scatters2 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'})); + bars1 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + bars2 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + stack1 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + stack2 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); - xAxis - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - d3.transition(g.select('.nv-x.nv-axis')) - .call(xAxis); + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1.filter(function(d){return !d.disabled})); + var scatters1Wrap = g.select('.scatters1Wrap') + .datum(dataScatters1.filter(function(d){return !d.disabled})); + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1.filter(function(d){return !d.disabled})); + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1.filter(function(d){return !d.disabled})); + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2.filter(function(d){return !d.disabled})); + var scatters2Wrap = g.select('.scatters2Wrap') + .datum(dataScatters2.filter(function(d){return !d.disabled})); + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2.filter(function(d){return !d.disabled})); + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2.filter(function(d){return !d.disabled})); - yAxis1 - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : []; + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : []; + yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]); - d3.transition(g.select('.nv-y1.nv-axis')) - .call(yAxis1); + yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]); - yAxis2 - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + lines1.yDomain(yScale1.domain()); + scatters1.yDomain(yScale1.domain()); + bars1.yDomain(yScale1.domain()); + stack1.yDomain(yScale1.domain()); - d3.transition(g.select('.nv-y2.nv-axis')) - .call(yAxis2); + lines2.yDomain(yScale2.domain()); + scatters2.yDomain(yScale2.domain()); + bars2.yDomain(yScale2.domain()); + stack2.yDomain(yScale2.domain()); - g.select('.nv-y1.nv-axis') - .classed('nv-disabled', series1.length ? false : true) - .attr('transform', 'translate(' + x.range()[0] + ',0)'); + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} - g.select('.nv-y2.nv-axis') - .classed('nv-disabled', series2.length ? false : true) - .attr('transform', 'translate(' + x.range()[1] + ',0)'); + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} - legend.dispatch.on('stateChange', function(newState) { - chart.update(); - }); + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} - if(useInteractiveGuideline){ - interactiveLayer - .width(availableWidth) - .height(availableHeight) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } + if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);} + if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);} - //============================================================ - // Event Handling/Dispatching - //------------------------------------------------------------ + xAxis + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); - function mouseover_line(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.value = evt.point.x; - evt.series = { - value: evt.point.y, - color: evt.point.color, - key: evt.series.key - }; - tooltip - .duration(0) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - } + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); - function mouseover_scatter(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.value = evt.point.x; - evt.series = { - value: evt.point.y, - color: evt.point.color, - key: evt.series.key - }; - tooltip - .duration(100) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - } + yAxis1 + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); - function mouseover_stack(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.point['x'] = stack1.x()(evt.point); - evt.point['y'] = stack1.y()(evt.point); - tooltip - .duration(0) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - } - function mouseover_bar(evt) { - var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1; + d3.transition(g.select('.nv-y1.nv-axis')) + .call(yAxis1); - evt.value = bars1.x()(evt.data); - evt['series'] = { - value: bars1.y()(evt.data), - color: evt.color, - key: evt.data.key - }; - tooltip - .duration(0) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - } + yAxis2 + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + d3.transition(g.select('.nv-y2.nv-axis')) + .call(yAxis2); + g.select('.nv-y1.nv-axis') + .classed('nv-disabled', series1.length ? false : true) + .attr('transform', 'translate(' + x.range()[0] + ',0)'); - function clearHighlights() { - for(var i=0, il=charts.length; i < il; i++){ - var chart = charts[i]; - try { - chart.clearHighlights(); - } catch(e){} - } - } + g.select('.nv-y2.nv-axis') + .classed('nv-disabled', series2.length ? false : true) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); - function highlightPoint(serieIndex, pointIndex, b){ - for(var i=0, il=charts.length; i < il; i++){ - var chart = charts[i]; - try { - chart.highlightPoint(serieIndex, pointIndex, b); - } catch(e){} - } - } + legend.dispatch.on('stateChange', function(newState) { + chart.update(); + }); - if(useInteractiveGuideline){ - interactiveLayer.dispatch.on('elementMousemove', function(e) { - clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - var extent = x.domain(); - var currentValues = series.values.filter(function(d,i) { - return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1]; - }); + if(useInteractiveGuideline){ + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left, top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } - pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x()); - var point = currentValues[pointIndex]; - var pointYValue = chart.y()(point, pointIndex); - if (pointYValue !== null) { - highlightPoint(i, pointIndex, true); - } - if (point === undefined) return; - if (singlePoint === undefined) singlePoint = point; - if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: pointYValue, - color: color(series,series.seriesIndex), - data: point, - yAxis: series.yAxis == 2 ? yAxis2 : yAxis1 - }); - }); + //============================================================ + // Event Handling/Dispatching + //------------------------------------------------------------ - var defaultValueFormatter = function(d,i) { - var yAxis = allData[i].yAxis; - return d == null ? "N/A" : yAxis.tickFormat()(d); + function mouseover_line(evt) { + var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; + evt.value = evt.point.x; + evt.series = { + value: evt.point.y, + color: evt.point.color, + key: evt.series.key }; + tooltip + .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + } - interactiveLayer.tooltip + function mouseover_scatter(evt) { + var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; + evt.value = evt.point.x; + evt.series = { + value: evt.point.y, + color: evt.point.color, + key: evt.series.key + }; + tooltip + .duration(100) .headerFormatter(function(d, i) { return xAxis.tickFormat()(d, i); }) - .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) - .data({ - value: chart.x()( singlePoint,pointIndex ), - index: pointIndex, - series: allData - })(); + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + } - interactiveLayer.renderGuideLine(pointXLocation); - }); + function mouseover_stack(evt) { + var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; + evt.point['x'] = stack1.x()(evt.point); + evt.point['y'] = stack1.y()(evt.point); + tooltip + .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + } - interactiveLayer.dispatch.on("elementMouseout",function(e) { - clearHighlights(); - }); - } else { - lines1.dispatch.on('elementMouseover.tooltip', mouseover_line); - lines2.dispatch.on('elementMouseover.tooltip', mouseover_line); - lines1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - lines2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); + function mouseover_bar(evt) { + var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1; - scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter); - scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter); - scatters1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - scatters2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); + evt.value = bars1.x()(evt.data); + evt['series'] = { + value: bars1.y()(evt.data), + color: evt.color, + key: evt.data.key + }; + tooltip + .duration(0) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + } - stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack); - stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack); - stack1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - stack2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar); - bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar); - bars1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - bars2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - bars1.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); - bars2.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); - } - }); + function clearHighlights() { + for(var i=0, il=charts.length; i < il; i++){ + var chart = charts[i]; + try { + chart.clearHighlights(); + } catch(e){} + } + } - return chart; - } + function highlightPoint(serieIndex, pointIndex, b){ + for(var i=0, il=charts.length; i < il; i++){ + var chart = charts[i]; + try { + chart.highlightPoint(serieIndex, pointIndex, b); + } catch(e){} + } + } - //============================================================ - // Global getters and setters - //------------------------------------------------------------ + if(useInteractiveGuideline){ + interactiveLayer.dispatch.on('elementMousemove', function(e) { + clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + var extent = x.domain(); + var currentValues = series.values.filter(function(d,i) { + return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1]; + }); - chart.dispatch = dispatch; - chart.legend = legend; - chart.lines1 = lines1; - chart.lines2 = lines2; - chart.scatters1 = scatters1; - chart.scatters2 = scatters2; - chart.bars1 = bars1; - chart.bars2 = bars2; - chart.stack1 = stack1; - chart.stack2 = stack2; - chart.xAxis = xAxis; - chart.yAxis1 = yAxis1; - chart.yAxis2 = yAxis2; - chart.tooltip = tooltip; - chart.interactiveLayer = interactiveLayer; + pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x()); + var point = currentValues[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue !== null) { + highlightPoint(i, pointIndex, true); + } + if (point === undefined) return; + if (singlePoint === undefined) singlePoint = point; + if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: pointYValue, + color: color(series,series.seriesIndex), + data: point, + yAxis: series.yAxis == 2 ? yAxis2 : yAxis1 + }); + }); - chart.options = nv.utils.optionsFunc.bind(chart); + var defaultValueFormatter = function(d,i) { + var yAxis = allData[i].yAxis; + return d == null ? "N/A" : yAxis.tickFormat()(d); + }; - 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=_;}}, - yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}}, - yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, - legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, + interactiveLayer.tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter) + .data({ + value: chart.x()( singlePoint,pointIndex ), + index: pointIndex, + series: allData + })(); - // 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(_); - }}, - x: {get: function(){return getX;}, set: function(_){ - getX = _; - lines1.x(_); - lines2.x(_); - scatters1.x(_); - scatters2.x(_); - bars1.x(_); - bars2.x(_); - stack1.x(_); - stack2.x(_); - }}, - y: {get: function(){return getY;}, set: function(_){ - getY = _; - lines1.y(_); - lines2.y(_); - scatters1.y(_); - scatters2.y(_); - stack1.y(_); - stack2.y(_); - bars1.y(_); - bars2.y(_); - }}, - useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ - useVoronoi=_; - lines1.useVoronoi(_); - lines2.useVoronoi(_); - stack1.useVoronoi(_); - stack2.useVoronoi(_); - }}, + interactiveLayer.renderGuideLine(pointXLocation); + }); - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (useInteractiveGuideline) { - lines1.interactive(false); - lines1.useVoronoi(false); - lines2.interactive(false); - lines2.useVoronoi(false); - stack1.interactive(false); - stack1.useVoronoi(false); - stack2.interactive(false); - stack2.useVoronoi(false); - scatters1.interactive(false); - scatters2.interactive(false); - } - }} - }); + interactiveLayer.dispatch.on("elementMouseout",function(e) { + clearHighlights(); + }); + } else { + lines1.dispatch.on('elementMouseover.tooltip', mouseover_line); + lines2.dispatch.on('elementMouseover.tooltip', mouseover_line); + lines1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + lines2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); - nv.utils.initOptions(chart); + scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter); + scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter); + scatters1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + scatters2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); - return chart; -}; + stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack); + stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack); + stack1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + stack2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); -nv.models.ohlcBar = function() { - "use strict"; + bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar); + bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar); - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + bars1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + bars2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + bars1.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); + bars2.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); + } + }); - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getOpen = function(d) { return d.open } - , getClose = function(d) { return d.close } - , getHigh = function(d) { return d.high } - , getLow = function(d) { return d.low } - , forceX = [] - , forceY = [] - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , clipEdge = true - , color = nv.utils.defaultColor() - , interactive = false - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') - ; + return chart; + } - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Global getters and setters + //------------------------------------------------------------ - function chart(selection) { - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.scatters1 = scatters1; + chart.scatters2 = scatters2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + chart.tooltip = tooltip; + chart.interactiveLayer = interactiveLayer; - nv.utils.initSVG(container); + chart.options = nv.utils.optionsFunc.bind(chart); - // ohlc bar width. - var w = (availableWidth / data[0].values.length) * .9; + 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=_;}}, + yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}}, + yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, + legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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(_); + }}, + x: {get: function(){return getX;}, set: function(_){ + getX = _; + lines1.x(_); + lines2.x(_); + scatters1.x(_); + scatters2.x(_); + bars1.x(_); + bars2.x(_); + stack1.x(_); + stack2.x(_); + }}, + y: {get: function(){return getY;}, set: function(_){ + getY = _; + lines1.y(_); + lines2.y(_); + scatters1.y(_); + scatters2.y(_); + stack1.y(_); + stack2.y(_); + bars1.y(_); + bars2.y(_); + }}, + useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ + useVoronoi=_; + lines1.useVoronoi(_); + lines2.useVoronoi(_); + stack1.useVoronoi(_); + stack2.useVoronoi(_); + }}, - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]); + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (useInteractiveGuideline) { + lines1.interactive(false); + lines1.useVoronoi(false); + lines2.interactive(false); + lines2.useVoronoi(false); + stack1.interactive(false); + stack1.useVoronoi(false); + stack2.interactive(false); + stack2.useVoronoi(false); + scatters1.interactive(false); + scatters2.interactive(false); + } + }}, - y.domain(yDomain || [ - d3.min(data[0].values.map(getLow).concat(forceY)), - d3.max(data[0].values.map(getHigh).concat(forceY)) - ] - ).range(yRange || [availableHeight, 0]); + duration: {get: function(){return duration;}, set: function(_) { + duration = _; + [lines1, lines2, stack1, stack2, scatters1, scatters2, xAxis, yAxis1, yAxis2].forEach(function(model){ + model.duration(duration); + }); + }} + }); - // 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]); + nv.utils.initOptions(chart); - 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]); + return chart; + }; - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + nv.models.ohlcBar = function() { + "use strict"; - gEnter.append('g').attr('class', 'nv-ticks'); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , interactive = false + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') + ; - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id + //============================================================ + // Private Variables + //------------------------------------------------------------ + + function chart(selection) { + 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); + + // ohlc bar width. + var w = (availableWidth / data[0].values.length) * .9; + + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]); + + y.domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).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]); + + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); }); - }); - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') - .data(function(d) { return d }); - ticks.exit().remove(); + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + ticks.exit().remove(); - ticks.enter().append('path') - .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) - .attr('d', function(d,i) { - return 'm0,0l0,' - + (y(getOpen(d,i)) + ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + return 'm0,0l0,' + + (y(getOpen(d,i)) - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; - }) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - .attr('fill', function(d,i) { return color[0]; }) - .attr('stroke', function(d,i) { return color[0]; }) - .attr('x', 0 ) - .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('fill', function(d,i) { return color[0]; }) + .attr('stroke', function(d,i) { return color[0]; }) + .attr('x', 0 ) + .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); - // the bar colors are controlled by CSS currently - ticks.attr('class', function(d,i,j) { - return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i; - }); + // the bar colors are controlled by CSS currently + ticks.attr('class', function(d,i,j) { + return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i; + }); - d3.transition(ticks) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - .attr('d', function(d,i) { - var w = (availableWidth / data[0].values.length) * .9; - return 'm0,0l0,' - + (y(getOpen(d,i)) + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) + + 'l0,' + + (y(getClose(d,i)) - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; - }); - }); + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }); + }); - return chart; - } + return chart; + } - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - chart.clearHighlights(); - container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + chart.clearHighlights(); + container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; - chart.clearHighlights = function() { - container.select(".nv-ohlcBar .nv-tick.hover") - .classed("hover", false) - ; - }; + chart.clearHighlights = function() { + container.select(".nv-ohlcBar .nv-tick.hover") + .classed("hover", false) + ; + }; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - 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=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + 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=_;}}, + 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=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, - close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, - high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, - low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, + close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, + high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, + low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, - // 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(_); - }} - }); + // 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.utils.initOptions(chart); + return chart; + }; // Code adapted from Jason Davies' "Parallel Coordinates" // http://bl.ocks.org/jasondavies/1341281 -nv.models.parallelCoordinates = function() { - "use strict"; + nv.models.parallelCoordinates = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var margin = {top: 30, right: 0, bottom: 10, left: 0} - , width = null - , height = null - , availableWidth = null - , availableHeight = null - , x = d3.scale.ordinal() - , y = {} - , undefinedValuesLabel = "undefined values" - , dimensionData = [] - , enabledDimensions = [] - , dimensionNames = [] - , displayBrush = true - , color = nv.utils.defaultColor() - , filters = [] - , active = [] - , dragging = [] - , axisWithUndefinedValues = [] - , lineTension = 1 - , foreground - , background - , dimensions - , line = d3.svg.line() - , axis = d3.svg.axis() - , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged') - ; + var margin = {top: 30, right: 0, bottom: 10, left: 0} + , width = null + , height = null + , availableWidth = null + , availableHeight = null + , x = d3.scale.ordinal() + , y = {} + , undefinedValuesLabel = "undefined values" + , dimensionData = [] + , enabledDimensions = [] + , dimensionNames = [] + , displayBrush = true + , color = nv.utils.defaultColor() + , filters = [] + , active = [] + , dragging = [] + , axisWithUndefinedValues = [] + , lineTension = 1 + , foreground + , background + , dimensions + , line = d3.svg.line() + , axis = d3.svg.axis() + , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged') + ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); + var renderWatch = nv.utils.renderWatch(dispatch); - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var container = d3.select(this); - availableWidth = nv.utils.availableWidth(width, container, margin); - availableHeight = nv.utils.availableHeight(height, container, margin); + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var container = d3.select(this); + availableWidth = nv.utils.availableWidth(width, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin); - nv.utils.initSVG(container); + nv.utils.initSVG(container); - //Convert old data to new format (name, values) - if (data[0].values === undefined) { - var newData = []; - data.forEach(function (d) { + //Convert old data to new format (name, values) + if (data[0].values === undefined) { + var newData = []; + data.forEach(function (d) { var val = {}; var key = Object.keys(d); key.forEach(function (k) { if (k !== "name") val[k] = d[k] }); newData.push({ key: d.name, values: val }); - }); - data = newData; - } + }); + data = newData; + } - var dataValues = data.map(function (d) {return d.values}); - if (active.length === 0) { - active = data; - }; //set all active before first brush call + var dataValues = data.map(function (d) {return d.values}); + if (active.length === 0) { + active = data; + }; //set all active before first brush call - dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key }); - enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; }); + dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key }); + enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; }); - // Setup Scales - x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; })); + // Setup Scales + x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; })); - //Set as true if all values on an axis are missing. - // Extract the list of dimensions and create a scale for each. - var oldDomainMaxValue = {}; - var displayMissingValuesline = false; - var currentTicks = []; + //Set as true if all values on an axis are missing. + // Extract the list of dimensions and create a scale for each. + var oldDomainMaxValue = {}; + var displayMissingValuesline = false; + var currentTicks = []; - dimensionNames.forEach(function(d) { - var extent = d3.extent(dataValues, function (p) { return +p[d]; }); - var min = extent[0]; - var max = extent[1]; - var onlyUndefinedValues = false; - //If there is no values to display on an axis, set the extent to 0 - if (isNaN(min) || isNaN(max)) { - onlyUndefinedValues = true; - min = 0; - max = 0; - } - //Scale axis if there is only one value - if (min === max) { - min = min - 1; - max = max + 1; - } - var f = filters.filter(function (k) { return k.dimension == d; }); - if (f.length !== 0) { - //If there is only NaN values, keep the existing domain. - if (onlyUndefinedValues) { - min = y[d].domain()[0]; - max = y[d].domain()[1]; + dimensionNames.forEach(function(d) { + var extent = d3.extent(dataValues, function (p) { return +p[d]; }); + var min = extent[0]; + var max = extent[1]; + var onlyUndefinedValues = false; + //If there is no values to display on an axis, set the extent to 0 + if (isNaN(min) || isNaN(max)) { + onlyUndefinedValues = true; + min = 0; + max = 0; } - //If the brush extent is > max (< min), keep the extent value. - else if (!f[0].hasOnlyNaN && displayBrush) { - min = min > f[0].extent[0] ? f[0].extent[0] : min; - max = max < f[0].extent[1] ? f[0].extent[1] : max; + //Scale axis if there is only one value + if (min === max) { + min = min - 1; + max = max + 1; } + var f = filters.filter(function (k) { return k.dimension == d; }); + if (f.length !== 0) { + //If there is only NaN values, keep the existing domain. + if (onlyUndefinedValues) { + min = y[d].domain()[0]; + max = y[d].domain()[1]; + } + //If the brush extent is > max (< min), keep the extent value. + else if (!f[0].hasOnlyNaN && displayBrush) { + min = min > f[0].extent[0] ? f[0].extent[0] : min; + max = max < f[0].extent[1] ? f[0].extent[1] : max; + } //If there is NaN values brushed be sure the brush extent is on the domain. - else if (f[0].hasNaN) { - max = max < f[0].extent[1] ? f[0].extent[1] : max; - oldDomainMaxValue[d] = y[d].domain()[1]; - displayMissingValuesline = true; + else if (f[0].hasNaN) { + max = max < f[0].extent[1] ? f[0].extent[1] : max; + oldDomainMaxValue[d] = y[d].domain()[1]; + displayMissingValuesline = true; + } } - } - //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text. - //The remaining 10% are used to display the missingValue line. - y[d] = d3.scale.linear() - .domain([min, max]) - .range([(availableHeight - 12) * 0.9, 0]); + //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text. + //The remaining 10% are used to display the missingValue line. + y[d] = d3.scale.linear() + .domain([min, max]) + .range([(availableHeight - 12) * 0.9, 0]); - axisWithUndefinedValues = []; - y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend); - }); + axisWithUndefinedValues = []; + y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend); + }); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-parallelCoordinates background'); - gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground'); - gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline'); + gEnter.append('g').attr('class', 'nv-parallelCoordinates background'); + gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground'); + gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - line.interpolate('cardinal').tension(lineTension); - axis.orient('left'); - var axisDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); + line.interpolate('cardinal').tension(lineTension); + axis.orient('left'); + var axisDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); - //Add missing value line at the bottom of the chart - var missingValuesline, missingValueslineText; - var step = x.range()[1] - x.range()[0]; - if (!isNaN(step)) { - var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12]; - missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]); - missingValuesline.enter().append('line'); - missingValuesline.exit().remove(); - missingValuesline.attr("x1", function(d) { return d[0]; }) + //Add missing value line at the bottom of the chart + var missingValuesline, missingValueslineText; + var step = x.range()[1] - x.range()[0]; + step = isNaN(step) ? x.range()[0] : step; + if (!isNaN(step)) { + var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12]; + missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]); + missingValuesline.enter().append('line'); + missingValuesline.exit().remove(); + missingValuesline.attr("x1", function(d) { return d[0]; }) .attr("y1", function(d) { return d[1]; }) .attr("x2", function(d) { return d[2]; }) .attr("y2", function(d) { return d[3]; }); - //Add the text "undefined values" under the missing value line - missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]); - missingValueslineText.append('text').data([undefinedValuesLabel]); - missingValueslineText.enter().append('text'); - missingValueslineText.exit().remove(); - missingValueslineText.attr("y", availableHeight) - //To have the text right align with the missingValues line, substract 92 representing the text size. + //Add the text "undefined values" under the missing value line + missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]); + missingValueslineText.append('text').data([undefinedValuesLabel]); + missingValueslineText.enter().append('text'); + missingValueslineText.exit().remove(); + missingValueslineText.attr("y", availableHeight) + //To have the text right align with the missingValues line, substract 92 representing the text size. .attr("x", availableWidth - 92 - step / 2) .text(function(d) { return d; }); - } - // Add grey background lines for context. - background = wrap.select('.background').selectAll('path').data(data); - background.enter().append('path'); - background.exit().remove(); - background.attr('d', path); + } + // Add grey background lines for context. + background = wrap.select('.background').selectAll('path').data(data); + background.enter().append('path'); + background.exit().remove(); + background.attr('d', path); - // Add blue foreground lines for focus. - foreground = wrap.select('.foreground').selectAll('path').data(data); - foreground.enter().append('path') - foreground.exit().remove(); - foreground.attr('d', path) - .style("stroke-width", function (d, i) { - if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;}) - .attr('stroke', function (d, i) { return d.color || color(d, i); }); - foreground.on("mouseover", function (d, i) { - d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1); - dispatch.elementMouseover({ - label: d.name, - color: d.color || color(d, i), - values: d.values, - dimensions: enabledDimensions - }); - - }); - foreground.on("mouseout", function (d, i) { - d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7); - dispatch.elementMouseout({ - label: d.name, - index: i - }); - }); - foreground.on('mousemove', function (d, i) { - dispatch.elementMousemove(); - }); - foreground.on('click', function (d) { - dispatch.elementClick({ - id: d.id - }); - }); - // Add a group element for each dimension. - dimensions = g.selectAll('.dimension').data(enabledDimensions); - var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension'); - - dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; }); - dimensionsEnter.append('g').attr('class', 'nv-axis'); - - // Add an axis and title. - dimensionsEnter.append('text') - .attr('class', 'nv-label') - .style("cursor", "move") - .attr('dy', '-1em') - .attr('text-anchor', 'middle') - .on("mouseover", function(d, i) { + // Add blue foreground lines for focus. + foreground = wrap.select('.foreground').selectAll('path').data(data); + foreground.enter().append('path') + foreground.exit().remove(); + foreground.attr('d', path) + .style("stroke-width", function (d, i) { + if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;}) + .attr('stroke', function (d, i) { return d.color || color(d, i); }); + foreground.on("mouseover", function (d, i) { + d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1); dispatch.elementMouseover({ - label: d.tooltip || d.key, - color: d.color + label: d.name, + color: d.color || color(d, i), + values: d.values, + dimensions: enabledDimensions }); - }) - .on("mouseout", function(d, i) { + + }); + foreground.on("mouseout", function (d, i) { + d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7); dispatch.elementMouseout({ - label: d.tooltip + label: d.name, + index: i }); - }) - .on('mousemove', function (d, i) { + }); + foreground.on('mousemove', function (d, i) { dispatch.elementMousemove(); - }) - .call(axisDrag); + }); + foreground.on('click', function (d) { + dispatch.elementClick({ + id: d.id + }); + }); + // Add a group element for each dimension. + dimensions = g.selectAll('.dimension').data(enabledDimensions); + var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension'); - dimensionsEnter.append('g').attr('class', 'nv-brushBackground'); - dimensions.exit().remove(); - dimensions.select('.nv-label').text(function (d) { return d.key }); + dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; }); + dimensionsEnter.append('g').attr('class', 'nv-axis'); - // Add and store a brush for each axis. - restoreBrush(displayBrush); + // Add an axis and title. + dimensionsEnter.append('text') + .attr('class', 'nv-label') + .style("cursor", "move") + .attr('dy', '-1em') + .attr('text-anchor', 'middle') + .on("mouseover", function(d, i) { + dispatch.elementMouseover({ + label: d.tooltip || d.key, + color: d.color + }); + }) + .on("mouseout", function(d, i) { + dispatch.elementMouseout({ + label: d.tooltip + }); + }) + .on('mousemove', function (d, i) { + dispatch.elementMousemove(); + }) + .call(axisDrag); - var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }), + dimensionsEnter.append('g').attr('class', 'nv-brushBackground'); + dimensions.exit().remove(); + dimensions.select('.nv-label').text(function (d) { return d.key }); + + // Add and store a brush for each axis. + restoreBrush(displayBrush); + + var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }), extents = actives.map(function (p) { return y[p].brush.extent(); }); - var formerActive = active.slice(0); + var formerActive = active.slice(0); - //Restore active values - active = []; - foreground.style("display", function (d) { - var isActive = actives.every(function (p, i) { - if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) { - return true; - } - return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p])); + //Restore active values + active = []; + foreground.style("display", function (d) { + var isActive = actives.every(function (p, i) { + if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) { + return true; + } + return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p])); + }); + if (isActive) + active.push(d); + return !isActive ? "none" : null; + }); - if (isActive) - active.push(d); - return !isActive ? "none" : null; - }); + if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) { + dispatch.activeChanged(active); + } - if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) { - dispatch.activeChanged(active); - } + // Returns the path for a given data point. + function path(d) { + return line(enabledDimensions.map(function (p) { + //If value if missing, put the value on the missing value line + if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) { + var domain = y[p.key].domain(); + var range = y[p.key].range(); + var min = domain[0] - (domain[1] - domain[0]) / 9; - // Returns the path for a given data point. - function path(d) { - return line(enabledDimensions.map(function (p) { - //If value if missing, put the value on the missing value line - if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) { - var domain = y[p.key].domain(); - var range = y[p.key].range(); - var min = domain[0] - (domain[1] - domain[0]) / 9; + //If it's not already the case, allow brush to select undefined values + if (axisWithUndefinedValues.indexOf(p.key) < 0) { - //If it's not already the case, allow brush to select undefined values - if (axisWithUndefinedValues.indexOf(p.key) < 0) { + var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]); + y[p.key].brush.y(newscale); + axisWithUndefinedValues.push(p.key); + } + if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) { + return [x(p.key), y[p.key](min)]; + } + } - var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]); - y[p.key].brush.y(newscale); - axisWithUndefinedValues.push(p.key); + //If parallelCoordinate contain missing values show the missing values line otherwise, hide it. + if (missingValuesline !== undefined) { + if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) { + missingValuesline.style("display", "inline"); + missingValueslineText.style("display", "inline"); + } else { + missingValuesline.style("display", "none"); + missingValueslineText.style("display", "none"); + } } - if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) { - return [x(p.key), y[p.key](min)]; - } - } + return [x(p.key), y[p.key](d.values[p.key])]; + })); + } - //If parallelCoordinate contain missing values show the missing values line otherwise, hide it. - if (missingValuesline !== undefined) { - if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) { - missingValuesline.style("display", "inline"); - missingValueslineText.style("display", "inline"); - } else { - missingValuesline.style("display", "none"); - missingValueslineText.style("display", "none"); + function restoreBrush(visible) { + filters.forEach(function (f) { + //If filter brushed NaN values, keep the brush on the bottom of the axis. + var brushDomain = y[f.dimension].brush.y().domain(); + if (f.hasOnlyNaN) { + f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0]; } - } - return [x(p.key), y[p.key](d.values[p.key])]; - })); - } + if (f.hasNaN) { + f.extent[0] = brushDomain[0]; + } + if (visible) + y[f.dimension].brush.extent(f.extent); + }); - function restoreBrush(visible) { - filters.forEach(function (f) { - //If filter brushed NaN values, keep the brush on the bottom of the axis. - var brushDomain = y[f.dimension].brush.y().domain(); - if (f.hasOnlyNaN) { - f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0]; - } - if (f.hasNaN) { - f.extent[0] = brushDomain[0]; - } - if (visible) - y[f.dimension].brush.extent(f.extent); - }); + dimensions.select('.nv-brushBackground') + .each(function (d) { + d3.select(this).call(y[d.key].brush); - dimensions.select('.nv-brushBackground') - .each(function (d) { - d3.select(this).call(y[d.key].brush); + }) + .selectAll('rect') + .attr('x', -8) + .attr('width', 16); - }) - .selectAll('rect') - .attr('x', -8) - .attr('width', 16); + updateTicks(); + } - updateTicks(); - } - - // Handles a brush event, toggling the display of foreground lines. - function brushstart() { - //If brush aren't visible, show it before brushing again. - if (displayBrush === false) { - displayBrush = true; - restoreBrush(true); + // Handles a brush event, toggling the display of foreground lines. + function brushstart() { + //If brush aren't visible, show it before brushing again. + if (displayBrush === false) { + displayBrush = true; + restoreBrush(true); + } } - } - // Handles a brush event, toggling the display of foreground lines. - function brush() { - actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }); - extents = actives.map(function(p) { return y[p].brush.extent(); }); + // Handles a brush event, toggling the display of foreground lines. + function brush() { + actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }); + extents = actives.map(function(p) { return y[p].brush.extent(); }); - filters = []; //erase current filters - actives.forEach(function(d,i) { - filters[i] = { - dimension: d, - extent: extents[i], - hasNaN: false, - hasOnlyNaN: false - } - }); + filters = []; //erase current filters + actives.forEach(function(d,i) { + filters[i] = { + dimension: d, + extent: extents[i], + hasNaN: false, + hasOnlyNaN: false + } + }); - active = []; //erase current active list - foreground.style('display', function(d) { - var isActive = actives.every(function(p, i) { - if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true; - return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p])); + active = []; //erase current active list + foreground.style('display', function(d) { + var isActive = actives.every(function(p, i) { + if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true; + return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p])); + }); + if (isActive) active.push(d); + return isActive ? null : 'none'; }); - if (isActive) active.push(d); - return isActive ? null : 'none'; - }); - updateTicks(); + updateTicks(); - dispatch.brush({ - filters: filters, - active: active - }); - } - function brushend() { - var hasActiveBrush = actives.length > 0 ? true : false; - filters.forEach(function (f) { - if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0) - f.hasNaN = true; - if (f.extent[1] < y[f.dimension].domain()[0]) - f.hasOnlyNaN = true; - }); - dispatch.brushEnd(active, hasActiveBrush); - } - function updateTicks() { - dimensions.select('.nv-axis') - .each(function (d, i) { - var f = filters.filter(function (k) { return k.dimension == d.key; }); - currentTicks[d.key] = y[d.key].domain(); + dispatch.brush({ + filters: filters, + active: active + }); + } + function brushend() { + var hasActiveBrush = actives.length > 0 ? true : false; + filters.forEach(function (f) { + if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0) + f.hasNaN = true; + if (f.extent[1] < y[f.dimension].domain()[0]) + f.hasOnlyNaN = true; + }); + dispatch.brushEnd(active, hasActiveBrush); + } + function updateTicks() { + dimensions.select('.nv-axis') + .each(function (d, i) { + var f = filters.filter(function (k) { return k.dimension == d.key; }); + currentTicks[d.key] = y[d.key].domain(); - //If brush are available, display brush extent - if (f.length != 0 && displayBrush) - { - currentTicks[d.key] = []; - if (f[0].extent[1] > y[d.key].domain()[0]) - currentTicks[d.key] = [f[0].extent[1]]; - if (f[0].extent[0] >= y[d.key].domain()[0]) - currentTicks[d.key].push(f[0].extent[0]); - } + //If brush are available, display brush extent + if (f.length != 0 && displayBrush) + { + currentTicks[d.key] = []; + if (f[0].extent[1] > y[d.key].domain()[0]) + currentTicks[d.key] = [f[0].extent[1]]; + if (f[0].extent[0] >= y[d.key].domain()[0]) + currentTicks[d.key].push(f[0].extent[0]); + } - d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key])); - }); - } - function dragStart(d) { - dragging[d.key] = this.parentNode.__origin__ = x(d.key); - background.attr("visibility", "hidden"); - } - function dragMove(d) { - dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); - foreground.attr("d", path); - enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); }); - enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; }); - x.domain(enabledDimensions.map(function (d) { return d.key; })); - dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; }); - } - function dragEnd(d, i) { - delete this.parentNode.__origin__; - delete dragging[d.key]; - d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")"); - foreground - .attr("d", path); - background - .attr("d", path) - .attr("visibility", null); + d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key])); + }); + } + function dragStart(d) { + dragging[d.key] = this.parentNode.__origin__ = x(d.key); + background.attr("visibility", "hidden"); + } + function dragMove(d) { + dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); + foreground.attr("d", path); + enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); }); + enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; }); + x.domain(enabledDimensions.map(function (d) { return d.key; })); + dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; }); + } + function dragEnd(d, i) { + delete this.parentNode.__origin__; + delete dragging[d.key]; + d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")"); + foreground + .attr("d", path); + background + .attr("d", path) + .attr("visibility", null); - dispatch.dimensionsOrder(enabledDimensions); - } - function dimensionPosition(d) { - var v = dragging[d]; - return v == null ? x(d) : v; - } - }); - return chart; - } + dispatch.dimensionsOrder(enabledDimensions); + } + function dimensionPosition(d) { + var v = dragging[d]; + return v == null ? x(d) : v; + } + }); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + 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= _;}}, - dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } }, - displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } }, - filters: { get: function () { return filters; }, set: function (_) { filters = _; } }, - active: { get: function () { return active; }, set: function (_) { active = _; } }, - lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, - undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}}, + 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= _;}}, + dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } }, + displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } }, + filters: { get: function () { return filters; }, set: function (_) { filters = _; } }, + active: { get: function () { return active; }, set: function (_) { active = _; } }, + lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, + undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}}, - // deprecated options - dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { - // deprecated after 1.8.1 - nv.deprecated('dimensions', 'use dimensionData instead'); - if (dimensionData.length === 0) { - _.forEach(function (k) { dimensionData.push({ key: k }) }) - } else { - _.forEach(function (k, i) { dimensionData[i].key= k }) - } - }}, - dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { - // deprecated after 1.8.1 - nv.deprecated('dimensionNames', 'use dimensionData instead'); - dimensionNames = []; - if (dimensionData.length === 0) { - _.forEach(function (k) { dimensionData.push({ key: k }) }) - } else { - _.forEach(function (k, i) { dimensionData[i].key = k }) - } + // deprecated options + dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { + // deprecated after 1.8.1 + nv.deprecated('dimensions', 'use dimensionData instead'); + if (dimensionData.length === 0) { + _.forEach(function (k) { dimensionData.push({ key: k }) }) + } else { + _.forEach(function (k, i) { dimensionData[i].key= k }) + } + }}, + dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) { + // deprecated after 1.8.1 + nv.deprecated('dimensionNames', 'use dimensionData instead'); + dimensionNames = []; + if (dimensionData.length === 0) { + _.forEach(function (k) { dimensionData.push({ key: k }) }) + } else { + _.forEach(function (k, i) { dimensionData[i].key = k }) + } - }}, - dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) { - // deprecated after 1.8.1 - nv.deprecated('dimensionFormats', 'use dimensionData instead'); - if (dimensionData.length === 0) { - _.forEach(function (f) { dimensionData.push({ format: f }) }) - } else { - _.forEach(function (f, i) { dimensionData[i].format = f }) - } + }}, + dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) { + // deprecated after 1.8.1 + nv.deprecated('dimensionFormats', 'use dimensionData instead'); + if (dimensionData.length === 0) { + _.forEach(function (f) { dimensionData.push({ format: f }) }) + } else { + _.forEach(function (f, i) { dimensionData[i].format = f }) + } - }}, - // 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.parallelCoordinatesChart = function () { + }}, + // 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.parallelCoordinatesChart = function () { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ @@ -10861,27 +10956,28 @@ var legend = nv.models.legend() var tooltip = nv.models.tooltip(); var dimensionTooltip = nv.models.tooltip(); var margin = { top: 0, right: 0, bottom: 0, left: 0 } - , width = null - , height = null - , showLegend = true - , color = nv.utils.defaultColor() - , state = nv.utils.state() - , dimensionData = [] - , displayBrush = true - , defaultState = null - , noData = null - , nanValue = "undefined" - , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd') - , controlWidth = function () { return showControls ? 180 : 0 } - ; + , marginTop = null + , width = null + , height = null + , showLegend = true + , color = nv.utils.defaultColor() + , state = nv.utils.state() + , dimensionData = [] + , displayBrush = true + , defaultState = null + , noData = null + , nanValue = "undefined" + , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd') + , controlWidth = function () { return showControls ? 180 : 0 } + ; - //============================================================ + //============================================================ - //============================================================ + //============================================================ // Private Variables //------------------------------------------------------------ var renderWatch = nv.utils.renderWatch(dispatch); @@ -10949,11 +11045,11 @@ dimensionData.forEach(function (d, i) { d.originalPosition = isNaN(d.originalPosition) ? i : d.originalPosition; d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition; }); - if (!defaultState) { + if (!defaultState) { var key; defaultState = {}; for(key in state) { if(state[key] instanceof Array) defaultState[key] = state[key].slice(0); @@ -10994,66 +11090,66 @@ g.select('.nv-legendWrap') .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; })) .call(legend); - if (legend.height() > margin.top) { + if (!marginTop && legend.height() !== margin.top) { margin.top = legend.height(); availableHeight = nv.utils.availableHeight(height, container, margin); } wrap.select('.nv-legendWrap') - .attr('transform', 'translate( 0 ,' + (-margin.top) + ')'); + .attr('transform', 'translate( 0 ,' + (-margin.top) + ')'); } wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); // Main Chart Component(s) parallelCoordinates .width(availableWidth) .height(availableHeight) .dimensionData(dimensionData) .displayBrush(displayBrush); - var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ') - .datum(data); + var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ') + .datum(data); - parallelCoordinatesWrap.transition().call(parallelCoordinates); + parallelCoordinatesWrap.transition().call(parallelCoordinates); - //============================================================ + //============================================================ // Event Handling/Dispatching (in chart's scope) //------------------------------------------------------------ //Display reset brush button - parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) { - if (hasActiveBrush) { - displayBrush = true; - dispatch.brushEnd(active); - } else { + parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) { + if (hasActiveBrush) { + displayBrush = true; + dispatch.brushEnd(active); + } else { - displayBrush = false; - } - }); + displayBrush = false; + } + }); - legend.dispatch.on('stateChange', function(newState) { - for(var key in newState) { - state[key] = newState[key]; - } - dispatch.stateChange(state); - chart.update(); - }); + legend.dispatch.on('stateChange', function(newState) { + for(var key in newState) { + state[key] = newState[key]; + } + dispatch.stateChange(state); + chart.update(); + }); //Update dimensions order and display reset sorting button - parallelCoordinates.dispatch.on('dimensionsOrder', function (e) { - dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }); - var isSorted = false; - dimensionData.forEach(function (d, i) { - d.currentPosition = i; - if (d.currentPosition !== d.originalPosition) - isSorted = true; - }); - dispatch.dimensionsOrder(dimensionData, isSorted); - }); + parallelCoordinates.dispatch.on('dimensionsOrder', function (e) { + dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }); + var isSorted = false; + dimensionData.forEach(function (d, i) { + d.currentPosition = i; + if (d.currentPosition !== d.originalPosition) + isSorted = true; + }); + dispatch.dimensionsOrder(dimensionData, isSorted); + }); - // Update chart from a state object passed to event handler + // Update chart from a state object passed to event handler dispatch.on('changeState', function (e) { if (typeof e.disabled !== 'undefined') { dimensionData.forEach(function (series, i) { series.disabled = e.disabled[i]; @@ -11066,20 +11162,20 @@ renderWatch.renderEnd('parraleleCoordinateChart immediate'); return chart; } - //============================================================ + //============================================================ // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) { var tp = { key: evt.label, color: evt.color, series: [] - } + } if(evt.values){ Object.keys(evt.values).forEach(function (d) { var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0]; if(dim){ var v; @@ -11090,26 +11186,26 @@ } tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color }); } }); tp.series.sort(function(a,b) {return a.idx - b.idx}); - } + } tooltip.data(tp).hidden(false); }); parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true) }); parallelCoordinates.dispatch.on('elementMousemove.tooltip', function () { tooltip(); }); - //============================================================ + //============================================================ // Expose Public Variables //------------------------------------------------------------ - // expose chart's sub-components + // expose chart's sub-components chart.dispatch = dispatch; chart.parallelCoordinates = parallelCoordinates; chart.legend = legend; chart.tooltip = tooltip; chart.options = nv.utils.optionsFunc.bind(chart); @@ -11127,266 +11223,308 @@ // options that require extra logic in the setter margin: { get: function () { return margin; }, set: function (_) { - margin.top = _.top !== undefined ? _.top : margin.top; + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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(_); - legend.color(color); - parallelCoordinates.color(color); - }} + color = nv.utils.getColor(_); + legend.color(color); + parallelCoordinates.color(color); + }} }); nv.utils.inheritOptions(chart, parallelCoordinates); nv.utils.initOptions(chart); return chart; }; -nv.models.pie = function() { - "use strict"; + nv.models.pie = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // 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 - , duration = 250 - , arcsRadius = [] - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; + 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 + , duration = 250 + , arcsRadius = [] + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; - var arcs = []; - var arcsOver = []; + var arcs = []; + var arcsOver = []; - //============================================================ - // chart function - //------------------------------------------------------------ + //============================================================ + // chart function + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); + 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 = [] - ; + 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); })); + 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 { - 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; })); + 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); + 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'); + // 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 + ')'); + 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 + // + 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++) { + 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); + 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 (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); + if (arc.cornerRadius && cornerRadius) { + arc.cornerRadius(cornerRadius); + arcOver.cornerRadius(cornerRadius); + } + + arcs.push(arc); + arcsOver.push(arcOver); } - 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) }); - // 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); + } - // 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'); - // 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 + ')'; + }); + } - 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); - 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(); - 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"), - percent: (d.endAngle - d.startAngle) / (2 * Math.PI) + 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"), + percent: (d.endAngle - d.startAngle) / (2 * Math.PI) + }); }); - }); - 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('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('dblclick', function(d, i) { - dispatch.elementDblClick({ - data: d.data, - index: i, - color: d3.select(this).style("fill") + 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); }); + 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; - }); + var paths = ae.append('path').each(function(d) { + this._current = d; + }); - slices.select('path') - .transition() - .duration(duration) - .attr('d', function (d, i) { return arcs[i](d); }) - .attrTween('d', arcTween); + slices.select('path') + .transition() + .duration(duration) + .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 (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) { + 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); + pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) { + var group = d3.select(this); - group.attr('transform', function (d, i) { + 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) { @@ -11396,3316 +11534,3892 @@ } 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) + ')' + + /* + 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 + ')' } }); - group.append('rect') - .style('stroke', '#fff') - .style('fill', '#fff') - .attr("rx", 3) - .attr("ry", 3); + 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 ''; - 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') - }); + 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; + }) + ; + } - 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; + + // 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; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, + 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.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} + , marginTop = null + , width = null + , height = null + , showTooltipPercent = false + , 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 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; + 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 (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); } - 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; + 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) } - return 'translate(' + center + ')' + 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(); }); - 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 ''; + // 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(); + }); + }); - 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; - }) - ; - } + renderWatch.renderEnd('pieChart immediate'); + return chart; + } + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - // 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; + pie.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: chart.x()(evt.data), + value: chart.y()(evt.data), + color: evt.color, + percent: evt.percent + }; + if (!showTooltipPercent) { + delete evt.percent; + delete evt.series.percent; } + tooltip.data(evt).hidden(false); + }); - 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)); - }; - } + pie.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); }); - renderWatch.renderEnd('pie immediate'); - return chart; - } + pie.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + chart.tooltip = tooltip; + 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=_;}}, + // 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=_;}}, + showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - // 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 + 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); + pie.duration(duration); + }}, + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.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; + }; + nv.models.sankey = function() { + 'use strict'; - // 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; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }}, - 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'; - }} - }); + // Sources: + // - https://bost.ocks.org/mike/sankey/ + // - https://github.com/soxofaan/d3-plugin-captain-sankey - nv.utils.initOptions(chart); - return chart; -}; -nv.models.pieChart = function() { - "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + var sankey = {}, + nodeWidth = 24, + nodePadding = 8, + size = [1, 1], + nodes = [], + links = [], + sinksRight = true; - var pie = nv.models.pie(); - var legend = nv.models.legend(); - var tooltip = nv.models.tooltip(); + var layout = function(iterations) { + computeNodeLinks(); + computeNodeValues(); + computeNodeBreadths(); + computeNodeDepths(iterations); + }; - var margin = {top: 30, right: 20, bottom: 20, left: 20} - , width = null - , height = null - , showTooltipPercent = false - , showLegend = true - , legendPosition = "top" - , color = nv.utils.defaultColor() - , state = nv.utils.state() - , defaultState = null - , noData = null - , duration = 250 - , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') - ; + var relayout = function() { + computeLinkDepths(); + }; - tooltip - .duration(0) - .headerEnabled(false) - .valueFormatter(function(d, i) { - return pie.valueFormat()(d, i); - }); + // SVG path data generator, to be used as 'd' attribute on 'path' element selection. + var link = function() { + var curvature = .5; - //============================================================ - // Private Variables - //------------------------------------------------------------ + function link(d) { - var renderWatch = nv.utils.renderWatch(dispatch); + var x0 = d.source.x + d.source.dx, + x1 = d.target.x, + xi = d3.interpolateNumber(x0, x1), + x2 = xi(curvature), + x3 = xi(1 - curvature), + y0 = d.source.y + d.sy + d.dy / 2, + y1 = d.target.y + d.ty + d.dy / 2; + var linkPath = 'M' + x0 + ',' + y0 + + 'C' + x2 + ',' + y0 + + ' ' + x3 + ',' + y1 + + ' ' + x1 + ',' + y1; + return linkPath; + } - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) + link.curvature = function(_) { + if (!arguments.length) return curvature; + curvature = +_; + return link; }; + + return link; + }; + + // Y-position of the middle of a node. + var center = function(node) { + return node.y + node.dy / 2; + }; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + // Populate the sourceLinks and targetLinks for each node. + // Also, if the source and target are not objects, assume they are indices. + function computeNodeLinks() { + nodes.forEach(function(node) { + // Links that have this node as source. + node.sourceLinks = []; + // Links that have this node as target. + node.targetLinks = []; + }); + links.forEach(function(link) { + var source = link.source, + target = link.target; + if (typeof source === 'number') source = link.source = nodes[link.source]; + if (typeof target === 'number') target = link.target = nodes[link.target]; + source.sourceLinks.push(link); + target.targetLinks.push(link); + }); } - }; - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) { - data.forEach(function (series, i) { - series.disabled = !state.active[i]; + // Compute the value (size) of each node by summing the associated links. + function computeNodeValues() { + nodes.forEach(function(node) { + node.value = Math.max( + d3.sum(node.sourceLinks, value), + d3.sum(node.targetLinks, value) + ); + }); + } + + // Iteratively assign the breadth (x-position) for each node. + // Nodes are assigned the maximum breadth of incoming neighbors plus one; + // nodes with no incoming links are assigned breadth zero, while + // nodes with no outgoing links are assigned the maximum breadth. + function computeNodeBreadths() { + // + var remainingNodes = nodes, + nextNodes, + x = 0; + + // Work from left to right. + // Keep updating the breath (x-position) of nodes that are target of recently updated nodes. + // + while (remainingNodes.length && x < nodes.length) { + nextNodes = []; + remainingNodes.forEach(function(node) { + node.x = x; + node.dx = nodeWidth; + node.sourceLinks.forEach(function(link) { + if (nextNodes.indexOf(link.target) < 0) { + nextNodes.push(link.target); + } + }); }); + remainingNodes = nextNodes; + ++x; + // } + + // Optionally move pure sinks always to the right. + if (sinksRight) { + moveSinksRight(x); + } + + scaleNodeBreadths((size[0] - nodeWidth) / (x - 1)); } - }; - //============================================================ - // Chart function - //------------------------------------------------------------ + function moveSourcesRight() { + nodes.forEach(function(node) { + if (!node.targetLinks.length) { + node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1; + } + }); + } - function chart(selection) { - renderWatch.reset(); - renderWatch.models(pie); + function moveSinksRight(x) { + nodes.forEach(function(node) { + if (!node.sourceLinks.length) { + node.x = x - 1; + } + }); + } - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); + function scaleNodeBreadths(kx) { + nodes.forEach(function(node) { + node.x *= kx; + }); + } - var that = this; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + // Compute the depth (y-position) for each node. + function computeNodeDepths(iterations) { + // Group nodes by breath. + var nodesByBreadth = d3.nest() + .key(function(d) { return d.x; }) + .sortKeys(d3.ascending) + .entries(nodes) + .map(function(d) { return d.values; }); - chart.update = function() { container.transition().call(chart); }; - chart.container = this; + // + initializeNodeDepth(); + resolveCollisions(); + computeLinkDepths(); + for (var alpha = 1; iterations > 0; --iterations) { + relaxRightToLeft(alpha *= .99); + resolveCollisions(); + computeLinkDepths(); + relaxLeftToRight(alpha); + resolveCollisions(); + computeLinkDepths(); + } - state.setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); + function initializeNodeDepth() { + // Calculate vertical scaling factor. + var ky = d3.min(nodesByBreadth, function(nodes) { + return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value); + }); - //set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); + nodesByBreadth.forEach(function(nodes) { + nodes.forEach(function(node, i) { + node.y = i; + node.dy = node.value * ky; + }); + }); - 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]; - } + links.forEach(function(link) { + link.dy = link.value * ky; + }); } - // 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(); + function relaxLeftToRight(alpha) { + nodesByBreadth.forEach(function(nodes, breadth) { + nodes.forEach(function(node) { + if (node.targetLinks.length) { + // Value-weighted average of the y-position of source node centers linked to this node. + var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); + + function weightedSource(link) { + return (link.source.y + link.sy + link.dy / 2) * link.value; + } } - // 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'); + function relaxRightToLeft(alpha) { + nodesByBreadth.slice().reverse().forEach(function(nodes) { + nodes.forEach(function(node) { + if (node.sourceLinks.length) { + // Value-weighted average of the y-positions of target nodes linked to this node. + var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); - gEnter.append('g').attr('class', 'nv-pieWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); + function weightedTarget(link) { + return (link.target.y + link.ty + link.dy / 2) * link.value; + } + } - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - if (legendPosition === "top") { - legend.width( availableWidth ).key(pie.x()); + function resolveCollisions() { + nodesByBreadth.forEach(function(nodes) { + var node, + dy, + y0 = 0, + n = nodes.length, + i; - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + // Push any overlapping nodes down. + nodes.sort(ascendingDepth); + for (i = 0; i < n; ++i) { + node = nodes[i]; + dy = y0 - node.y; + if (dy > 0) node.y += dy; + y0 = node.y + node.dy + nodePadding; } - 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) + // If the bottommost node goes outside the bounds, push it back up. + dy = y0 - nodePadding - size[1]; + if (dy > 0) { + y0 = node.y -= dy; + + // Push any overlapping nodes back up. + for (i = n - 2; i >= 0; --i) { + node = nodes[i]; + dy = node.y + node.dy + nodePadding - y0; + if (dy > 0) node.y -= dy; + y0 = node.y; + } } - 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)'); - } + function ascendingDepth(a, b) { + return a.y - b.y; } - 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); + // Compute y-offset of the source endpoint (sy) and target endpoints (ty) of links, + // relative to the source/target node's y-position. + function computeLinkDepths() { + nodes.forEach(function(node) { + node.sourceLinks.sort(ascendingTargetDepth); + node.targetLinks.sort(ascendingSourceDepth); + }); + nodes.forEach(function(node) { + var sy = 0, ty = 0; + node.sourceLinks.forEach(function(link) { + link.sy = sy; + sy += link.dy; + }); + node.targetLinks.forEach(function(link) { + link.ty = ty; + ty += link.dy; + }); + }); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + function ascendingSourceDepth(a, b) { + return a.source.y - b.source.y; + } - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) { - state[key] = newState[key]; - } - dispatch.stateChange(state); - chart.update(); - }); + function ascendingTargetDepth(a, b) { + return a.target.y - b.target.y; + } + } - // 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; + // Value property accessor. + function value(x) { + return x.value; + } + + sankey.options = nv.utils.optionsFunc.bind(sankey); + sankey._options = Object.create({}, { + nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=+_;}}, + nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}}, + nodes: {get: function(){return nodes;}, set: function(_){nodes=_;}}, + links: {get: function(){return links ;}, set: function(_){links=_;}}, + size: {get: function(){return size;}, set: function(_){size=_;}}, + sinksRight: {get: function(){return sinksRight;}, set: function(_){sinksRight=_;}}, + + layout: {get: function(){layout(32);}, set: function(_){layout(_);}}, + relayout: {get: function(){relayout();}, set: function(_){}}, + center: {get: function(){return center();}, set: function(_){ + if(typeof _ === 'function'){ + center=_; } - chart.update(); - }); + }}, + link: {get: function(){return link();}, set: function(_){ + if(typeof _ === 'function'){ + link=_; + } + return link(); + }} }); - renderWatch.renderEnd('pieChart immediate'); - return chart; - } + nv.utils.initOptions(sankey); - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + return sankey; + }; + nv.models.sankeyChart = function() { + "use strict"; - pie.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: chart.x()(evt.data), - value: chart.y()(evt.data), - color: evt.color, - percent: evt.percent + // Sources: + // - https://bost.ocks.org/mike/sankey/ + // - https://github.com/soxofaan/d3-plugin-captain-sankey + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , sankey = nv.models.sankey() + , width = 600 + , height = 400 + , nodeWidth = 36 + , nodePadding = 40 + , units = 'units' + , center = undefined + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var formatNumber = d3.format(',.0f'); // zero decimal places + var format = function(d) { + return formatNumber(d) + ' ' + units; }; - if (!showTooltipPercent) { - delete evt.percent; - delete evt.series.percent; + var color = d3.scale.category20(); + var linkTitle = function(d){ + return d.source.name + ' → ' + d.target.name + '\n' + format(d.value); + }; + var nodeFillColor = function(d){ + return d.color = color(d.name.replace(/ .*/, '')); + }; + var nodeStrokeColor = function(d){ + return d3.rgb(d.color).darker(2); + }; + var nodeTitle = function(d){ + return d.name + '\n' + format(d.value); + }; + + var showError = function(element, message) { + element.append('text') + .attr('x', 0) + .attr('y', 0) + .attr('class', 'nvd3-sankey-chart-error') + .attr('text-anchor', 'middle') + .text(message); + }; + + function chart(selection) { + selection.each(function(data) { + + var testData = { + nodes: + [ + {'node': 1, 'name': 'Test 1'}, + {'node': 2, 'name': 'Test 2'}, + {'node': 3, 'name': 'Test 3'}, + {'node': 4, 'name': 'Test 4'}, + {'node': 5, 'name': 'Test 5'}, + {'node': 6, 'name': 'Test 6'} + ], + links: + [ + {'source': 0, 'target': 1, 'value': 2295}, + {'source': 0, 'target': 5, 'value': 1199}, + {'source': 1, 'target': 2, 'value': 1119}, + {'source': 1, 'target': 5, 'value': 1176}, + {'source': 2, 'target': 3, 'value': 487}, + {'source': 2, 'target': 5, 'value': 632}, + {'source': 3, 'target': 4, 'value': 301}, + {'source': 3, 'target': 5, 'value': 186} + ] + }; + + // Error handling + var isDataValid = false; + var dataAvailable = false; + + // check if data is valid + if( + (typeof data['nodes'] === 'object' && data['nodes'].length) >= 0 && + (typeof data['links'] === 'object' && data['links'].length) >= 0 + ){ + isDataValid = true; + } + + // check if data is available + if( + data['nodes'] && data['nodes'].length > 0 && + data['links'] && data['links'].length > 0 + ) { + dataAvailable = true; + } + + // show error + if(!isDataValid) { + console.error('NVD3 Sankey chart error:', 'invalid data format for', data); + console.info('Valid data format is: ', testData, JSON.stringify(testData)); + showError(selection, 'Error loading chart, data is invalid'); + return false; + } + + // TODO use nv.utils.noData + if(!dataAvailable) { + showError(selection, 'No data available'); + return false; + } + + // No errors, continue + + // append the svg canvas to the page + var svg = selection.append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('class', 'nvd3 nv-wrap nv-sankeyChart'); + + // Set the sankey diagram properties + sankey + .nodeWidth(nodeWidth) + .nodePadding(nodePadding) + .size([width, height]); + + var path = sankey.link(); + + sankey + .nodes(data.nodes) + .links(data.links) + .layout(32) + .center(center); + + // add in the links + var link = svg.append('g').selectAll('.link') + .data(data.links) + .enter().append('path') + .attr('class', 'link') + .attr('d', path) + .style('stroke-width', function(d) { return Math.max(1, d.dy); }) + .sort(function(a,b) { return b.dy - a.dy; }); + + // add the link titles + link.append('title') + .text(linkTitle); + + // add in the nodes + var node = svg.append('g').selectAll('.node') + .data(data.nodes) + .enter().append('g') + .attr('class', 'node') + .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; }) + .call( + d3.behavior + .drag() + .origin(function(d) { return d; }) + .on('dragstart', function() { + this.parentNode.appendChild(this); + }) + .on('drag', dragmove) + ); + + // add the rectangles for the nodes + node.append('rect') + .attr('height', function(d) { return d.dy; }) + .attr('width', sankey.nodeWidth()) + .style('fill', nodeFillColor) + .style('stroke', nodeStrokeColor) + .append('title') + .text(nodeTitle); + + // add in the title for the nodes + node.append('text') + .attr('x', -6) + .attr('y', function(d) { return d.dy / 2; }) + .attr('dy', '.35em') + .attr('text-anchor', 'end') + .attr('transform', null) + .text(function(d) { return d.name; }) + .filter(function(d) { return d.x < width / 2; }) + .attr('x', 6 + sankey.nodeWidth()) + .attr('text-anchor', 'start'); + + // the function for moving the nodes + function dragmove(d) { + d3.select(this).attr('transform', + 'translate(' + d.x + ',' + ( + d.y = Math.max(0, Math.min(height - d.dy, d3.event.y)) + ) + ')'); + sankey.relayout(); + link.attr('d', path); + } + }); + + return chart; } - tooltip.data(evt).hidden(false); - }); - pie.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - pie.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + chart.options = nv.utils.optionsFunc.bind(chart); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + units: {get: function(){return units;}, set: function(_){units=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + format: {get: function(){return format;}, set: function(_){format=_;}}, + linkTitle: {get: function(){return linkTitle;}, set: function(_){linkTitle=_;}}, + nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=_;}}, + nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}}, + center: {get: function(){return center}, set: function(_){center=_}}, - // expose chart's sub-components - chart.legend = legend; - chart.dispatch = dispatch; - chart.pie = pie; - chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); + // 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; + }}, + nodeStyle: {get: function(){return {};}, set: function(_){ + nodeFillColor = _.fillColor !== undefined ? _.fillColor : nodeFillColor; + nodeStrokeColor = _.strokeColor !== undefined ? _.strokeColor : nodeStrokeColor; + nodeTitle = _.title !== undefined ? _.title : nodeTitle; + }} - // 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=_;}}, - showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, - 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); - pie.duration(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; -}; + nv.utils.initOptions(chart); -nv.models.scatter = function() { - "use strict"; + return chart; + }; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + nv.models.scatter = function() { + "use strict"; - 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 - ; + //============================================================ + // 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 + , pointBorderColor = null + , 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 - ; + //============================================================ + // Private Variables + //------------------------------------------------------------ - 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; - } + var x0, y0, z0 // used to store previous scales + , width0 + , height0 + , 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] + , _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; + function getCache(d) { + var key, val; + key = d[0].series + ':' + d[1]; + val = _cache[key] = _cache[key] || {}; + return val; + } + + function delCache(d) { + var key, val; + key = d[0].series + ':' + d[1]; + delete _cache[key]; + } + + function getDiffs(d) { + var i, key, val, + cache = getCache(d), + diffs = false; + for (i = 1; i < arguments.length; i += 2) { + key = arguments[i]; + val = arguments[i + 1](d[0], d[1]); + if (cache[key] !== val || !cache.hasOwnProperty(key)) { + cache[key] = val; + diffs = true; + } } + return diffs; } - 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); + 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); + 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; + //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) } + // 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))) + 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 (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) { + 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))) + 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))) + 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); + 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 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 (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 (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(x.domain()[0])) { + x.domain([-1,1]); + } - if ( isNaN(y.domain()[0])) { - y.domain([-1,1]); - } + if ( isNaN(y.domain()[0])) { + y.domain([-1,1]); + } - x0 = x0 || x; - y0 = y0 || y; - z0 = z0 || z; + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; - var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1); + 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'); + width0 = width0 || width; + height0 = height0 || height; - 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'); + var sizeDiff = width0 !== width || height0 !== height; - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // 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'); - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); + 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.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', (availableHeight > 0) ? availableHeight : 0); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect') + .attr('transform', 'translate( -10, -10)'); - function updateInteractiveLayer() { - // Always clear needs-update flag regardless of whether or not - // we will actually do anything (avoids needless invocations). - needsUpdate = false; + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth + 20) + .attr('height', (availableHeight > 0) ? availableHeight + 20 : 0); - if (!interactive) return false; + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - // 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 - /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. - */ - var pX = getX(point,pointIndex); - var pY = getY(point,pointIndex); + function updateInteractiveLayer() { + // Always clear needs-update flag regardless of whether or not + // we will actually do anything (avoids needless invocations). + needsUpdate = false; - 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 (!interactive) return false; - 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]); - } + // 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 + /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. + */ + var pX = getX(point,pointIndex); + var pY = getY(point,pointIndex); - // 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] - ]); + 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! + }) + }) + ); - var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { - return { - 'data': bounds.clip(d), - 'series': vertices[i][2], - 'point': vertices[i][3] + 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]); } - }); - // 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+")"; }) - ; + // 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] + ]); - // 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)); - } + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); - 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); - } + // 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+")"; }) + ; - 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); + // 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)); + } - // standardize attributes for tooltip. - point['x'] = getX(point); - point['y'] = getY(point); + 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); + } - // 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 mouseEventCallback = function(el, 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); - 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 - }; + // standardize attributes for tooltip. + point['x'] = getX(point); + point['y'] = getY(point); - 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 - }); - }; + // 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; - 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); - }); + 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 + }; - } 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]; - var element = this; - dispatch.elementClick({ + mDispatch({ 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], + pos: pos, + relativePos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], seriesIndex: d.series, - pointIndex: i, + pointIndex: d.point, event: d3.event, - element: element + element: el }); - }) - .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 + pointPaths + .on('click', function(d) { + mouseEventCallback(this, d, dispatch.elementClick); + }) + .on('dblclick', function(d) { + mouseEventCallback(this, d, dispatch.elementDblClick); + }) + .on('mouseover', function(d) { + mouseEventCallback(this, d, dispatch.elementMouseover); + }) + .on('mouseout', function(d, i) { + mouseEventCallback(this, d, dispatch.elementMouseout); }); - }) - .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]; + } 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]; + var element = this; + 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, + event: d3.event, + element: element + }); + }) + .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.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) + 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); + 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 d.pointBorderColor || pointBorderColor || 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') + // 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) - }) - }); - - titles.enter().append('text') - .style('fill', function (d,i) { - return d.color }) - .style('stroke-opacity', 0) - .style('fill-opacity', 1) + 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) { - 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]))) + ')'; + return 'translate(' + nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + ',' + 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') + .attr('d', + nv.utils.symbol() + .type(function(d) { return getShape(d[0]); }) + .size(function(d) { return z(getSize(d[0],d[1])) }) + ); + points.exit().each(delCache).remove(); + groups.exit().selectAll('path.nv-point') .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]))) + ')'; + return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + 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') + // Update points position only if "x" or "y" have changed + points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'x', getX, 'y', getY); }) + .watchTransition(renderWatch, 'scatter points') .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]))) + ')' + //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]))) + ')' }); - } + // Update points appearance only if "shape" or "size" have changed + points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'shape', getShape, 'size', getSize); }) + .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])) }) + ); - // Delay updating the invisible interactive layer for smoother animation - if( interactiveUpdateDelay ) - { - clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer - timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay ); - } - else - { - updateInteractiveLayer(); - } + // 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) + }) + }); - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - z0 = z.copy(); + 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;}); - }); - renderWatch.renderEnd('scatter immediate'); - return chart; - } + 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]))) + ')' + }); + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + // Delay updating the invisible interactive layer for smoother animation + if( interactiveUpdateDelay ) + { + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay ); + } + else + { + updateInteractiveLayer(); + } - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + z0 = z.copy(); - // 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); + width0 = width; + height0 = height; + }); - return null; + 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); + }); + }; }; - 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); - }); + // 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); - }); + 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 = _;}}, + 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 = _;}}, + pointBorderColor: {get: function(){return pointBorderColor;}, set: function(_){pointBorderColor=_;}}, - // 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(_);}}, + // 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; - } - }} - }); + // 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.utils.initOptions(chart); + return chart; + }; -nv.models.scatterChart = function() { - "use strict"; + nv.models.scatterChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var scatter = nv.models.scatter() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , distX = nv.models.distribution() - , distY = nv.models.distribution() - , tooltip = nv.models.tooltip() - ; + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + , tooltip = nv.models.tooltip() + ; - var margin = {top: 30, right: 20, bottom: 50, left: 75} - , width = null - , height = null - , container = null - , color = nv.utils.defaultColor() - , x = scatter.xScale() - , y = scatter.yScale() - , showDistX = false - , showDistY = false - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , state = nv.utils.state() - , defaultState = null - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') - , noData = null - , duration = 250 - , showLabels = false + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , marginTop = null + , width = null + , height = null + , container = null + , color = nv.utils.defaultColor() + , x = scatter.xScale() + , y = scatter.yScale() + , showDistX = false + , showDistY = false + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , state = nv.utils.state() + , defaultState = null + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') + , noData = null + , duration = 250 + , showLabels = false + ; + + scatter.xScale(x).yScale(y); + xAxis.orient('bottom').tickPadding(10); + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickPadding(10) ; + distX.axis('x'); + distY.axis('y'); + tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }); - scatter.xScale(x).yScale(y); - xAxis.orient('bottom').tickPadding(10); - yAxis - .orient((rightAlignYAxis) ? 'right' : 'left') - .tickPadding(10) - ; - distX.axis('x'); - distY.axis('y'); - tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }); + //============================================================ + // Private Variables + //------------------------------------------------------------ - //============================================================ - // Private Variables - //------------------------------------------------------------ + var x0, y0 + , renderWatch = nv.utils.renderWatch(dispatch, duration); - var x0, y0 - , renderWatch = nv.utils.renderWatch(dispatch, duration); + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; - 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]; + }); + } + }; - 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(scatter); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + if (showDistX) renderWatch.models(distX); + if (showDistY) renderWatch.models(distY); - function chart(selection) { - renderWatch.reset(); - renderWatch.models(scatter); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - if (showDistX) renderWatch.models(distX); - if (showDistY) renderWatch.models(distY); + selection.each(function(data) { + var that = this; - selection.each(function(data) { - var that = this; + container = d3.select(this); + nv.utils.initSVG(container); - container = d3.select(this); - nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); - 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; - 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(); - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); - // 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]; + } + } - 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); + renderWatch.renderEnd('scatter immediate'); + return chart; + } else { + container.selectAll('.nv-noData').remove(); } - } - // 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); - renderWatch.renderEnd('scatter immediate'); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); - // background for pointer events - gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); - gEnter.append('g').attr('class', 'nv-distWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + var legendWidth = availableWidth; + legend.width(legendWidth); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - var legendWidth = availableWidth; - legend.width(legendWidth); + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend); + if (!marginTop && legend.height() !== margin.top) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0' + ',' + (-margin.top) +')'); } - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0' + ',' + (-margin.top) +')'); - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // Main Chart Component(s) + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + d.color = d.color || color(d, i); + return d.color; + }).filter(function(d,i) { return !data[i].disabled })) + .showLabels(showLabels); - // Main Chart Component(s) - scatter - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - d.color = d.color || color(d, i); - return d.color; - }).filter(function(d,i) { return !data[i].disabled })) - .showLabels(showLabels); + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); - wrap.select('.nv-scatterWrap') - .datum(data.filter(function(d) { return !d.disabled })) - .call(scatter); + wrap.select('.nv-regressionLinesWrap') + .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); - wrap.select('.nv-regressionLinesWrap') - .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); + var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') + .data(function (d) { + return d; + }); - var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') - .data(function (d) { - return d; - }); + regWrap.enter().append('g').attr('class', 'nv-regLines'); - regWrap.enter().append('g').attr('class', 'nv-regLines'); + var regLine = regWrap.selectAll('.nv-regLine') + .data(function (d) { + return [d] + }); - var regLine = regWrap.selectAll('.nv-regLine') - .data(function (d) { - return [d] - }); + regLine.enter() + .append('line').attr('class', 'nv-regLine') + .style('stroke-opacity', 0); - regLine.enter() - .append('line').attr('class', 'nv-regLine') - .style('stroke-opacity', 0); - - // don't add lines unless we have slope and intercept to use - regLine.filter(function(d) { - return d.intercept && d.slope; - }) - .watchTransition(renderWatch, 'scatterPlusLineChart: regline') - .attr('x1', x.range()[0]) - .attr('x2', x.range()[1]) - .attr('y1', function (d, i) { - return y(x.domain()[0] * d.slope + d.intercept) + // don't add lines unless we have slope and intercept to use + regLine.filter(function(d) { + return d.intercept && d.slope; }) - .attr('y2', function (d, i) { - return y(x.domain()[1] * d.slope + d.intercept) - }) - .style('stroke', function (d, i, j) { - return color(d, j) - }) - .style('stroke-opacity', function (d, i) { - return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 - }); + .watchTransition(renderWatch, 'scatterPlusLineChart: regline') + .attr('x1', x.range()[0]) + .attr('x2', x.range()[1]) + .attr('y1', function (d, i) { + return y(x.domain()[0] * d.slope + d.intercept) + }) + .attr('y2', function (d, i) { + return y(x.domain()[1] * d.slope + d.intercept) + }) + .style('stroke', function (d, i, j) { + return color(d, j) + }) + .style('stroke-opacity', function (d, i) { + return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 + }); - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight , 0); + // 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] + ')') - .call(xAxis); - } + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + } - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); - g.select('.nv-y.nv-axis') - .call(yAxis); - } + g.select('.nv-y.nv-axis') + .call(yAxis); + } - // Setup Distribution - if (showDistX) { - distX - .getData(scatter.x()) - .scale(x) - .width(availableWidth) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionX'); - g.select('.nv-distributionX') - .attr('transform', 'translate(0,' + y.range()[0] + ')') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distX); - } + // Setup Distribution + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } - if (showDistY) { - distY - .getData(scatter.y()) - .scale(y) - .width(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionY'); - g.select('.nv-distributionY') - .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distY); - } + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // 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(); - }); + 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(); - }); + // 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(); + }); - // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block - scatter.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) - .attr('y1', 0); - container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) - .attr('x2', distY.size()); - }); + // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block + scatter.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', 0); + container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', distY.size()); + }); - scatter.dispatch.on('elementMouseover.tooltip', function(evt) { - container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) - .attr('y1', evt.relativePos[1] - availableHeight); - container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) - .attr('x2', evt.relativePos[0] + distX.size()); - tooltip.data(evt).hidden(false); - }); + scatter.dispatch.on('elementMouseover.tooltip', function(evt) { + container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', evt.relativePos[1] - availableHeight); + container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', evt.relativePos[0] + distX.size()); + tooltip.data(evt).hidden(false); + }); - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); - }); + }); - renderWatch.renderEnd('scatter with line immediate'); - return chart; - } + renderWatch.renderEnd('scatter with line immediate'); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - // expose chart's sub-components - chart.dispatch = dispatch; - chart.scatter = scatter; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.distX = distX; - chart.distY = distY; - chart.tooltip = tooltip; + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + 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=_;}}, - container: {get: function(){return container;}, set: function(_){container=_;}}, - showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}}, - showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - 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=_;}}, - duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, + 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=_;}}, + container: {get: function(){return container;}, set: function(_){container=_;}}, + showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}}, + showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + 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=_;}}, + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, - // 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; - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - distX.color(color); - distY.color(color); - }} - }); + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.top; + } + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + }} + }); - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); - return chart; -}; + nv.utils.inheritOptions(chart, scatter); + nv.utils.initOptions(chart); + return chart; + }; -nv.models.sparkline = function() { - "use strict"; + nv.models.sparkline = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var margin = {top: 2, right: 0, bottom: 2, left: 0} - , width = 400 - , height = 32 - , container = null - , animate = true - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.getColor(['#000']) - , xDomain - , yDomain - , xRange - , yRange - , showMinMaxPoints = true - , showCurrentPoint = true - , dispatch = d3.dispatch('renderEnd') - ; + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , container = null + , animate = true + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.getColor(['#000']) + , xDomain + , yDomain + , xRange + , yRange + , showMinMaxPoints = true + , showCurrentPoint = true + , dispatch = d3.dispatch('renderEnd') + ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); + 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; + 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); + container = d3.select(this); + nv.utils.initSVG(container); - // Setup Scales - x .domain(xDomain || d3.extent(data, getX )) - .range(xRange || [0, availableWidth]); + // Setup Scales + x .domain(xDomain || d3.extent(data, getX )) + .range(xRange || [0, availableWidth]); - y .domain(yDomain || d3.extent(data, getY )) - .range(yRange || [availableHeight, 0]); + y .domain(yDomain || d3.extent(data, getY )) + .range(yRange || [availableHeight, 0]); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - var paths = wrap.selectAll('path') - .data(function(d) { return [d] }); - paths.enter().append('path'); - paths.exit().remove(); - paths - .style('stroke', function(d,i) { return d.color || color(d, i) }) - .attr('d', d3.svg.line() - .x(function(d,i) { return x(getX(d,i)) }) - .y(function(d,i) { return y(getY(d,i)) }) - ); + var paths = wrap.selectAll('path') + .data(function(d) { return [d] }); + paths.enter().append('path'); + paths.exit().remove(); + paths + .style('stroke', function(d,i) { return d.color || color(d, i) }) + .attr('d', d3.svg.line() + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); - // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) - var points = wrap.selectAll('circle.nv-point') - .data(function(data) { - var yValues = data.map(function(d, i) { return getY(d,i); }); - function pointIndex(index) { - if (index != -1) { - var result = data[index]; - result.pointIndex = index; - return result; - } else { - return null; + // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) + var points = wrap.selectAll('circle.nv-point') + .data(function(data) { + var yValues = data.map(function(d, i) { return getY(d,i); }); + function pointIndex(index) { + if (index != -1) { + var result = data[index]; + result.pointIndex = index; + return result; + } else { + return null; + } } - } - var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), - minPoint = pointIndex(yValues.indexOf(y.domain()[0])), - currentPoint = pointIndex(yValues.length - 1); - return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;}); - }); - points.enter().append('circle'); - points.exit().remove(); - points - .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) - .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) - .attr('r', 2) - .attr('class', function(d,i) { - return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : + var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), + minPoint = pointIndex(yValues.indexOf(y.domain()[0])), + currentPoint = pointIndex(yValues.length - 1); + return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;}); + }); + points.enter().append('circle'); + points.exit().remove(); + points + .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) + .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) + .attr('r', 2) + .attr('class', function(d,i) { + return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' - }); - }); + }); + }); - renderWatch.renderEnd('sparkline immediate'); - return chart; - } + renderWatch.renderEnd('sparkline immediate'); + return chart; + } - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.options = nv.utils.optionsFunc.bind(chart); + 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=_;}}, - 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=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - animate: {get: function(){return animate;}, set: function(_){animate=_;}}, - showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}}, - showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}}, + 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=_;}}, + 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=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + animate: {get: function(){return animate;}, set: function(_){animate=_;}}, + showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}}, + showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}}, - //functor options - x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, + //functor options + x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY=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; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); + // 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(_); + }} + }); - chart.dispatch = dispatch; - nv.utils.initOptions(chart); - return chart; -}; + chart.dispatch = dispatch; + nv.utils.initOptions(chart); + return chart; + }; -nv.models.sparklinePlus = function() { - "use strict"; + nv.models.sparklinePlus = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var sparkline = nv.models.sparkline(); + var sparkline = nv.models.sparkline(); - var margin = {top: 15, right: 100, bottom: 10, left: 50} - , width = null - , height = null - , x - , y - , index = [] - , paused = false - , xTickFormat = d3.format(',r') - , yTickFormat = d3.format(',.2f') - , showLastValue = true - , alignValue = true - , rightAlignValue = false - , noData = null - , dispatch = d3.dispatch('renderEnd') - ; + var margin = {top: 15, right: 100, bottom: 10, left: 50} + , width = null + , height = null + , x + , y + , index = [] + , paused = false + , xTickFormat = d3.format(',r') + , yTickFormat = d3.format(',.2f') + , showLastValue = true + , alignValue = true + , rightAlignValue = false + , noData = null + , dispatch = d3.dispatch('renderEnd') + ; - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); + var renderWatch = nv.utils.renderWatch(dispatch); - function chart(selection) { - renderWatch.reset(); - renderWatch.models(sparkline); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(sparkline); + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); - chart.update = function() { container.call(chart); }; - chart.container = this; + chart.update = function() { container.call(chart); }; + chart.container = this; - // 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(); - } + // 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(); + } - var currentValue = sparkline.y()(data[data.length-1], data.length-1); + var currentValue = sparkline.y()(data[data.length-1], data.length-1); - // Setup Scales - x = sparkline.xScale(); - y = sparkline.yScale(); + // Setup Scales + x = sparkline.xScale(); + y = sparkline.yScale(); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-sparklineWrap'); - gEnter.append('g').attr('class', 'nv-valueWrap'); - gEnter.append('g').attr('class', 'nv-hoverArea'); + gEnter.append('g').attr('class', 'nv-sparklineWrap'); + gEnter.append('g').attr('class', 'nv-valueWrap'); + gEnter.append('g').attr('class', 'nv-hoverArea'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // Main Chart Component(s) - var sparklineWrap = g.select('.nv-sparklineWrap'); + // Main Chart Component(s) + var sparklineWrap = g.select('.nv-sparklineWrap'); - sparkline.width(availableWidth).height(availableHeight); - sparklineWrap.call(sparkline); + sparkline.width(availableWidth).height(availableHeight); + sparklineWrap.call(sparkline); - if (showLastValue) { - var valueWrap = g.select('.nv-valueWrap'); - var value = valueWrap.selectAll('.nv-currentValue') - .data([currentValue]); + if (showLastValue) { + var valueWrap = g.select('.nv-valueWrap'); + var value = valueWrap.selectAll('.nv-currentValue') + .data([currentValue]); - value.enter().append('text').attr('class', 'nv-currentValue') - .attr('dx', rightAlignValue ? -8 : 8) - .attr('dy', '.9em') - .style('text-anchor', rightAlignValue ? 'end' : 'start'); + value.enter().append('text').attr('class', 'nv-currentValue') + .attr('dx', rightAlignValue ? -8 : 8) + .attr('dy', '.9em') + .style('text-anchor', rightAlignValue ? 'end' : 'start'); - value - .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) - .attr('y', alignValue ? function (d) { - return y(d) - } : 0) - .style('fill', sparkline.color()(data[data.length - 1], data.length - 1)) - .text(yTickFormat(currentValue)); - } + value + .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) + .attr('y', alignValue ? function (d) { + return y(d) + } : 0) + .style('fill', sparkline.color()(data[data.length - 1], data.length - 1)) + .text(yTickFormat(currentValue)); + } - gEnter.select('.nv-hoverArea').append('rect') - .on('mousemove', sparklineHover) - .on('click', function() { paused = !paused }) - .on('mouseout', function() { index = []; updateValueLine(); }); + gEnter.select('.nv-hoverArea').append('rect') + .on('mousemove', sparklineHover) + .on('click', function() { paused = !paused }) + .on('mouseout', function() { index = []; updateValueLine(); }); - g.select('.nv-hoverArea rect') - .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) - .attr('width', availableWidth + margin.left + margin.right) - .attr('height', availableHeight + margin.top); + g.select('.nv-hoverArea rect') + .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) + .attr('width', availableWidth + margin.left + margin.right) + .attr('height', availableHeight + margin.top); - //index is currently global (within the chart), may or may not keep it that way - function updateValueLine() { - if (paused) return; + //index is currently global (within the chart), may or may not keep it that way + function updateValueLine() { + if (paused) return; - var hoverValue = g.selectAll('.nv-hoverValue').data(index); + var hoverValue = g.selectAll('.nv-hoverValue').data(index); - var hoverEnter = hoverValue.enter() - .append('g').attr('class', 'nv-hoverValue') - .style('stroke-opacity', 0) - .style('fill-opacity', 0); + var hoverEnter = hoverValue.enter() + .append('g').attr('class', 'nv-hoverValue') + .style('stroke-opacity', 0) + .style('fill-opacity', 0); - hoverValue.exit() - .transition().duration(250) - .style('stroke-opacity', 0) - .style('fill-opacity', 0) - .remove(); + hoverValue.exit() + .transition().duration(250) + .style('stroke-opacity', 0) + .style('fill-opacity', 0) + .remove(); - hoverValue - .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) - .transition().duration(250) - .style('stroke-opacity', 1) - .style('fill-opacity', 1); + hoverValue + .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) + .transition().duration(250) + .style('stroke-opacity', 1) + .style('fill-opacity', 1); - if (!index.length) return; + if (!index.length) return; - hoverEnter.append('line') - .attr('x1', 0) - .attr('y1', -margin.top) - .attr('x2', 0) - .attr('y2', availableHeight); + hoverEnter.append('line') + .attr('x1', 0) + .attr('y1', -margin.top) + .attr('x2', 0) + .attr('y2', availableHeight); - hoverEnter.append('text').attr('class', 'nv-xValue') - .attr('x', -6) - .attr('y', -margin.top) - .attr('text-anchor', 'end') - .attr('dy', '.9em'); + hoverEnter.append('text').attr('class', 'nv-xValue') + .attr('x', -6) + .attr('y', -margin.top) + .attr('text-anchor', 'end') + .attr('dy', '.9em'); - g.select('.nv-hoverValue .nv-xValue') - .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); + g.select('.nv-hoverValue .nv-xValue') + .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); - hoverEnter.append('text').attr('class', 'nv-yValue') - .attr('x', 6) - .attr('y', -margin.top) - .attr('text-anchor', 'start') - .attr('dy', '.9em'); + hoverEnter.append('text').attr('class', 'nv-yValue') + .attr('x', 6) + .attr('y', -margin.top) + .attr('text-anchor', 'start') + .attr('dy', '.9em'); - g.select('.nv-hoverValue .nv-yValue') - .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); - } + g.select('.nv-hoverValue .nv-yValue') + .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); + } - function sparklineHover() { - if (paused) return; + function sparklineHover() { + if (paused) return; - var pos = d3.mouse(this)[0] - margin.left; + var pos = d3.mouse(this)[0] - margin.left; - function getClosestIndex(data, x) { - var distance = Math.abs(sparkline.x()(data[0], 0) - x); - var closestIndex = 0; - for (var i = 0; i < data.length; i++){ - if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { - distance = Math.abs(sparkline.x()(data[i], i) - x); - closestIndex = i; + function getClosestIndex(data, x) { + var distance = Math.abs(sparkline.x()(data[0], 0) - x); + var closestIndex = 0; + for (var i = 0; i < data.length; i++){ + if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { + distance = Math.abs(sparkline.x()(data[i], i) - x); + closestIndex = i; + } } + return closestIndex; } - return closestIndex; + + index = [getClosestIndex(data, Math.round(x.invert(pos)))]; + updateValueLine(); } - index = [getClosestIndex(data, Math.round(x.invert(pos)))]; - updateValueLine(); - } + }); + renderWatch.renderEnd('sparklinePlus immediate'); + return chart; + } - }); - renderWatch.renderEnd('sparklinePlus immediate'); - return chart; - } + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + // expose chart's sub-components + chart.dispatch = dispatch; + chart.sparkline = sparkline; - // expose chart's sub-components - chart.dispatch = dispatch; - chart.sparkline = sparkline; + chart.options = nv.utils.optionsFunc.bind(chart); - 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=_;}}, + xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}}, + yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}}, + showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}}, + alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}}, + rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - 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=_;}}, - xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}}, - yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}}, - showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}}, - alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}}, - rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}}, - 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; + }} + }); - // 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; - }} - }); + nv.utils.inheritOptions(chart, sparkline); + nv.utils.initOptions(chart); - nv.utils.inheritOptions(chart, sparkline); - nv.utils.initOptions(chart); + return chart; + }; - return chart; -}; + nv.models.stackedArea = function() { + "use strict"; -nv.models.stackedArea = function() { - "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , container = null + , 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 + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , 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() + , scatter = nv.models.scatter() + , duration = 250 + , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout') + ; - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , color = nv.utils.defaultColor() // a function that computes the color - , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one - , container = null - , 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 - , style = 'stack' - , offset = 'zero' - , order = 'default' - , interpolate = 'linear' // controls the line interpolation - , 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() - , scatter = nv.models.scatter() - , duration = 250 - , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout') + scatter + .pointSize(2.2) // default size + .pointDomain([2.2, 2.2]) // all the same size by default ; - scatter - .pointSize(2.2) // default size - .pointDomain([2.2, 2.2]) // all the same size by default - ; + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ - /************************************ - * offset: - * 'wiggle' (stream) - * 'zero' (stacked) - * 'expand' (normalize to 100%) - * 'silhouette' (simple centered) - * - * order: - * 'inside-out' (stream) - * 'default' (input order) - ************************************/ + var renderWatch = nv.utils.renderWatch(dispatch, duration); - var renderWatch = nv.utils.renderWatch(dispatch, duration); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(scatter); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; - function chart(selection) { - renderWatch.reset(); - renderWatch.models(scatter); - 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); - container = d3.select(this); - nv.utils.initSVG(container); + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); + var dataRaw = data; + // Injecting point index into each point because d3.layout.stack().out does not give index + data.forEach(function(aseries, i) { + aseries.seriesIndex = i; + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.seriesIndex = i; + return d; + }); + }); - var dataRaw = data; - // Injecting point index into each point because d3.layout.stack().out does not give index - data.forEach(function(aseries, i) { - aseries.seriesIndex = i; - aseries.values = aseries.values.map(function(d, j) { - d.index = j; - d.seriesIndex = i; - return d; + var dataFiltered = data.filter(function(series) { + return !series.disabled; }); - }); - var dataFiltered = data.filter(function(series) { - return !series.disabled; - }); + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(getY) + .out(function(d, y0, y) { + d.display = { + y: y, + y0: y0 + }; + }) + (dataFiltered); - data = d3.layout.stack() - .order(order) - .offset(offset) - .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion - .x(getX) - .y(getY) - .out(function(d, y0, y) { - d.display = { - y: y, - y0: y0 - }; - }) - (dataFiltered); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); - gEnter.append('g').attr('class', 'nv-areaWrap'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + // If the user has not specified forceY, make sure 0 is included in the domain + // Otherwise, use user-specified values for forceY + if (scatter.forceY().length == 0) { + scatter.forceY().push(0); + } - // If the user has not specified forceY, make sure 0 is included in the domain - // Otherwise, use user-specified values for forceY - if (scatter.forceY().length == 0) { - scatter.forceY().push(0); - } + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { + if (d.display !== undefined) { return d.display.y + d.display.y0; } + }) + .color(data.map(function(d,i) { + d.color = d.color || color(d, d.seriesIndex); + return d.color; + })); - scatter - .width(availableWidth) - .height(availableHeight) - .x(getX) - .y(function(d) { - if (d.display !== undefined) { return d.display.y + d.display.y0; } - }) - .color(data.map(function(d,i) { - d.color = d.color || color(d, d.seriesIndex); - return d.color; - })); + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data); - var scatterWrap = g.select('.nv-scatterWrap') - .datum(data); + scatterWrap.call(scatter); - scatterWrap.call(scatter); + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + var area = d3.svg.area() + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { + return y(d.display.y0) + }) + .y1(function(d) { + return y(d.display.y + d.display.y0) + }) + .interpolate(interpolate); - var area = d3.svg.area() - .defined(defined) - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { - return y(d.display.y0) - }) - .y1(function(d) { - return y(d.display.y + d.display.y0) - }) - .interpolate(interpolate); + var zeroArea = d3.svg.area() + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); - var zeroArea = d3.svg.area() - .defined(defined) - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { return y(d.display.y0) }) - .y1(function(d) { return y(d.display.y0) }); + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); - var path = g.select('.nv-areaWrap').selectAll('path.nv-area') - .data(function(d) { return d }); - - path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) - .attr('d', function(d,i){ - return zeroArea(d.values, d.seriesIndex); - }) - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.areaMouseover({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .attr('d', function(d,i){ + return zeroArea(d.values, d.seriesIndex); + }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaMouseout({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex - }); - }) - .on('click', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaClick({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex - }); - }); - path.exit().remove(); - path.style('fill', function(d,i){ + path.exit().remove(); + path.style('fill', function(d,i){ return d.color || color(d, d.seriesIndex) }) - .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); - path.watchTransition(renderWatch,'stackedArea path') - .attr('d', function(d,i) { - return area(d.values,i) - }); + .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); + path.watchTransition(renderWatch,'stackedArea path') + .attr('d', function(d,i) { + return area(d.values,i) + }); - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - scatter.dispatch.on('elementMouseover.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); - }); - scatter.dispatch.on('elementMouseout.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); - }); + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); + }); - //Special offset functions - chart.d3_stackedOffset_stackPercent = function(stackData) { - var n = stackData.length, //How many series - m = stackData[0].length, //how many points per series - i, - j, - o, - y0 = []; + //Special offset functions + chart.d3_stackedOffset_stackPercent = function(stackData) { + var n = stackData.length, //How many series + m = stackData[0].length, //how many points per series + i, + j, + o, + y0 = []; - for (j = 0; j < m; ++j) { //Looping through all points - for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series - o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time. - } + for (j = 0; j < m; ++j) { //Looping through all points + for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series + o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time. + } - if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0 - stackData[i][j][1] /= o; - } else { //(total y value of all series at point in time i) == 0 - for (i = 0; i < n; i++) { - stackData[i][j][1] = 0; + if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0 + stackData[i][j][1] /= o; + } else { //(total y value of all series at point in time i) == 0 + for (i = 0; i < n; i++) { + stackData[i][j][1] = 0; + } } } - } - for (j = 0; j < m; ++j) y0[j] = 0; - return y0; - }; + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }; - }); + }); - renderWatch.renderEnd('stackedArea immediate'); - return chart; - } + renderWatch.renderEnd('stackedArea immediate'); + return chart; + } - //============================================================ - // Global getters and setters - //------------------------------------------------------------ + //============================================================ + // Global getters and setters + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.scatter = scatter; + chart.dispatch = dispatch; + chart.scatter = scatter; - 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); }); + 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.interpolate = function(_) { - if (!arguments.length) return interpolate; - interpolate = _; - return chart; - }; + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; - chart.duration = function(_) { - if (!arguments.length) return duration; - duration = _; - renderWatch.reset(duration); - scatter.duration(duration); - return chart; - }; + chart.duration = function(_) { + if (!arguments.length) return duration; + duration = _; + renderWatch.reset(duration); + scatter.duration(duration); + return chart; + }; - chart.dispatch = dispatch; - chart.scatter = scatter; - chart.options = nv.utils.optionsFunc.bind(chart); + chart.dispatch = dispatch; + chart.scatter = scatter; + 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=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - offset: {get: function(){return offset;}, set: function(_){offset=_;}}, - order: {get: function(){return order;}, set: function(_){order=_;}}, - interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, + 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=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + offset: {get: function(){return offset;}, set: function(_){offset=_;}}, + order: {get: function(){return order;}, set: function(_){order=_;}}, + interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, - // simple functor options - x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, + // simple functor options + x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY = 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; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - style: {get: function(){return style;}, set: function(_){ - style = _; - switch (style) { - case 'stack': - chart.offset('zero'); - chart.order('default'); - break; - case 'stream': - chart.offset('wiggle'); - chart.order('inside-out'); - break; - case 'stream-center': - chart.offset('silhouette'); - chart.order('inside-out'); - break; - case 'expand': - chart.offset('expand'); - chart.order('default'); - break; - case 'stack_percent': - chart.offset(chart.d3_stackedOffset_stackPercent); - chart.order('default'); - break; - } - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - scatter.duration(duration); - }} - }); + // 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(_); + }}, + style: {get: function(){return style;}, set: function(_){ + style = _; + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + case 'stack_percent': + chart.offset(chart.d3_stackedOffset_stackPercent); + chart.order('default'); + break; + } + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + scatter.duration(duration); + }} + }); - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); + nv.utils.inheritOptions(chart, scatter); + nv.utils.initOptions(chart); - return chart; -}; + return chart; + }; -nv.models.stackedAreaChart = function() { - "use strict"; + nv.models.stackedAreaChart = function() { + "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - var stacked = nv.models.stackedArea() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - , focus = nv.models.focus(nv.models.stackedArea()) - ; + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + , focus = nv.models.focus(nv.models.stackedArea()) + ; - var margin = {top: 30, right: 25, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.defaultColor() - , showControls = true - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , focusEnable = false - , useInteractiveGuideline = false - , showTotalInTooltip = true - , totalLabel = 'TOTAL' - , 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 = 250 - , controlOptions = ['Stacked','Stream','Expanded'] - , controlLabels = {} - , duration = 250 - ; + var margin = {top: 10, right: 25, bottom: 50, left: 60} + , marginTop = null + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , legendPosition = 'top' + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , focusEnable = false + , useInteractiveGuideline = false + , showTotalInTooltip = true + , totalLabel = 'TOTAL' + , 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 = 250 + , controlOptions = ['Stacked','Stream','Expanded'] + , controlLabels = {} + , duration = 250 + ; - state.style = stacked.style(); - xAxis.orient('bottom').tickPadding(7); - yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); + state.style = stacked.style(); + xAxis.orient('bottom').tickPadding(7); + yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); - tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }); + tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }); - interactiveLayer.tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return d == null ? "N/A" : yAxis.tickFormat()(d, i); - }); + interactiveLayer.tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return d == null ? "N/A" : yAxis.tickFormat()(d, i); + }); - var oldYTickFormat = null, - oldValueFormatter = null; + var oldYTickFormat = null, + oldValueFormatter = null; - controls.updateState(false); + controls.updateState(false); - //============================================================ - // Private Variables - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); - var style = stacked.style(); + var renderWatch = nv.utils.renderWatch(dispatch); + var style = stacked.style(); - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - style: stacked.style() - }; - } - }; + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + style: stacked.style() + }; + } + }; - var stateSetter = function(data) { - return function(state) { - if (state.style !== undefined) - style = state.style; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; + var stateSetter = function(data) { + return function(state) { + if (state.style !== undefined) + style = state.style; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; - var percentFormatter = d3.format('%'); + var percentFormatter = d3.format('%'); - function chart(selection) { - renderWatch.reset(); - renderWatch.models(stacked); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); + function chart(selection) { + renderWatch.reset(); + renderWatch.models(stacked); + 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); + 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) - (focusEnable ? focus.height() : 0); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); - chart.update = function() { container.transition().duration(duration).call(chart); }; - chart.container = this; + chart.update = function() { container.transition().duration(duration).call(chart); }; + chart.container = this; - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); - // DEPRECATED set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); + // 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]; + 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 || !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 = stacked.xScale(); - y = stacked.yScale(); + // 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 = stacked.xScale(); + y = stacked.yScale(); - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); - var g = wrap.select('g'); + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); + var g = wrap.select('g'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); - 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-stackedWrap'); - focusEnter.append('g').attr('class', 'nv-interactive'); + 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-stackedWrap'); + focusEnter.append('g').attr('class', 'nv-interactive'); - // g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + // g.select("rect").attr("width",availableWidth).attr("height",availableHeight); - var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap'); + var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap'); - // Legend - if (!showLegend) { - g.select('.nv-legendWrap').selectAll('*').remove(); - } else { - var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; + // Legend + if (!showLegend) { + g.select('.nv-legendWrap').selectAll('*').remove(); + } else { + var legendWidth = (showControls && legendPosition === 'top') ? availableWidth - controlWidth : availableWidth; - legend.width(legendWidth); - g.select('.nv-legendWrap').datum(data).call(legend); + legend.width(legendWidth); + g.select('.nv-legendWrap').datum(data).call(legend); - if (legend.height() > margin.top) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + if (legendPosition === 'bottom') { + // constant from axis.js, plus some margin for better layout + var xAxisHeight = (showXAxis ? 12 : 0) + 10; + margin.bottom = Math.max(legend.height() + xAxisHeight, margin.bottom); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + var legendTop = availableHeight + xAxisHeight; + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + legendTop +')'); + } else if (legendPosition === 'top') { + if (!marginTop && margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); + } } - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); - } + // Controls + if (!showControls) { + g.select('.nv-controlsWrap').selectAll('*').remove(); + } else { + var controlsData = [ + { + key: controlLabels.stacked || 'Stacked', + metaKey: 'Stacked', + disabled: stacked.style() != 'stack', + style: 'stack' + }, + { + key: controlLabels.stream || 'Stream', + metaKey: 'Stream', + disabled: stacked.style() != 'stream', + style: 'stream' + }, + { + key: controlLabels.expanded || 'Expanded', + metaKey: 'Expanded', + disabled: stacked.style() != 'expand', + style: 'expand' + }, + { + key: controlLabels.stack_percent || 'Stack %', + metaKey: 'Stack_Percent', + disabled: stacked.style() != 'stack_percent', + style: 'stack_percent' + } + ]; - // Controls - if (!showControls) { - g.select('.nv-controlsWrap').selectAll('*').remove(); - } else { - var controlsData = [ - { - key: controlLabels.stacked || 'Stacked', - metaKey: 'Stacked', - disabled: stacked.style() != 'stack', - style: 'stack' - }, - { - key: controlLabels.stream || 'Stream', - metaKey: 'Stream', - disabled: stacked.style() != 'stream', - style: 'stream' - }, - { - key: controlLabels.expanded || 'Expanded', - metaKey: 'Expanded', - disabled: stacked.style() != 'expand', - style: 'expand' - }, - { - key: controlLabels.stack_percent || 'Stack %', - metaKey: 'Stack_Percent', - disabled: stacked.style() != 'stack_percent', - style: 'stack_percent' - } - ]; + controlWidth = (controlOptions.length/3) * 260; + controlsData = controlsData.filter(function(d) { + return controlOptions.indexOf(d.metaKey) !== -1; + }); - controlWidth = (controlOptions.length/3) * 260; - controlsData = controlsData.filter(function(d) { - return controlOptions.indexOf(d.metaKey) !== -1; - }); + controls + .width( controlWidth ) + .color(['#444', '#444', '#444']); - controls - .width( controlWidth ) - .color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); - g.select('.nv-controlsWrap') - .datum(controlsData) - .call(controls); + var requiredTop = Math.max(controls.height(), showLegend && (legendPosition === 'top') ? legend.height() : 0); - if (Math.max(controls.height(), legend.height()) > margin.top) { - margin.top = Math.max(controls.height(), legend.height()); - availableHeight = nv.utils.availableHeight(height, container, margin); + if ( margin.top != requiredTop ) { + margin.top = requiredTop; + availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0); + } + + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); } - g.select('.nv-controlsWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); - } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } + //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); + } - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer + g.select('.nv-focus .nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + stacked .width(availableWidth) .height(availableHeight) - .margin({left: margin.left, top: margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled; })); - g.select('.nv-focus .nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); + var stackedWrap = g.select('.nv-focus .nv-stackedWrap') + .datum(data.filter(function(d) { return !d.disabled; })); - stacked - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled; })); + // Setup Axes + if (showXAxis) { + xAxis.scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize( -availableHeight, 0); + } - var stackedWrap = g.select('.nv-focus .nv-stackedWrap') - .datum(data.filter(function(d) { return !d.disabled; })); - - // Setup Axes - if (showXAxis) { - xAxis.scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight, 0); - } - - if (showYAxis) { - var ticks; - if (stacked.offset() === 'wiggle') { - ticks = 0; + if (showYAxis) { + var ticks; + if (stacked.offset() === 'wiggle') { + ticks = 0; + } + else { + ticks = nv.utils.calcTicksY(availableHeight/36, data); + } + yAxis.scale(y) + ._ticks(ticks) + .tickSize(-availableWidth, 0); } - else { - ticks = nv.utils.calcTicksY(availableHeight/36, data); - } - yAxis.scale(y) - ._ticks(ticks) - .tickSize(-availableWidth, 0); - } - //============================================================ - // Update Axes - //============================================================ - function updateXAxis() { - if(showXAxis) { - g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')') - .transition() - .duration(duration) - .call(xAxis) + //============================================================ + // Update Axes + //============================================================ + function updateXAxis() { + if(showXAxis) { + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')') + .transition() + .duration(duration) + .call(xAxis) ; + } } - } - function updateYAxis() { - if(showYAxis) { - if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { - var currentFormat = yAxis.tickFormat(); + function updateYAxis() { + if(showYAxis) { + if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { + var currentFormat = yAxis.tickFormat(); - if ( !oldYTickFormat || currentFormat !== percentFormatter ) - oldYTickFormat = currentFormat; + if ( !oldYTickFormat || currentFormat !== percentFormatter ) + oldYTickFormat = currentFormat; - //Forces the yAxis to use percentage in 'expand' mode. - yAxis.tickFormat(percentFormatter); - } - else { - if (oldYTickFormat) { - yAxis.tickFormat(oldYTickFormat); - oldYTickFormat = null; + //Forces the yAxis to use percentage in 'expand' mode. + yAxis.tickFormat(percentFormatter); } - } + else { + if (oldYTickFormat) { + yAxis.tickFormat(oldYTickFormat); + oldYTickFormat = null; + } + } - g.select('.nv-focus .nv-y.nv-axis') - .transition().duration(0) - .call(yAxis); + g.select('.nv-focus .nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); + } } - } - //============================================================ - // Update Focus - //============================================================ - if(!focusEnable) { - stackedWrap.transition().call(stacked); - updateXAxis(); - updateYAxis(); - } else { - focus.width(availableWidth); - g.select('.nv-focusWrap') - .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') - .datum(data.filter(function(d) { return !d.disabled; })) - .call(focus); - var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); - if(extent !== null){ - onBrush(extent); + //============================================================ + // Update Focus + //============================================================ + if(!focusEnable) { + stackedWrap.transition().call(stacked); + updateXAxis(); + updateYAxis(); + } else { + focus.width(availableWidth); + g.select('.nv-focusWrap') + .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')') + .datum(data.filter(function(d) { return !d.disabled; })) + .call(focus); + var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent(); + if(extent !== null){ + onBrush(extent); + } } - } - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ - stacked.dispatch.on('areaClick.toggle', function(e) { - if (data.filter(function(d) { return !d.disabled }).length === 1) - data.forEach(function(d) { - d.disabled = false; - }); - else - data.forEach(function(d,i) { - d.disabled = (i != e.seriesIndex); - }); + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data.forEach(function(d) { + d.disabled = false; + }); + else + data.forEach(function(d,i) { + d.disabled = (i != e.seriesIndex); + }); - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); - chart.update(); - }); + chart.update(); + }); - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); + 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; + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + stacked.style(d.style); + + + state.style = stacked.style(); + dispatch.stateChange(state); + + chart.update(); }); - d.disabled = false; - stacked.style(d.style); + interactiveLayer.dispatch.on('elementMousemove', function(e) { + stacked.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + var point = series.values[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue != null) { + stacked.highlightPoint(i, pointIndex, true); + } + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + //If we are in 'expand' mode, use the stacked percent value instead of raw value. + var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); + allData.push({ + key: series.key, + value: tooltipValue, + color: color(series,series.seriesIndex), + point: point + }); - state.style = stacked.style(); - dispatch.stateChange(state); + if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) { + valueSum += tooltipValue; + allNullValues = false; + }; + }); - chart.update(); - }); + allData.reverse(); - interactiveLayer.dispatch.on('elementMousemove', function(e) { - stacked.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - var point = series.values[pointIndex]; - var pointYValue = chart.y()(point, pointIndex); - if (pointYValue != null) { - stacked.highlightPoint(i, pointIndex, true); - } - if (typeof point === 'undefined') return; - if (typeof singlePoint === 'undefined') singlePoint = point; - if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + //Highlight the tooltip entry based on which stack the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var yDistMax = Infinity, indexToHighlight = null; + allData.forEach(function(series,i) { - //If we are in 'expand' mode, use the stacked percent value instead of raw value. - var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); + //To handle situation where the stacked area chart is negative, we need to use absolute values + //when checking if the mouse Y value is within the stack area. + yValue = Math.abs(yValue); + var stackedY0 = Math.abs(series.point.display.y0); + var stackedY = Math.abs(series.point.display.y); + if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) + { + indexToHighlight = i; + return; + } + }); + if (indexToHighlight != null) + allData[indexToHighlight].highlight = true; + } + + //If we are not in 'expand' mode, add a 'Total' row to the tooltip. + if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) { allData.push({ - key: series.key, - value: tooltipValue, - color: color(series,series.seriesIndex), - point: point + key: totalLabel, + value: valueSum, + total: true }); + } - if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) { - valueSum += tooltipValue; - allNullValues = false; - }; - }); + var xValue = chart.x()(singlePoint,pointIndex); - allData.reverse(); + var valueFormatter = interactiveLayer.tooltip.valueFormatter(); + // Keeps track of the tooltip valueFormatter if the chart changes to expanded view + if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { + if ( !oldValueFormatter ) { + oldValueFormatter = valueFormatter; + } + //Forces the tooltip to use percentage in 'expand' mode. + valueFormatter = d3.format(".1%"); + } + else { + if (oldValueFormatter) { + valueFormatter = oldValueFormatter; + oldValueFormatter = null; + } + } - //Highlight the tooltip entry based on which stack the mouse is closest to. - if (allData.length > 2) { - var yValue = chart.yScale().invert(e.mouseY); - var yDistMax = Infinity, indexToHighlight = null; - allData.forEach(function(series,i) { + interactiveLayer.tooltip + .valueFormatter(valueFormatter) + .data( + { + value: xValue, + series: allData + } + )(); - //To handle situation where the stacked area chart is negative, we need to use absolute values - //when checking if the mouse Y value is within the stack area. - yValue = Math.abs(yValue); - var stackedY0 = Math.abs(series.point.display.y0); - var stackedY = Math.abs(series.point.display.y); - if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) - { - indexToHighlight = i; - return; - } - }); - if (indexToHighlight != null) - allData[indexToHighlight].highlight = true; - } + interactiveLayer.renderGuideLine(pointXLocation); - //If we are not in 'expand' mode, add a 'Total' row to the tooltip. - if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) { - allData.push({ - key: totalLabel, - value: valueSum, - total: true - }); - } + }); - var xValue = chart.x()(singlePoint,pointIndex); + interactiveLayer.dispatch.on("elementMouseout",function(e) { + stacked.clearHighlights(); + }); - var valueFormatter = interactiveLayer.tooltip.valueFormatter(); - // Keeps track of the tooltip valueFormatter if the chart changes to expanded view - if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { - if ( !oldValueFormatter ) { - oldValueFormatter = valueFormatter; + /* Update `main' graph on brush update. */ + focus.dispatch.on("onBrush", function(extent) { + onBrush(extent); + }); + + // Update chart from a state object passed to event handler + 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; } - //Forces the tooltip to use percentage in 'expand' mode. - valueFormatter = d3.format(".1%"); - } - else { - if (oldValueFormatter) { - valueFormatter = oldValueFormatter; - oldValueFormatter = null; - } - } - interactiveLayer.tooltip - .valueFormatter(valueFormatter) - .data( - { - value: xValue, - series: allData + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + style = e.style; } - )(); - interactiveLayer.renderGuideLine(pointXLocation); + chart.update(); + }); - }); + //============================================================ + // Functions + //------------------------------------------------------------ - interactiveLayer.dispatch.on("elementMouseout",function(e) { - stacked.clearHighlights(); - }); + function onBrush(extent) { + // Update Main (Focus) + var stackedWrap = g.select('.nv-focus .nv-stackedWrap') + .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 stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1]; + }), + disableTooltip: d.disableTooltip + }; + }) + ); + stackedWrap.transition().duration(duration).call(stacked); - /* Update `main' graph on brush update. */ - focus.dispatch.on("onBrush", function(extent) { - onBrush(extent); + // Update Main (Focus) Axes + updateXAxis(); + updateYAxis(); + } + }); - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { + renderWatch.renderEnd('stacked Area chart immediate'); + return chart; + } - if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - state.disabled = e.disabled; - } + stacked.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.point['x'] = stacked.x()(evt.point); + evt.point['y'] = stacked.y()(evt.point); + tooltip.data(evt).hidden(false); + }); - if (typeof e.style !== 'undefined') { - stacked.style(e.style); - style = e.style; - } + stacked.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - chart.update(); - }); + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.x2Axis = focus.xAxis; + chart.yAxis = yAxis; + chart.y2Axis = focus.yAxis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + chart.focus = focus; - //============================================================ - // Functions - //------------------------------------------------------------ + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - function onBrush(extent) { - // Update Main (Focus) - var stackedWrap = g.select('.nv-focus .nv-stackedWrap') - .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 stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1]; - }), - disableTooltip: d.disableTooltip - }; - }) - ); - stackedWrap.transition().duration(duration).call(stacked); + 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=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, + controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, + showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}}, + totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}}, + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, + brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, - // Update Main (Focus) Axes - updateXAxis(); - updateYAxis(); - } - + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + if (_.top !== undefined) { + margin.top = _.top; + marginTop = _.top; + } + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + focusMargin: {get: function(){return focus.margin}, set: function(_){ + focus.margin.top = _.top !== undefined ? _.top : focus.margin.top; + focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; + focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; + focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + stacked.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + focus.color(color); + }}, + x: {get: function(){return stacked.x();}, set: function(_){ + stacked.x(_); + focus.x(_); + }}, + y: {get: function(){return stacked.y();}, set: function(_){ + stacked.y(_); + focus.y(_); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = !!_; + chart.interactive(!_); + chart.useVoronoi(!_); + stacked.scatter.interactive(!_); + }} }); - renderWatch.renderEnd('stacked Area chart immediate'); + nv.utils.inheritOptions(chart, stacked); + nv.utils.initOptions(chart); + return chart; - } + }; - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + nv.models.stackedAreaWithFocusChart = function() { + return nv.models.stackedAreaChart() + .margin({ bottom: 30 }) + .focusEnable( true ); + }; +// based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad + nv.models.sunburst = function() { + "use strict"; - stacked.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.point['x'] = stacked.x()(evt.point); - evt.point['y'] = stacked.y()(evt.point); - tooltip.data(evt).hidden(false); - }); + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - stacked.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 600 + , height = 600 + , mode = "count" + , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }} + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , color = nv.utils.defaultColor() + , showLabels = false + , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}} + , labelThreshold = 0.02 + , sort = function(d1, d2){return d1.name > d2.name;} + , key = function(d,i){return d.name;} + , groupColorByParent = true + , duration = 500 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd'); - // expose chart's sub-components - chart.dispatch = dispatch; - chart.stacked = stacked; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.x2Axis = focus.xAxis; - chart.yAxis = yAxis; - chart.y2Axis = focus.yAxis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - chart.focus = focus; + //============================================================ + // aux functions and setup + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + var x = d3.scale.linear().range([0, 2 * Math.PI]); + var y = d3.scale.sqrt(); - 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=_;}}, - 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=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, - controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, - showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}}, - totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}}, - focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, - focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}}, - brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}}, + var partition = d3.layout.partition().sort(sort); - // 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; - }}, - focusMargin: {get: function(){return focus.margin}, set: function(_){ - focus.margin.top = _.top !== undefined ? _.top : focus.margin.top; - focus.margin.right = _.right !== undefined ? _.right : focus.margin.right; - focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom; - focus.margin.left = _.left !== undefined ? _.left : focus.margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - stacked.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - stacked.color(color); - focus.color(color); - }}, - x: {get: function(){return stacked.x();}, set: function(_){ - stacked.x(_); - focus.x(_); - }}, - y: {get: function(){return stacked.y();}, set: function(_){ - stacked.y(_); - focus.y(_); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( rightAlignYAxis ? 'right' : 'left'); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = !!_; - chart.interactive(!_); - chart.useVoronoi(!_); - stacked.scatter.interactive(!_); - }} - }); + var node, availableWidth, availableHeight, radius; + var prevPositions = {}; - nv.utils.inheritOptions(chart, stacked); - nv.utils.initOptions(chart); + var arc = d3.svg.arc() + .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) }) + .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) }) + .innerRadius(function(d) {return Math.max(0, y(d.y)) }) + .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) }); - return chart; -}; + function rotationToAvoidUpsideDown(d) { + var centerAngle = computeCenterAngle(d); + if(centerAngle > 90){ + return 180; + } + else { + return 0; + } + } -nv.models.stackedAreaWithFocusChart = function() { - return nv.models.stackedAreaChart() - .margin({ bottom: 30 }) - .focusEnable( true ); -}; -// based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad -nv.models.sunburst = function() { - "use strict"; + function computeCenterAngle(d) { + var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); + var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90; + return centerAngle; + } - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + function computeNodePercentage(d) { + var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); + var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + return (endAngle - startAngle) / (2 * Math.PI); + } - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 600 - , height = 600 - , mode = "count" - , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }} - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , color = nv.utils.defaultColor() - , showLabels = false - , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}} - , labelThreshold = 0.02 - , sort = function(d1, d2){return d1.name > d2.name;} - , key = function(d,i){return d.name;} - , groupColorByParent = true - , duration = 500 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd'); + function labelThresholdMatched(d) { + var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); + var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); - //============================================================ - // aux functions and setup - //------------------------------------------------------------ + var size = endAngle - startAngle; + return size > labelThreshold; + } - var x = d3.scale.linear().range([0, 2 * Math.PI]); - var y = d3.scale.sqrt(); + // When zooming: interpolate the scales. + function arcTweenZoom(e,i) { + var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]), + yd = d3.interpolate(y.domain(), [node.y, 1]), + yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]); - var partition = d3.layout.partition().sort(sort); + if (i === 0) { + return function() {return arc(e);} + } + else { + return function (t) { + x.domain(xd(t)); + y.domain(yd(t)).range(yr(t)); + return arc(e); + } + }; + } - var node, availableWidth, availableHeight, radius; - var prevPositions = {}; + function arcTweenUpdate(d) { + var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d); - var arc = d3.svg.arc() - .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) }) - .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) }) - .innerRadius(function(d) {return Math.max(0, y(d.y)) }) - .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) }); + return function (t) { + var b = ipo(t); - function rotationToAvoidUpsideDown(d) { - var centerAngle = computeCenterAngle(d); - if(centerAngle > 90){ - return 180; + d.x0 = b.x; + d.dx0 = b.dx; + d.y0 = b.y; + d.dy0 = b.dy; + + return arc(b); + }; } - else { - return 0; + + function updatePrevPosition(node) { + var k = key(node); + if(! prevPositions[k]) prevPositions[k] = {}; + var pP = prevPositions[k]; + pP.dx = node.dx; + pP.x = node.x; + pP.dy = node.dy; + pP.y = node.y; } - } - function computeCenterAngle(d) { - var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); - var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); - var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90; - return centerAngle; - } + function storeRetrievePrevPositions(nodes) { + nodes.forEach(function(n){ + var k = key(n); + var pP = prevPositions[k]; + //console.log(k,n,pP); + if( pP ){ + n.dx0 = pP.dx; + n.x0 = pP.x; + n.dy0 = pP.dy; + n.y0 = pP.y; + } + else { + n.dx0 = n.dx; + n.x0 = n.x; + n.dy0 = n.dy; + n.y0 = n.y; + } + updatePrevPosition(n); + }); + } - function computeNodePercentage(d) { - var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); - var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); - return (endAngle - startAngle) / (2 * Math.PI); - } + function zoomClick(d) { + var labels = container.selectAll('text') + var path = container.selectAll('path') - function labelThresholdMatched(d) { - var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x))); - var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + // fade out all text elements + labels.transition().attr("opacity",0); - var size = endAngle - startAngle; - return size > labelThreshold; - } + // to allow reference to the new center node + node = d; - // When zooming: interpolate the scales. - function arcTweenZoom(e,i) { - var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]), - yd = d3.interpolate(y.domain(), [node.y, 1]), - yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]); + path.transition() + .duration(duration) + .attrTween("d", arcTweenZoom) + .each('end', function(e) { + // partially taken from here: http://bl.ocks.org/metmajer/5480307 + // check if the animated element's data e lies within the visible angle span given in d + if(e.x >= d.x && e.x < (d.x + d.dx) ){ + if(e.depth >= d.depth){ + // get a selection of the associated text element + var parentNode = d3.select(this.parentNode); + var arcText = parentNode.select('text'); - if (i === 0) { - return function() {return arc(e);} + // fade in the text element and recalculate positions + arcText.transition().duration(duration) + .text( function(e){return labelFormat(e) }) + .attr("opacity", function(d){ + if(labelThresholdMatched(d)) { + return 1; + } + else { + return 0; + } + }) + .attr("transform", function() { + var width = this.getBBox().width; + if(e.depth === 0) + return "translate(" + (width / 2 * - 1) + ",0)"; + else if(e.depth === d.depth){ + return "translate(" + (y(e.y) + 5) + ",0)"; + } + else { + var centerAngle = computeCenterAngle(e); + var rotation = rotationToAvoidUpsideDown(e); + if (rotation === 0) { + return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)'; + } + else { + return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')'; + } + } + }); + } + } + }) } - else { - return function (t) { - x.domain(xd(t)); - y.domain(yd(t)).range(yr(t)); - return arc(e); - } - }; - } - function arcTweenUpdate(d) { - var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d); + //============================================================ + // chart function + //------------------------------------------------------------ + var renderWatch = nv.utils.renderWatch(dispatch); - return function (t) { - var b = ipo(t); + function chart(selection) { + renderWatch.reset(); - d.x0 = b.x; - d.dx0 = b.dx; - d.y0 = b.y; - d.dy0 = b.dy; + selection.each(function(data) { + container = d3.select(this); + availableWidth = nv.utils.availableWidth(width, container, margin); + availableHeight = nv.utils.availableHeight(height, container, margin); + radius = Math.min(availableWidth, availableHeight) / 2; - return arc(b); - }; - } + y.range([0, radius]); - function updatePrevPosition(node) { - var k = key(node); - if(! prevPositions[k]) prevPositions[k] = {}; - var pP = prevPositions[k]; - pP.dx = node.dx; - pP.x = node.x; - pP.dy = node.dy; - pP.y = node.y; - } + // Setup containers and skeleton of chart + var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst'); + if( !wrap[0][0] ) { + wrap = container.append('g') + .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id) + .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); + } else { + wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); + } - function storeRetrievePrevPositions(nodes) { - nodes.forEach(function(n){ - var k = key(n); - var pP = prevPositions[k]; - //console.log(k,n,pP); - if( pP ){ - n.dx0 = pP.dx; - n.x0 = pP.x; - n.dy0 = pP.dy; - n.y0 = pP.y; - } - else { - n.dx0 = n.dx; - n.x0 = n.x; - n.dy0 = n.dy; - n.y0 = n.y; - } - updatePrevPosition(n); - }); - } + container.on('click', function (d, i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); - function zoomClick(d) { - var labels = container.selectAll('text') - var path = container.selectAll('path') + partition.value(modes[mode] || modes["count"]); - // fade out all text elements - labels.transition().attr("opacity",0); + //reverse the drawing order so that the labels of inner + //arcs are drawn on top of the outer arcs. + var nodes = partition.nodes(data[0]).reverse() - // to allow reference to the new center node - node = d; + storeRetrievePrevPositions(nodes); + var cG = wrap.selectAll('.arc-container').data(nodes, key) - path.transition() - .duration(duration) - .attrTween("d", arcTweenZoom) - .each('end', function(e) { - // partially taken from here: http://bl.ocks.org/metmajer/5480307 - // check if the animated element's data e lies within the visible angle span given in d - if(e.x >= d.x && e.x < (d.x + d.dx) ){ - if(e.depth >= d.depth){ - // get a selection of the associated text element - var parentNode = d3.select(this.parentNode); - var arcText = parentNode.select('text'); + //handle new datapoints + var cGE = cG.enter() + .append("g") + .attr("class",'arc-container') - // fade in the text element and recalculate positions - arcText.transition().duration(duration) - .text( function(e){return labelFormat(e) }) + cGE.append("path") + .attr("d", arc) + .style("fill", function (d) { + if (d.color) { + return d.color; + } + else if (groupColorByParent) { + return color((d.children ? d : d.parent).name); + } + else { + return color(d.name); + } + }) + .style("stroke", "#FFF") + .on("click", function(d,i){ + zoomClick(d); + dispatch.elementClick({ + data: d, + index: i + }) + }) + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true).style('opacity', 0.8); + dispatch.elementMouseover({ + data: d, + color: d3.select(this).style("fill"), + percent: computeNodePercentage(d) + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false).style('opacity', 1); + dispatch.elementMouseout({ + data: d + }); + }) + .on('mousemove', function(d,i){ + dispatch.elementMousemove({ + data: d + }); + }); + + ///Iterating via each and selecting based on the this + ///makes it work ... a cG.selectAll('path') doesn't. + ///Without iteration the data (in the element) didn't update. + cG.each(function(d){ + d3.select(this).select('path') + .transition() + .duration(duration) + .attrTween('d', arcTweenUpdate); + }); + + if(showLabels){ + //remove labels first and add them back + cG.selectAll('text').remove(); + + //this way labels are on top of newly added arcs + cG.append('text') + .text( function(e){ return labelFormat(e)}) + .transition() + .duration(duration) .attr("opacity", function(d){ if(labelThresholdMatched(d)) { return 1; } else { return 0; } }) - .attr("transform", function() { + .attr("transform", function(d) { var width = this.getBBox().width; - if(e.depth === 0) - return "translate(" + (width / 2 * - 1) + ",0)"; - else if(e.depth === d.depth){ - return "translate(" + (y(e.y) + 5) + ",0)"; + if(d.depth === 0){ + return "rotate(0)translate(" + (width / 2 * -1) + ",0)"; } else { - var centerAngle = computeCenterAngle(e); - var rotation = rotationToAvoidUpsideDown(e); + var centerAngle = computeCenterAngle(d); + var rotation = rotationToAvoidUpsideDown(d); if (rotation === 0) { - return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)'; + return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)'; } else { - return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')'; + return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')'; } } }); - } } - }) - } - //============================================================ - // chart function - //------------------------------------------------------------ - var renderWatch = nv.utils.renderWatch(dispatch); + //zoom out to the center when the data is updated. + zoomClick(nodes[nodes.length - 1]) - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - container = d3.select(this); - availableWidth = nv.utils.availableWidth(width, container, margin); - availableHeight = nv.utils.availableHeight(height, container, margin); - radius = Math.min(availableWidth, availableHeight) / 2; - - y.range([0, radius]); - - // Setup containers and skeleton of chart - var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst'); - if( !wrap[0][0] ) { - wrap = container.append('g') - .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id) - .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); - } else { - wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')'); - } - - container.on('click', function (d, i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - partition.value(modes[mode] || modes["count"]); - - //reverse the drawing order so that the labels of inner - //arcs are drawn on top of the outer arcs. - var nodes = partition.nodes(data[0]).reverse() - - storeRetrievePrevPositions(nodes); - var cG = wrap.selectAll('.arc-container').data(nodes, key) - - //handle new datapoints - var cGE = cG.enter() - .append("g") - .attr("class",'arc-container') - - cGE.append("path") - .attr("d", arc) - .style("fill", function (d) { - if (d.color) { - return d.color; - } - else if (groupColorByParent) { - return color((d.children ? d : d.parent).name); - } - else { - return color(d.name); - } - }) - .style("stroke", "#FFF") - .on("click", zoomClick) - .on('mouseover', function(d,i){ - d3.select(this).classed('hover', true).style('opacity', 0.8); - dispatch.elementMouseover({ - data: d, - color: d3.select(this).style("fill"), - percent: computeNodePercentage(d) - }); - }) - .on('mouseout', function(d,i){ - d3.select(this).classed('hover', false).style('opacity', 1); - dispatch.elementMouseout({ - data: d - }); - }) - .on('mousemove', function(d,i){ - dispatch.elementMousemove({ - data: d - }); - }); - - ///Iterating via each and selecting based on the this - ///makes it work ... a cG.selectAll('path') doesn't. - ///Without iteration the data (in the element) didn't update. - cG.each(function(d){ - d3.select(this).select('path') + //remove unmatched elements ... + cG.exit() .transition() .duration(duration) - .attrTween('d', arcTweenUpdate); + .attr('opacity',0) + .each('end',function(d){ + var k = key(d); + prevPositions[k] = undefined; + }) + .remove(); }); - if(showLabels){ - //remove labels first and add them back - cG.selectAll('text').remove(); - //this way labels are on top of newly added arcs - cG.append('text') - .text( function(e){ return labelFormat(e)}) - .transition() - .duration(duration) - .attr("opacity", function(d){ - if(labelThresholdMatched(d)) { - return 1; - } - else { - return 0; - } - }) - .attr("transform", function(d) { - var width = this.getBBox().width; - if(d.depth === 0){ - return "rotate(0)translate(" + (width / 2 * -1) + ",0)"; - } - else { - var centerAngle = computeCenterAngle(d); - var rotation = rotationToAvoidUpsideDown(d); - if (rotation === 0) { - return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)'; - } - else { - return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')'; - } - } - }); - } + renderWatch.renderEnd('sunburst immediate'); + return chart; + } - //zoom out to the center when the data is updated. - zoomClick(nodes[nodes.length - 1]) + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); - //remove unmatched elements ... - cG.exit() - .transition() - .duration(duration) - .attr('opacity',0) - .each('end',function(d){ - var k = key(d); - prevPositions[k] = undefined; - }) - .remove(); + 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=_;}}, + mode: {get: function(){return mode;}, set: function(_){mode=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}}, + showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}}, + labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}}, + labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}}, + sort: {get: function(){return sort;}, set: function(_){sort=_}}, + key: {get: function(){return key;}, set: function(_){key=_}}, + // 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(_); + }} }); - - renderWatch.renderEnd('sunburst immediate'); + nv.utils.initOptions(chart); return chart; - } + }; + nv.models.sunburstChart = function() { + "use strict"; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); + var sunburst = nv.models.sunburst(); + var tooltip = nv.models.tooltip(); - 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=_;}}, - mode: {get: function(){return mode;}, set: function(_){mode=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}}, - showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}}, - labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}}, - labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}}, - sort: {get: function(){return sort;}, set: function(_){sort=_}}, - key: {get: function(){return key;}, set: function(_){key=_}}, - // 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(_); - }} - }); + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showTooltipPercent = false + , id = Math.round(Math.random() * 100000) + , defaultState = null + , noData = null + , duration = 250 + , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd'); - nv.utils.initOptions(chart); - return chart; -}; -nv.models.sunburstChart = function() { - "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ + //============================================================ + // Private Variables + //------------------------------------------------------------ - var sunburst = nv.models.sunburst(); - var tooltip = nv.models.tooltip(); + var renderWatch = nv.utils.renderWatch(dispatch); - var margin = {top: 30, right: 20, bottom: 20, left: 20} - , width = null - , height = null - , color = nv.utils.defaultColor() - , showTooltipPercent = false - , id = Math.round(Math.random() * 100000) - , defaultState = null - , noData = null - , duration = 250 - , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd'); + tooltip + .duration(0) + .headerEnabled(false) + .valueFormatter(function(d){return d;}); + //============================================================ + // Chart function + //------------------------------------------------------------ - //============================================================ - // Private Variables - //------------------------------------------------------------ + function chart(selection) { + renderWatch.reset(); + renderWatch.models(sunburst); - var renderWatch = nv.utils.renderWatch(dispatch); + selection.each(function(data) { + var container = d3.select(this); - tooltip - .duration(0) - .headerEnabled(false) - .valueFormatter(function(d){return d;}); + nv.utils.initSVG(container); - //============================================================ - // Chart function - //------------------------------------------------------------ + var availableWidth = nv.utils.availableWidth(width, container, margin); + var availableHeight = nv.utils.availableHeight(height, container, margin); - function chart(selection) { - renderWatch.reset(); - renderWatch.models(sunburst); + chart.update = function() { + if (duration === 0) { + container.call(chart); + } else { + container.transition().duration(duration).call(chart); + } + }; + chart.container = container; - selection.each(function(data) { - var container = d3.select(this); + // 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(); + } - nv.utils.initSVG(container); + sunburst.width(availableWidth).height(availableHeight).margin(margin); + container.call(sunburst); + }); - var availableWidth = nv.utils.availableWidth(width, container, margin); - var availableHeight = nv.utils.availableHeight(height, container, margin); + renderWatch.renderEnd('sunburstChart immediate'); + return chart; + } - chart.update = function() { - if (duration === 0) { - container.call(chart); - } else { - container.transition().duration(duration).call(chart); - } - }; - chart.container = container; + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ - // 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(); + sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.series = { + key: evt.data.name, + value: (evt.data.value || evt.data.size), + color: evt.color, + percent: evt.percent + }; + if (!showTooltipPercent) { + delete evt.percent; + delete evt.series.percent; } + tooltip.data(evt).hidden(false); + }); - sunburst.width(availableWidth).height(availableHeight).margin(margin); - container.call(sunburst); + sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); }); - renderWatch.renderEnd('sunburstChart immediate'); - return chart; - } + sunburst.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip(); + }); - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ - sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.series = { - key: evt.data.name, - value: (evt.data.value || evt.data.size), - color: evt.color, - percent: evt.percent - }; - if (!showTooltipPercent) { - delete evt.percent; - delete evt.series.percent; - } - tooltip.data(evt).hidden(false); - }); + // expose chart's sub-components + chart.dispatch = dispatch; + chart.sunburst = sunburst; + chart.tooltip = tooltip; + chart.options = nv.utils.optionsFunc.bind(chart); - sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); + // use Object get/set functionality to map between vars and chart functions + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, - sunburst.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip(); - }); + // options that require extra logic in the setter + color: {get: function(){return color;}, set: function(_){ + color = _; + sunburst.color(color); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + sunburst.duration(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; + sunburst.margin(margin); + }} + }); + nv.utils.inheritOptions(chart, sunburst); + nv.utils.initOptions(chart); + return chart; - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ + }; - // expose chart's sub-components - chart.dispatch = dispatch; - chart.sunburst = sunburst; - 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 - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}}, - - // options that require extra logic in the setter - color: {get: function(){return color;}, set: function(_){ - color = _; - sunburst.color(color); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - sunburst.duration(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; - sunburst.margin(margin); - }} - }); - nv.utils.inheritOptions(chart, sunburst); - nv.utils.initOptions(chart); - return chart; - -}; - -nv.version = "1.8.4"; + nv.version = "1.8.5"; })(); +//# sourceMappingURL=nv.d3.js.map \ No newline at end of file