]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/static/js/nv.d3.js
Use latest and greatest NVD3.
[xonotic/xonstat.git] / xonstat / static / js / nv.d3.js
1 /* nvd3 version 1.6.0(https://github.com/liquidpele/nvd3) 2014-11-23 */
2 (function(){
3
4 // set up main nv object on window
5 var nv = window.nv || {};
6 window.nv = nv;
7
8 // the major global objects under the nv namespace
9 nv.dev = true; //set false when in production
10 nv.tooltip = nv.tooltip || {}; // For the tooltip system
11 nv.utils = nv.utils || {}; // Utility subsystem
12 nv.models = nv.models || {}; //stores all the possible models/components
13 nv.charts = {}; //stores all the ready to use charts
14 nv.graphs = []; //stores all the graphs currently on the page
15 nv.logs = {}; //stores some statistics and potential error messages
16
17 nv.dispatch = d3.dispatch('render_start', 'render_end');
18
19 // Function bind polyfill
20 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
21 // https://github.com/ariya/phantomjs/issues/10522
22 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
23 // phantomJS is used for running the test suite
24 if (!Function.prototype.bind) {
25     Function.prototype.bind = function (oThis) {
26         if (typeof this !== "function") {
27             // closest thing possible to the ECMAScript 5 internal IsCallable function
28             throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
29         }
30
31         var aArgs = Array.prototype.slice.call(arguments, 1),
32             fToBind = this,
33             fNOP = function () {},
34             fBound = function () {
35                 return fToBind.apply(this instanceof fNOP && oThis
36                         ? this
37                         : oThis,
38                     aArgs.concat(Array.prototype.slice.call(arguments)));
39             };
40
41         fNOP.prototype = this.prototype;
42         fBound.prototype = new fNOP();
43         return fBound;
44     };
45 }
46
47 //  Development render timers - disabled if dev = false
48 if (nv.dev) {
49     nv.dispatch.on('render_start', function(e) {
50         nv.logs.startTime = +new Date();
51     });
52
53     nv.dispatch.on('render_end', function(e) {
54         nv.logs.endTime = +new Date();
55         nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
56         nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
57     });
58 }
59
60 // Logs all arguments, and returns the last so you can test things in place
61 // Note: in IE8 console.log is an object not a function, and if modernizr is used
62 // then calling Function.prototype.bind with with anything other than a function
63 // causes a TypeError to be thrown.
64 nv.log = function() {
65     if (nv.dev && window.console && console.log && console.log.apply)
66         console.log.apply(console, arguments);
67     else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
68         var log = Function.prototype.bind.call(console.log, console);
69         log.apply(console, arguments);
70     }
71     return arguments[arguments.length - 1];
72 };
73
74 // print console warning, should be used by deprecated functions
75 nv.deprecated = function(name) {
76     if (nv.dev && console && console.warn) {
77         console.warn('`' + name + '` has been deprecated.');
78     }
79 };
80
81 // render function is used to queue up chart rendering
82 // in non-blocking timeout functions
83 nv.render = function render(step) {
84     // number of graphs to generate in each timeout loop
85     step = step || 1;
86
87     nv.render.active = true;
88     nv.dispatch.render_start();
89
90     setTimeout(function() {
91         var chart, graph;
92
93         for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
94             chart = graph.generate();
95             if (typeof graph.callback == typeof(Function)) graph.callback(chart);
96             nv.graphs.push(chart);
97         }
98
99         nv.render.queue.splice(0, i);
100
101         if (nv.render.queue.length) setTimeout(arguments.callee, 0);
102         else {
103             nv.dispatch.render_end();
104             nv.render.active = false;
105         }
106     }, 0);
107 };
108
109 nv.render.active = false;
110 nv.render.queue = [];
111
112 // main function to use when adding a new graph, see examples
113 nv.addGraph = function(obj) {
114     if (typeof arguments[0] === typeof(Function)) {
115         obj = {generate: arguments[0], callback: arguments[1]};
116     }
117
118     nv.render.queue.push(obj);
119
120     if (!nv.render.active) {
121         nv.render();
122     }
123 };/* Utility class to handle creation of an interactive layer.
124  This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
125  containing the X-coordinate. It can also render a vertical line where the mouse is located.
126
127  dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
128  the rectangle. The dispatch is given one object which contains the mouseX/Y location.
129  It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
130  */
131 nv.interactiveGuideline = function() {
132     "use strict";
133
134     var tooltip = nv.models.tooltip();
135
136     //Public settings
137     var width = null;
138     var height = null;
139
140     //Please pass in the bounding chart's top and left margins
141     //This is important for calculating the correct mouseX/Y positions.
142     var margin = {left: 0, top: 0}
143         , xScale = d3.scale.linear()
144         , yScale = d3.scale.linear()
145         , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
146         , showGuideLine = true;
147     //Must pass in the bounding chart's <svg> container.
148     //The mousemove event is attached to this container.
149     var svgContainer = null;
150
151     // check if IE by looking for activeX
152     var isMSIE = "ActiveXObject" in window;
153
154
155     function layer(selection) {
156         selection.each(function(data) {
157             var container = d3.select(this);
158             var availableWidth = (width || 960), availableHeight = (height || 400);
159             var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
160                 .data([data]);
161             var wrapEnter = wrap.enter()
162                 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
163             wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
164
165             if (!svgContainer) {
166                 return;
167             }
168
169             function mouseHandler() {
170                 var d3mouse = d3.mouse(this);
171                 var mouseX = d3mouse[0];
172                 var mouseY = d3mouse[1];
173                 var subtractMargin = true;
174                 var mouseOutAnyReason = false;
175                 if (isMSIE) {
176                     /*
177                      D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
178                      d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
179                      over a rect in IE 10.
180                      However, d3.event.offsetX/Y also returns the mouse coordinates
181                      relative to the triggering <rect>. So we use offsetX/Y on IE.
182                      */
183                     mouseX = d3.event.offsetX;
184                     mouseY = d3.event.offsetY;
185
186                     /*
187                      On IE, if you attach a mouse event listener to the <svg> container,
188                      it will actually trigger it for all the child elements (like <path>, <circle>, etc).
189                      When this happens on IE, the offsetX/Y is set to where ever the child element
190                      is located.
191                      As a result, we do NOT need to subtract margins to figure out the mouse X/Y
192                      position under this scenario. Removing the line below *will* cause
193                      the interactive layer to not work right on IE.
194                      */
195                     if(d3.event.target.tagName !== "svg") {
196                         subtractMargin = false;
197                     }
198
199                     if (d3.event.target.className.baseVal.match("nv-legend")) {
200                         mouseOutAnyReason = true;
201                     }
202
203                 }
204
205                 if(subtractMargin) {
206                     mouseX -= margin.left;
207                     mouseY -= margin.top;
208                 }
209
210                 /* If mouseX/Y is outside of the chart's bounds,
211                  trigger a mouseOut event.
212                  */
213                 if (mouseX < 0 || mouseY < 0
214                     || mouseX > availableWidth || mouseY > availableHeight
215                     || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
216                     || mouseOutAnyReason
217                     ) {
218
219                     if (isMSIE) {
220                         if (d3.event.relatedTarget
221                             && d3.event.relatedTarget.ownerSVGElement === undefined
222                             && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
223
224                             return;
225                         }
226                     }
227                     dispatch.elementMouseout({
228                         mouseX: mouseX,
229                         mouseY: mouseY
230                     });
231                     layer.renderGuideLine(null); //hide the guideline
232                     return;
233                 }
234
235                 var pointXValue = xScale.invert(mouseX);
236                 dispatch.elementMousemove({
237                     mouseX: mouseX,
238                     mouseY: mouseY,
239                     pointXValue: pointXValue
240                 });
241
242                 //If user double clicks the layer, fire a elementDblclick
243                 if (d3.event.type === "dblclick") {
244                     dispatch.elementDblclick({
245                         mouseX: mouseX,
246                         mouseY: mouseY,
247                         pointXValue: pointXValue
248                     });
249                 }
250
251                 // if user single clicks the layer, fire elementClick
252                 if (d3.event.type === 'click') {
253                     dispatch.elementClick({
254                         mouseX: mouseX,
255                         mouseY: mouseY,
256                         pointXValue: pointXValue
257                     });
258                 }
259             }
260
261             svgContainer
262                 .on("mousemove",mouseHandler, true)
263                 .on("mouseout" ,mouseHandler,true)
264                 .on("dblclick" ,mouseHandler)
265                 .on("click", mouseHandler)
266             ;
267
268             //Draws a vertical guideline at the given X postion.
269             layer.renderGuideLine = function(x) {
270                 if (!showGuideLine) return;
271                 var line = wrap.select(".nv-interactiveGuideLine")
272                     .selectAll("line")
273                     .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
274
275                 line.enter()
276                     .append("line")
277                     .attr("class", "nv-guideline")
278                     .attr("x1", function(d) { return d;})
279                     .attr("x2", function(d) { return d;})
280                     .attr("y1", availableHeight)
281                     .attr("y2",0)
282                 ;
283                 line.exit().remove();
284
285             }
286         });
287     }
288
289     layer.dispatch = dispatch;
290     layer.tooltip = tooltip;
291
292     layer.margin = function(_) {
293         if (!arguments.length) return margin;
294         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
295         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
296         return layer;
297     };
298
299     layer.width = function(_) {
300         if (!arguments.length) return width;
301         width = _;
302         return layer;
303     };
304
305     layer.height = function(_) {
306         if (!arguments.length) return height;
307         height = _;
308         return layer;
309     };
310
311     layer.xScale = function(_) {
312         if (!arguments.length) return xScale;
313         xScale = _;
314         return layer;
315     };
316
317     layer.showGuideLine = function(_) {
318         if (!arguments.length) return showGuideLine;
319         showGuideLine = _;
320         return layer;
321     };
322
323     layer.svgContainer = function(_) {
324         if (!arguments.length) return svgContainer;
325         svgContainer = _;
326         return layer;
327     };
328
329     return layer;
330 };
331
332 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
333  This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
334
335  For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
336  Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
337  because 28 is closer to 30 than 10.
338
339  Unit tests can be found in: interactiveBisectTest.html
340
341  Has the following known issues:
342  * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
343  * Won't work if there are duplicate x coordinate values.
344  */
345 nv.interactiveBisect = function (values, searchVal, xAccessor) {
346     "use strict";
347     if (! (values instanceof Array)) {
348         return null;
349     }
350     if (typeof xAccessor !== 'function') {
351         xAccessor = function(d,i) {
352             return d.x;
353         }
354     }
355
356     var bisect = d3.bisector(xAccessor).left;
357     var index = d3.max([0, bisect(values,searchVal) - 1]);
358     var currentValue = xAccessor(values[index], index);
359
360     if (typeof currentValue === 'undefined') {
361         currentValue = index;
362     }
363
364     if (currentValue === searchVal) {
365         return index; //found exact match
366     }
367
368     var nextIndex = d3.min([index+1, values.length - 1]);
369     var nextValue = xAccessor(values[nextIndex], nextIndex);
370
371     if (typeof nextValue === 'undefined') {
372         nextValue = nextIndex;
373     }
374
375     if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
376         return index;
377     } else {
378         return nextIndex
379     }
380 };
381
382 /*
383  Returns the index in the array "values" that is closest to searchVal.
384  Only returns an index if searchVal is within some "threshold".
385  Otherwise, returns null.
386  */
387 nv.nearestValueIndex = function (values, searchVal, threshold) {
388     "use strict";
389     var yDistMax = Infinity, indexToHighlight = null;
390     values.forEach(function(d,i) {
391         var delta = Math.abs(searchVal - d);
392         if ( delta <= yDistMax && delta < threshold) {
393             yDistMax = delta;
394             indexToHighlight = i;
395         }
396     });
397     return indexToHighlight;
398 };
399 /* Tooltip rendering model for nvd3 charts.
400  window.nv.models.tooltip is the updated,new way to render tooltips.
401
402  window.nv.tooltip.show is the old tooltip code.
403  window.nv.tooltip.* also has various helper methods.
404  */
405 (function() {
406     "use strict";
407     window.nv.tooltip = {};
408
409     /* Model which can be instantiated to handle tooltip rendering.
410      Example usage:
411      var tip = nv.models.tooltip().gravity('w').distance(23)
412      .data(myDataObject);
413
414      tip();    //just invoke the returned function to render tooltip.
415      */
416     window.nv.models.tooltip = function() {
417         //HTML contents of the tooltip.  If null, the content is generated via the data variable.
418         var content = null;
419
420         /*
421         Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
422         Example Format of data:
423         {
424             key: "Date",
425             value: "August 2009",
426             series: [
427                 {key: "Series 1", value: "Value 1", color: "#000"},
428                 {key: "Series 2", value: "Value 2", color: "#00f"}
429             ]
430         }
431         */
432         var data = null;
433
434         var gravity = 'w'   //Can be 'n','s','e','w'. Determines how tooltip is positioned.
435             ,distance = 50   //Distance to offset tooltip from the mouse location.
436             ,snapDistance = 25   //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
437             ,   fixedTop = null //If not null, this fixes the top position of the tooltip.
438             ,   classes = null  //Attaches additional CSS classes to the tooltip DIV that is created.
439             ,   chartContainer = null   //Parent DIV, of the SVG Container that holds the chart.
440             ,   tooltipElem = null  //actual DOM element representing the tooltip.
441             ,   position = {left: null, top: null}      //Relative position of the tooltip inside chartContainer.
442             ,   enabled = true;  //True -> tooltips are rendered. False -> don't render tooltips.
443
444         //Generates a unique id when you create a new tooltip() object
445         var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
446
447         //CSS class to specify whether element should not have mouse events.
448         var  nvPointerEventsClass = "nv-pointer-events-none";
449
450         //Format function for the tooltip values column
451         var valueFormatter = function(d,i) {
452             return d;
453         };
454
455         //Format function for the tooltip header value.
456         var headerFormatter = function(d) {
457             return d;
458         };
459
460         //By default, the tooltip model renders a beautiful table inside a DIV.
461         //You can override this function if a custom tooltip is desired.
462         var contentGenerator = function(d) {
463             if (content != null) {
464                 return content;
465             }
466
467             if (d == null) {
468                 return '';
469             }
470
471             var table = d3.select(document.createElement("table"));
472             var theadEnter = table.selectAll("thead")
473                 .data([d])
474                 .enter().append("thead");
475
476             theadEnter.append("tr")
477                 .append("td")
478                 .attr("colspan",3)
479                 .append("strong")
480                 .classed("x-value",true)
481                 .html(headerFormatter(d.value));
482
483             var tbodyEnter = table.selectAll("tbody")
484                 .data([d])
485                 .enter().append("tbody");
486
487             var trowEnter = tbodyEnter.selectAll("tr")
488                     .data(function(p) { return p.series})
489                     .enter()
490                     .append("tr")
491                     .classed("highlight", function(p) { return p.highlight});
492
493             trowEnter.append("td")
494                 .classed("legend-color-guide",true)
495                 .append("div")
496                 .style("background-color", function(p) { return p.color});
497
498             trowEnter.append("td")
499                 .classed("key",true)
500                 .html(function(p) {return p.key});
501
502             trowEnter.append("td")
503                 .classed("value",true)
504                 .html(function(p,i) { return valueFormatter(p.value,i) });
505
506
507             trowEnter.selectAll("td").each(function(p) {
508                 if (p.highlight) {
509                     var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
510                     var opacity = 0.6;
511                     d3.select(this)
512                         .style("border-bottom-color", opacityScale(opacity))
513                         .style("border-top-color", opacityScale(opacity))
514                     ;
515                 }
516             });
517
518             var html = table.node().outerHTML;
519             if (d.footer !== undefined)
520                 html += "<div class='footer'>" + d.footer + "</div>";
521             return html;
522
523         };
524
525         var dataSeriesExists = function(d) {
526             if (d && d.series && d.series.length > 0) {
527                 return true;
528             }
529             return false;
530         };
531
532         //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
533         function convertViewBoxRatio() {
534             if (chartContainer) {
535                 var svg = d3.select(chartContainer);
536                 if (svg.node().tagName !== "svg") {
537                     svg = svg.select("svg");
538                 }
539                 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
540                 if (viewBox) {
541                     viewBox = viewBox.split(' ');
542                     var ratio = parseInt(svg.style('width')) / viewBox[2];
543
544                     position.left = position.left * ratio;
545                     position.top  = position.top * ratio;
546                 }
547             }
548         }
549
550         //Creates new tooltip container, or uses existing one on DOM.
551         function getTooltipContainer(newContent) {
552             var body;
553             if (chartContainer) {
554                 body = d3.select(chartContainer);
555             } else {
556                 body = d3.select("body");
557             }
558
559             var container = body.select(".nvtooltip");
560             if (container.node() === null) {
561                 //Create new tooltip div if it doesn't exist on DOM.
562                 container = body.append("div")
563                     .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
564                     .attr("id",id)
565                 ;
566             }
567
568             container.node().innerHTML = newContent;
569             container.style("top",0).style("left",0).style("opacity",0);
570             container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
571             container.classed(nvPointerEventsClass,true);
572             return container.node();
573         }
574
575         //Draw the tooltip onto the DOM.
576         function nvtooltip() {
577             if (!enabled) return;
578             if (!dataSeriesExists(data)) return;
579
580             convertViewBoxRatio();
581
582             var left = position.left;
583             var top = (fixedTop != null) ? fixedTop : position.top;
584             var container = getTooltipContainer(contentGenerator(data));
585             tooltipElem = container;
586             if (chartContainer) {
587                 var svgComp = chartContainer.getElementsByTagName("svg")[0];
588                 var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
589                 var svgOffset = {left:0,top:0};
590                 if (svgComp) {
591                     var svgBound = svgComp.getBoundingClientRect();
592                     var chartBound = chartContainer.getBoundingClientRect();
593                     var svgBoundTop = svgBound.top;
594
595                     //Defensive code. Sometimes, svgBoundTop can be a really negative
596                     //  number, like -134254. That's a bug.
597                     //  If such a number is found, use zero instead. FireFox bug only
598                     if (svgBoundTop < 0) {
599                         var containerBound = chartContainer.getBoundingClientRect();
600                         svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
601                     }
602                     svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
603                     svgOffset.left = Math.abs(svgBound.left - chartBound.left);
604                 }
605                 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
606                 //You need to also add any offset between the <svg> element and its containing <div>
607                 //Finally, add any offset of the containing <div> on the whole page.
608                 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
609                 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
610             }
611
612             if (snapDistance && snapDistance > 0) {
613                 top = Math.floor(top/snapDistance) * snapDistance;
614             }
615
616             nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
617             return nvtooltip;
618         }
619
620         nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
621
622         nvtooltip.content = function(_) {
623             if (!arguments.length) return content;
624             content = _;
625             return nvtooltip;
626         };
627
628         //Returns tooltipElem...not able to set it.
629         nvtooltip.tooltipElem = function() {
630             return tooltipElem;
631         };
632
633         nvtooltip.contentGenerator = function(_) {
634             if (!arguments.length) return contentGenerator;
635             if (typeof _ === 'function') {
636                 contentGenerator = _;
637             }
638             return nvtooltip;
639         };
640
641         nvtooltip.data = function(_) {
642             if (!arguments.length) return data;
643             data = _;
644             return nvtooltip;
645         };
646
647         nvtooltip.gravity = function(_) {
648             if (!arguments.length) return gravity;
649             gravity = _;
650             return nvtooltip;
651         };
652
653         nvtooltip.distance = function(_) {
654             if (!arguments.length) return distance;
655             distance = _;
656             return nvtooltip;
657         };
658
659         nvtooltip.snapDistance = function(_) {
660             if (!arguments.length) return snapDistance;
661             snapDistance = _;
662             return nvtooltip;
663         };
664
665         nvtooltip.classes = function(_) {
666             if (!arguments.length) return classes;
667             classes = _;
668             return nvtooltip;
669         };
670
671         nvtooltip.chartContainer = function(_) {
672             if (!arguments.length) return chartContainer;
673             chartContainer = _;
674             return nvtooltip;
675         };
676
677         nvtooltip.position = function(_) {
678             if (!arguments.length) return position;
679             position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
680             position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
681             return nvtooltip;
682         };
683
684         nvtooltip.fixedTop = function(_) {
685             if (!arguments.length) return fixedTop;
686             fixedTop = _;
687             return nvtooltip;
688         };
689
690         nvtooltip.enabled = function(_) {
691             if (!arguments.length) return enabled;
692             enabled = _;
693             return nvtooltip;
694         };
695
696         nvtooltip.valueFormatter = function(_) {
697             if (!arguments.length) return valueFormatter;
698             if (typeof _ === 'function') {
699                 valueFormatter = _;
700             }
701             return nvtooltip;
702         };
703
704         nvtooltip.headerFormatter = function(_) {
705             if (!arguments.length) return headerFormatter;
706             if (typeof _ === 'function') {
707                 headerFormatter = _;
708             }
709             return nvtooltip;
710         };
711
712         //id() is a read-only function. You can't use it to set the id.
713         nvtooltip.id = function() {
714             return id;
715         };
716
717         return nvtooltip;
718     };
719
720     //Original tooltip.show function. Kept for backward compatibility.
721     // pos = [left,top]
722     nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
723
724         //Create new tooltip div if it doesn't exist on DOM.
725         var   container = document.createElement('div');
726         container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
727
728         var body = parentContainer;
729         if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
730             //If the parent element is an SVG element, place tooltip in the <body> element.
731             body = document.getElementsByTagName('body')[0];
732         }
733
734         container.style.left = 0;
735         container.style.top = 0;
736         container.style.opacity = 0;
737         // Content can also be dom element
738         if (typeof content !== 'string')
739             container.appendChild(content);
740         else
741             container.innerHTML = content;
742         body.appendChild(container);
743
744         //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
745         if (parentContainer) {
746             pos[0] = pos[0] - parentContainer.scrollLeft;
747             pos[1] = pos[1] - parentContainer.scrollTop;
748         }
749         nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
750     };
751
752     //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
753     nv.tooltip.findFirstNonSVGParent = function(Elem) {
754         while(Elem.tagName.match(/^g|svg$/i) !== null) {
755             Elem = Elem.parentNode;
756         }
757         return Elem;
758     };
759
760     //Finds the total offsetTop of a given DOM element.
761     //Looks up the entire ancestry of an element, up to the first relatively positioned element.
762     nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
763         var offsetTop = initialTop;
764
765         do {
766             if( !isNaN( Elem.offsetTop ) ) {
767                 offsetTop += (Elem.offsetTop);
768             }
769         } while( Elem = Elem.offsetParent );
770         return offsetTop;
771     };
772
773     //Finds the total offsetLeft of a given DOM element.
774     //Looks up the entire ancestry of an element, up to the first relatively positioned element.
775     nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
776         var offsetLeft = initialLeft;
777
778         do {
779             if( !isNaN( Elem.offsetLeft ) ) {
780                 offsetLeft += (Elem.offsetLeft);
781             }
782         } while( Elem = Elem.offsetParent );
783         return offsetLeft;
784     };
785
786     //Global utility function to render a tooltip on the DOM.
787     //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
788     //gravity = how to orient the tooltip
789     //dist = how far away from the mouse to place tooltip
790     //container = tooltip DIV
791     nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
792
793         var height = parseInt(container.offsetHeight),
794             width = parseInt(container.offsetWidth),
795             windowWidth = nv.utils.windowSize().width,
796             windowHeight = nv.utils.windowSize().height,
797             scrollTop = window.pageYOffset,
798             scrollLeft = window.pageXOffset,
799             left, top;
800
801         windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
802         windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
803
804         gravity = gravity || 's';
805         dist = dist || 20;
806
807         var tooltipTop = function ( Elem ) {
808             return nv.tooltip.findTotalOffsetTop(Elem, top);
809         };
810
811         var tooltipLeft = function ( Elem ) {
812             return nv.tooltip.findTotalOffsetLeft(Elem,left);
813         };
814
815         switch (gravity) {
816             case 'e':
817                 left = pos[0] - width - dist;
818                 top = pos[1] - (height / 2);
819                 var tLeft = tooltipLeft(container);
820                 var tTop = tooltipTop(container);
821                 if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
822                 if (tTop < scrollTop) top = scrollTop - tTop + top;
823                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
824                 break;
825             case 'w':
826                 left = pos[0] + dist;
827                 top = pos[1] - (height / 2);
828                 var tLeft = tooltipLeft(container);
829                 var tTop = tooltipTop(container);
830                 if (tLeft + width > windowWidth) left = pos[0] - width - dist;
831                 if (tTop < scrollTop) top = scrollTop + 5;
832                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
833                 break;
834             case 'n':
835                 left = pos[0] - (width / 2) - 5;
836                 top = pos[1] + dist;
837                 var tLeft = tooltipLeft(container);
838                 var tTop = tooltipTop(container);
839                 if (tLeft < scrollLeft) left = scrollLeft + 5;
840                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
841                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
842                 break;
843             case 's':
844                 left = pos[0] - (width / 2);
845                 top = pos[1] - height - dist;
846                 var tLeft = tooltipLeft(container);
847                 var tTop = tooltipTop(container);
848                 if (tLeft < scrollLeft) left = scrollLeft + 5;
849                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
850                 if (scrollTop > tTop) top = scrollTop;
851                 break;
852             case 'none':
853                 left = pos[0];
854                 top = pos[1] - dist;
855                 var tLeft = tooltipLeft(container);
856                 var tTop = tooltipTop(container);
857                 break;
858         }
859
860         container.style.left = left+'px';
861         container.style.top = top+'px';
862         container.style.opacity = 1;
863         container.style.position = 'absolute';
864
865         return container;
866     };
867
868     //Global utility function to remove tooltips from the DOM.
869     nv.tooltip.cleanup = function() {
870
871         // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
872         var tooltips = document.getElementsByClassName('nvtooltip');
873         var purging = [];
874         while(tooltips.length) {
875             purging.push(tooltips[0]);
876             tooltips[0].style.transitionDelay = '0 !important';
877             tooltips[0].style.opacity = 0;
878             tooltips[0].className = 'nvtooltip-pending-removal';
879         }
880
881         setTimeout(function() {
882
883             while (purging.length) {
884                 var removeMe = purging.pop();
885                 removeMe.parentNode.removeChild(removeMe);
886             }
887         }, 500);
888     };
889
890 })();
891
892
893 /*
894 Gets the browser window size
895
896 Returns object with height and width properties
897  */
898 nv.utils.windowSize = function() {
899     // Sane defaults
900     var size = {width: 640, height: 480};
901
902     // Earlier IE uses Doc.body
903     if (document.body && document.body.offsetWidth) {
904         size.width = document.body.offsetWidth;
905         size.height = document.body.offsetHeight;
906     }
907
908     // IE can use depending on mode it is in
909     if (document.compatMode=='CSS1Compat' &&
910         document.documentElement &&
911         document.documentElement.offsetWidth ) {
912
913         size.width = document.documentElement.offsetWidth;
914         size.height = document.documentElement.offsetHeight;
915     }
916
917     // Most recent browsers use
918     if (window.innerWidth && window.innerHeight) {
919         size.width = window.innerWidth;
920         size.height = window.innerHeight;
921     }
922     return (size);
923 };
924
925
926 /*
927 Binds callback function to run when window is resized
928  */
929 nv.utils.windowResize = function(handler) {
930     if (window.addEventListener) {
931         window.addEventListener('resize', handler);
932     } else {
933         nv.log("ERROR: Failed to bind to window.resize with: ", handler);
934     }
935     // return object with clear function to remove the single added callback.
936     return {
937         callback: handler,
938         clear: function() {
939             window.removeEventListener('resize', handler);
940         }
941     }
942 };
943
944
945 /*
946 Backwards compatible way to implement more d3-like coloring of graphs.
947 If passed an array, wrap it in a function which implements the old behavior
948 */
949 nv.utils.getColor = function(color) {
950     //if you pass in nothing or not an array, get default colors back
951     if (!arguments.length || !(color instanceof Array)) {
952         return nv.utils.defaultColor();
953     } else {
954         return function (d, i) {
955             return d.color || color[i % color.length];
956         };
957     }
958 };
959
960
961 /*
962 Default color chooser uses the index of an object as before.
963  */
964 nv.utils.defaultColor = function() {
965     var colors = d3.scale.category20().range();
966     return function(d, i) {
967         return d.color || colors[i % colors.length]
968     };
969 };
970
971
972 /*
973 Returns a color function that takes the result of 'getKey' for each series and
974 looks for a corresponding color from the dictionary
975 */
976 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
977     // use default series.key if getKey is undefined
978     getKey = getKey || function(series) { return series.key };
979     defaultColors = defaultColors || d3.scale.category20().range();
980
981     // start at end of default color list and walk back to index 0
982     var defIndex = defaultColors.length;
983
984     return function(series, index) {
985         var key = getKey(series);
986         if (typeof dictionary[key] === 'function') {
987             return dictionary[key]();
988         } else if (dictionary[key] !== undefined) {
989             return dictionary[key];
990         } else {
991             // no match in dictionary, use a default color
992             if (!defIndex) {
993                 // used all the default colors, start over
994                 defIndex = defaultColors.length;
995             }
996             defIndex = defIndex - 1;
997             return defaultColors[defIndex];
998         }
999     };
1000 };
1001
1002
1003 /*
1004 From the PJAX example on d3js.org, while this is not really directly needed
1005 it's a very cool method for doing pjax, I may expand upon it a little bit,
1006 open to suggestions on anything that may be useful
1007 */
1008 nv.utils.pjax = function(links, content) {
1009
1010     var load = function(href) {
1011         d3.html(href, function(fragment) {
1012             var target = d3.select(content).node();
1013             target.parentNode.replaceChild(
1014                 d3.select(fragment).select(content).node(),
1015                 target);
1016             nv.utils.pjax(links, content);
1017         });
1018     };
1019
1020     d3.selectAll(links).on("click", function() {
1021         history.pushState(this.href, this.textContent, this.href);
1022         load(this.href);
1023         d3.event.preventDefault();
1024     });
1025
1026     d3.select(window).on("popstate", function() {
1027         if (d3.event.state) {
1028             load(d3.event.state);
1029         }
1030     });
1031 };
1032
1033
1034 /*
1035 For when we want to approximate the width in pixels for an SVG:text element.
1036 Most common instance is when the element is in a display:none; container.
1037 Forumla is : text.length * font-size * constant_factor
1038 */
1039 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1040     if (typeof svgTextElem.style === 'function'
1041         && typeof svgTextElem.text === 'function') {
1042
1043         var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
1044         var textLength = svgTextElem.text().length;
1045         return textLength * fontSize * 0.5;
1046     }
1047     return 0;
1048 };
1049
1050
1051 /*
1052 Numbers that are undefined, null or NaN, convert them to zeros.
1053 */
1054 nv.utils.NaNtoZero = function(n) {
1055     if (typeof n !== 'number'
1056         || isNaN(n)
1057         || n === null
1058         || n === Infinity
1059         || n === -Infinity) {
1060
1061         return 0;
1062     }
1063     return n;
1064 };
1065
1066 /*
1067 Add a way to watch for d3 transition ends to d3
1068 */
1069 d3.selection.prototype.watchTransition = function(renderWatch){
1070     var args = [this].concat([].slice.call(arguments, 1));
1071     return renderWatch.transition.apply(renderWatch, args);
1072 };
1073
1074
1075 /*
1076 Helper object to watch when d3 has rendered something
1077 */
1078 nv.utils.renderWatch = function(dispatch, duration) {
1079     if (!(this instanceof nv.utils.renderWatch)) {
1080         return new nv.utils.renderWatch(dispatch, duration);
1081     }
1082
1083     var _duration = duration !== undefined ? duration : 250;
1084     var renderStack = [];
1085     var self = this;
1086
1087     this.models = function(models) {
1088         models = [].slice.call(arguments, 0);
1089         models.forEach(function(model){
1090             model.__rendered = false;
1091             (function(m){
1092                 m.dispatch.on('renderEnd', function(arg){
1093                     m.__rendered = true;
1094                     self.renderEnd('model');
1095                 });
1096             })(model);
1097
1098             if (renderStack.indexOf(model) < 0) {
1099                 renderStack.push(model);
1100             }
1101         });
1102     return this;
1103     };
1104
1105     this.reset = function(duration) {
1106         if (duration !== undefined) {
1107             _duration = duration;
1108         }
1109         renderStack = [];
1110     };
1111
1112     this.transition = function(selection, args, duration) {
1113         args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1114
1115         if (args.length > 1) {
1116             duration = args.pop();
1117         } else {
1118             duration = _duration !== undefined ? _duration : 250;
1119         }
1120         selection.__rendered = false;
1121
1122         if (renderStack.indexOf(selection) < 0) {
1123             renderStack.push(selection);
1124         }
1125
1126         if (duration === 0) {
1127             selection.__rendered = true;
1128             selection.delay = function() { return this; };
1129             selection.duration = function() { return this; };
1130             return selection;
1131         } else {
1132             if (selection.length === 0) {
1133                 selection.__rendered = true;
1134             } else if (selection.every( function(d){ return !d.length; } )) {
1135                 selection.__rendered = true;
1136             } else {
1137                 selection.__rendered = false;
1138             }
1139
1140             var n = 0;
1141             return selection
1142                 .transition()
1143                 .duration(duration)
1144                 .each(function(){ ++n; })
1145                 .each('end', function(d, i) {
1146                     if (--n === 0) {
1147                         selection.__rendered = true;
1148                         self.renderEnd.apply(this, args);
1149                     }
1150                 });
1151         }
1152     };
1153
1154     this.renderEnd = function() {
1155         if (renderStack.every( function(d){ return d.__rendered; } )) {
1156             renderStack.forEach( function(d){ d.__rendered = false; });
1157             dispatch.renderEnd.apply(this, arguments);
1158         }
1159     }
1160
1161 };
1162
1163
1164 /*
1165 Takes multiple objects and combines them into the first one (dst)
1166 example:  nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1167 gives:  {a: 2, b: 3, c: 4}
1168 */
1169 nv.utils.deepExtend = function(dst){
1170     var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1171     sources.forEach(function(source) {
1172         for (key in source) {
1173             var isArray = dst[key] instanceof Array;
1174             var isObject = typeof dst[key] === 'object';
1175             var srcObj = typeof source[key] === 'object';
1176
1177             if (isObject && !isArray && srcObj) {
1178                 nv.utils.deepExtend(dst[key], source[key]);
1179             } else {
1180                 dst[key] = source[key];
1181             }
1182         }
1183     });
1184 };
1185
1186
1187 /*
1188 state utility object, used to track d3 states in the models
1189 */
1190 nv.utils.state = function(){
1191     if (!(this instanceof nv.utils.state)) {
1192         return new nv.utils.state();
1193     }
1194     var state = {};
1195     var _self = this;
1196     var _setState = function(){};
1197     var _getState = function(){ return {}; };
1198     var init = null;
1199     var changed = null;
1200
1201     this.dispatch = d3.dispatch('change', 'set');
1202
1203     this.dispatch.on('set', function(state){
1204         _setState(state, true);
1205     });
1206
1207     this.getter = function(fn){
1208         _getState = fn;
1209         return this;
1210     };
1211
1212     this.setter = function(fn, callback) {
1213         if (!callback) {
1214             callback = function(){};
1215         }
1216         _setState = function(state, update){
1217             fn(state);
1218             if (update) {
1219                 callback();
1220             }
1221         };
1222         return this;
1223     };
1224
1225     this.init = function(state){
1226         init = init || {};
1227         nv.utils.deepExtend(init, state);
1228     };
1229
1230     var _set = function(){
1231         var settings = _getState();
1232
1233         if (JSON.stringify(settings) === JSON.stringify(state)) {
1234             return false;
1235         }
1236
1237         for (var key in settings) {
1238             if (state[key] === undefined) {
1239                 state[key] = {};
1240             }
1241             state[key] = settings[key];
1242             changed = true;
1243         }
1244         return true;
1245     };
1246
1247     this.update = function(){
1248         if (init) {
1249             _setState(init, false);
1250             init = null;
1251         }
1252         if (_set.call(this)) {
1253             this.dispatch.change(state);
1254         }
1255     };
1256
1257 };
1258
1259
1260 /*
1261 Snippet of code you can insert into each nv.models.* to give you the ability to
1262 do things like:
1263 chart.options({
1264   showXAxis: true,
1265   tooltips: true
1266 });
1267
1268 To enable in the chart:
1269 chart.options = nv.utils.optionsFunc.bind(chart);
1270 */
1271 nv.utils.optionsFunc = function(args) {
1272     nv.deprecated('nv.utils.optionsFunc');
1273     if (args) {
1274         d3.map(args).forEach((function(key,value) {
1275             if (typeof this[key] === "function") {
1276                 this[key](value);
1277             }
1278         }).bind(this));
1279     }
1280     return this;
1281 };
1282
1283
1284 /*
1285 numTicks:  requested number of ticks
1286 data:  the chart data
1287
1288 returns the number of ticks to actually use on X axis, based on chart data
1289 to avoid duplicate ticks with the same value
1290 */
1291 nv.utils.calcTicksX = function(numTicks, data) {
1292     // find max number of values from all data streams
1293     var numValues = 1;
1294     var i = 0;
1295     for (i; i < data.length; i += 1) {
1296         var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1297         numValues = stream_len > numValues ? stream_len : numValues;
1298     }
1299     nv.log("Requested number of ticks: ", numTicks);
1300     nv.log("Calculated max values to be: ", numValues);
1301     // make sure we don't have more ticks than values to avoid duplicates
1302     numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1303     // make sure we have at least one tick
1304     numTicks = numTicks < 1 ? 1 : numTicks;
1305     // make sure it's an integer
1306     numTicks = Math.floor(numTicks);
1307     nv.log("Calculating tick count as: ", numTicks);
1308     return numTicks;
1309 };
1310
1311
1312 /*
1313 returns number of ticks to actually use on Y axis, based on chart data
1314 */
1315 nv.utils.calcTicksY = function(numTicks, data) {
1316     // currently uses the same logic but we can adjust here if needed later
1317     return nv.utils.calcTicksX(numTicks, data);
1318 };
1319
1320
1321 /*
1322 Add a particular option from an options object onto chart
1323 Options exposed on a chart are a getter/setter function that returns chart
1324 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1325
1326 option objects should be generated via Object.create() to provide
1327 the option of manipulating data via get/set functions.
1328 */
1329 nv.utils.initOption = function(chart, name) {
1330     chart[name] = function (_) {
1331         if (!arguments.length) return chart._options[name];
1332         chart._options[name] = _;
1333         return chart;
1334     };
1335 };
1336
1337
1338 /*
1339 Add all options in an options object to the chart
1340 */
1341 nv.utils.initOptions = function(chart) {
1342     var ops = Object.getOwnPropertyNames(chart._options);
1343     for (var i in ops) {
1344         nv.utils.initOption(chart, ops[i]);
1345     }
1346 };
1347
1348
1349 /*
1350 Inherit option getter/setter functions from source to target
1351 d3.rebind makes calling the function on target actually call it on source
1352 */
1353 nv.utils.inheritOptions = function(target, source) {
1354     var args = Object.getOwnPropertyNames(source._options);
1355     args.unshift(source);
1356     args.unshift(target);
1357     d3.rebind.apply(this, args);
1358 };
1359
1360
1361 /*
1362 Runs common initialize code on the svg before the chart builds
1363 */
1364 nv.utils.initSVG = function(svg) {
1365     svg.classed({'nvd3-svg':true});
1366 };nv.models.axis = function() {
1367     "use strict";
1368     //============================================================
1369     // Public Variables with Default Settings
1370     //------------------------------------------------------------
1371
1372     var axis = d3.svg.axis();
1373
1374     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1375         , width = 75 //only used for tickLabel currently
1376         , height = 60 //only used for tickLabel currently
1377         , scale = d3.scale.linear()
1378         , axisLabelText = null
1379         , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1380         , highlightZero = true
1381         , rotateLabels = 0
1382         , rotateYLabel = true
1383         , staggerLabels = false
1384         , isOrdinal = false
1385         , ticks = null
1386         , axisLabelDistance = 0
1387         , duration = 250
1388         , dispatch = d3.dispatch('renderEnd')
1389         , axisRendered = false
1390         , maxMinRendered = false
1391         ;
1392     axis
1393         .scale(scale)
1394         .orient('bottom')
1395         .tickFormat(function(d) { return d })
1396     ;
1397
1398     //============================================================
1399
1400
1401     //============================================================
1402     // Private Variables
1403     //------------------------------------------------------------
1404
1405     var scale0
1406         , renderWatch = nv.utils.renderWatch(dispatch, duration)
1407         ;
1408
1409     //============================================================
1410
1411     function chart(selection) {
1412         renderWatch.reset();
1413         selection.each(function(data) {
1414             var container = d3.select(this);
1415             nv.utils.initSVG(container);
1416
1417             //------------------------------------------------------------
1418             // Setup containers and skeleton of chart
1419
1420             var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1421             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1422             var gEnter = wrapEnter.append('g');
1423             var g = wrap.select('g')
1424
1425             //------------------------------------------------------------
1426
1427
1428             if (ticks !== null)
1429                 axis.ticks(ticks);
1430             else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1431                 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1432
1433
1434             //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1435             g.watchTransition(renderWatch, 'axis').call(axis);
1436
1437             scale0 = scale0 || axis.scale();
1438
1439             var fmt = axis.tickFormat();
1440             if (fmt == null) {
1441                 fmt = scale0.tickFormat();
1442             }
1443
1444             var axisLabel = g.selectAll('text.nv-axislabel')
1445                 .data([axisLabelText || null]);
1446             axisLabel.exit().remove();
1447             switch (axis.orient()) {
1448                 case 'top':
1449                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1450                     var w;
1451                     if (scale.range().length < 2) {
1452                         w = 0;
1453                     } else if (scale.range().length === 2) {
1454                         w = scale.range()[1];
1455                     } else {
1456                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1457                     }
1458                     axisLabel
1459                         .attr('text-anchor', 'middle')
1460                         .attr('y', 0)
1461                         .attr('x', w/2);
1462                     if (showMaxMin) {
1463                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1464                             .data(scale.domain());
1465                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1466                         axisMaxMin.exit().remove();
1467                         axisMaxMin
1468                             .attr('transform', function(d,i) {
1469                                 return 'translate(' + scale(d) + ',0)'
1470                             })
1471                             .select('text')
1472                             .attr('dy', '-0.5em')
1473                             .attr('y', -axis.tickPadding())
1474                             .attr('text-anchor', 'middle')
1475                             .text(function(d,i) {
1476                                 var v = fmt(d);
1477                                 return ('' + v).match('NaN') ? '' : v;
1478                             });
1479                         axisMaxMin.watchTransition(renderWatch, 'min-max top')
1480                             .attr('transform', function(d,i) {
1481                                 return 'translate(' + scale.range()[i] + ',0)'
1482                             });
1483                     }
1484                     break;
1485                 case 'bottom':
1486                     var xLabelMargin = axisLabelDistance + 36;
1487                     var maxTextWidth = 30;
1488                     var xTicks = g.selectAll('g').select("text");
1489                     if (rotateLabels%360) {
1490                         //Calculate the longest xTick width
1491                         xTicks.each(function(d,i){
1492                             var width = this.getBoundingClientRect().width;
1493                             if(width > maxTextWidth) maxTextWidth = width;
1494                         });
1495                         //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1496                         var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1497                         var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1498                         //Rotate all xTicks
1499                         xTicks
1500                             .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1501                             .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1502                     }
1503                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1504                     var w;
1505                     if (scale.range().length < 2) {
1506                         w = 0;
1507                     } else if (scale.range().length === 2) {
1508                         w = scale.range()[1];
1509                     } else {
1510                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1511                     }
1512                     axisLabel
1513                         .attr('text-anchor', 'middle')
1514                         .attr('y', xLabelMargin)
1515                         .attr('x', w/2);
1516                     if (showMaxMin) {
1517                         //if (showMaxMin && !isOrdinal) {
1518                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1519                             //.data(scale.domain())
1520                             .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1521                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1522                         axisMaxMin.exit().remove();
1523                         axisMaxMin
1524                             .attr('transform', function(d,i) {
1525                                 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1526                             })
1527                             .select('text')
1528                             .attr('dy', '.71em')
1529                             .attr('y', axis.tickPadding())
1530                             .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1531                             .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1532                             .text(function(d,i) {
1533                                 var v = fmt(d);
1534                                 return ('' + v).match('NaN') ? '' : v;
1535                             });
1536                         axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1537                             .attr('transform', function(d,i) {
1538                                 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1539                             });
1540                     }
1541                     if (staggerLabels)
1542                         xTicks
1543                             .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1544
1545                     break;
1546                 case 'right':
1547                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1548                     axisLabel
1549                         .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1550                         .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1551                         .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1552                         .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1553                     if (showMaxMin) {
1554                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1555                             .data(scale.domain());
1556                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1557                             .style('opacity', 0);
1558                         axisMaxMin.exit().remove();
1559                         axisMaxMin
1560                             .attr('transform', function(d,i) {
1561                                 return 'translate(0,' + scale(d) + ')'
1562                             })
1563                             .select('text')
1564                             .attr('dy', '.32em')
1565                             .attr('y', 0)
1566                             .attr('x', axis.tickPadding())
1567                             .style('text-anchor', 'start')
1568                             .text(function(d,i) {
1569                                 var v = fmt(d);
1570                                 return ('' + v).match('NaN') ? '' : v;
1571                             });
1572                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1573                             .attr('transform', function(d,i) {
1574                                 return 'translate(0,' + scale.range()[i] + ')'
1575                             })
1576                             .select('text')
1577                             .style('opacity', 1);
1578                     }
1579                     break;
1580                 case 'left':
1581                     /*
1582                      //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1583                      var yTicks = g.selectAll('g').select("text");
1584                      yTicks.each(function(d,i){
1585                      var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1586                      if(labelPadding > width) width = labelPadding;
1587                      });
1588                      */
1589                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1590                     axisLabel
1591                         .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1592                         .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1593                         .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 25 - (axisLabelDistance || 0)) : -10)
1594                         .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1595                     if (showMaxMin) {
1596                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1597                             .data(scale.domain());
1598                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1599                             .style('opacity', 0);
1600                         axisMaxMin.exit().remove();
1601                         axisMaxMin
1602                             .attr('transform', function(d,i) {
1603                                 return 'translate(0,' + scale0(d) + ')'
1604                             })
1605                             .select('text')
1606                             .attr('dy', '.32em')
1607                             .attr('y', 0)
1608                             .attr('x', -axis.tickPadding())
1609                             .attr('text-anchor', 'end')
1610                             .text(function(d,i) {
1611                                 var v = fmt(d);
1612                                 return ('' + v).match('NaN') ? '' : v;
1613                             });
1614                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1615                             .attr('transform', function(d,i) {
1616                                 return 'translate(0,' + scale.range()[i] + ')'
1617                             })
1618                             .select('text')
1619                             .style('opacity', 1);
1620                     }
1621                     break;
1622             }
1623             axisLabel
1624                 .text(function(d) { return d });
1625
1626
1627             if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1628                 //check if max and min overlap other values, if so, hide the values that overlap
1629                 g.selectAll('g') // the g's wrapping each tick
1630                     .each(function(d,i) {
1631                         d3.select(this).select('text').attr('opacity', 1);
1632                         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!
1633                             if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1634                                 d3.select(this).attr('opacity', 0);
1635
1636                             d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1637                         }
1638                     });
1639
1640                 //if Max and Min = 0 only show min, Issue #281
1641                 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1642                     wrap.selectAll('g.nv-axisMaxMin')
1643                         .style('opacity', function(d,i) { return !i ? 1 : 0 });
1644
1645             }
1646
1647             if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1648                 var maxMinRange = [];
1649                 wrap.selectAll('g.nv-axisMaxMin')
1650                     .each(function(d,i) {
1651                         try {
1652                             if (i) // i== 1, max position
1653                                 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)
1654                             else // i==0, min position
1655                                 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1656                         }catch (err) {
1657                             if (i) // i== 1, max position
1658                                 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)
1659                             else // i==0, min position
1660                                 maxMinRange.push(scale(d) + 4)
1661                         }
1662                     });
1663                 g.selectAll('g') // the g's wrapping each tick
1664                     .each(function(d,i) {
1665                         if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1666                             if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1667                                 d3.select(this).remove();
1668                             else
1669                                 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1670                         }
1671                     });
1672             }
1673
1674
1675             //highlight zero line ... Maybe should not be an option and should just be in CSS?
1676             if (highlightZero)
1677                 g.selectAll('.tick')
1678                     .filter(function(d) { return !parseFloat(Math.round(this.__data__*100000)/1000000) && (this.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1679                     .classed('zero', true);
1680
1681             //store old scales for use in transitions on update
1682             scale0 = scale.copy();
1683
1684         });
1685
1686         renderWatch.renderEnd('axis immediate');
1687         return chart;
1688     }
1689
1690
1691     //============================================================
1692     // Expose Public Variables
1693     //------------------------------------------------------------
1694
1695     // expose chart's sub-components
1696     chart.axis = axis;
1697     chart.dispatch = dispatch;
1698
1699     d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1700     d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1701
1702     chart.options = nv.utils.optionsFunc.bind(chart);
1703
1704     chart.margin = function(_) {
1705         if(!arguments.length) return margin;
1706         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
1707         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
1708         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1709         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
1710         return chart;
1711     }
1712
1713     chart.width = function(_) {
1714         if (!arguments.length) return width;
1715         width = _;
1716         return chart;
1717     };
1718
1719     chart.ticks = function(_) {
1720         if (!arguments.length) return ticks;
1721         ticks = _;
1722         return chart;
1723     };
1724
1725     chart.height = function(_) {
1726         if (!arguments.length) return height;
1727         height = _;
1728         return chart;
1729     };
1730
1731     chart.axisLabel = function(_) {
1732         if (!arguments.length) return axisLabelText;
1733         axisLabelText = _;
1734         return chart;
1735     }
1736
1737     chart.showMaxMin = function(_) {
1738         if (!arguments.length) return showMaxMin;
1739         showMaxMin = _;
1740         return chart;
1741     }
1742
1743     chart.highlightZero = function(_) {
1744         if (!arguments.length) return highlightZero;
1745         highlightZero = _;
1746         return chart;
1747     }
1748
1749     chart.scale = function(_) {
1750         if (!arguments.length) return scale;
1751         scale = _;
1752         axis.scale(scale);
1753         isOrdinal = typeof scale.rangeBands === 'function';
1754         d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1755         return chart;
1756     }
1757
1758     chart.rotateYLabel = function(_) {
1759         if(!arguments.length) return rotateYLabel;
1760         rotateYLabel = _;
1761         return chart;
1762     }
1763
1764     chart.rotateLabels = function(_) {
1765         if(!arguments.length) return rotateLabels;
1766         rotateLabels = _;
1767         return chart;
1768     };
1769
1770     chart.staggerLabels = function(_) {
1771         if (!arguments.length) return staggerLabels;
1772         staggerLabels = _;
1773         return chart;
1774     };
1775
1776     chart.axisLabelDistance = function(_) {
1777         if (!arguments.length) return axisLabelDistance;
1778         axisLabelDistance = _;
1779         return chart;
1780     };
1781
1782     chart.duration = function(_) {
1783         if (!arguments.length) return duration;
1784         duration = _;
1785         renderWatch.reset(duration);
1786         return chart;
1787     };
1788
1789
1790     //============================================================
1791
1792
1793     return chart;
1794 }
1795
1796 // Chart design based on the recommendations of Stephen Few. Implementation
1797 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1798 // http://projects.instantcognition.com/protovis/bulletchart/
1799
1800 nv.models.bullet = function() {
1801     "use strict";
1802     //============================================================
1803     // Public Variables with Default Settings
1804     //------------------------------------------------------------
1805
1806     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1807         , orient = 'left' // TODO top & bottom
1808         , reverse = false
1809         , ranges = function(d) { return d.ranges }
1810         , markers = function(d) { return d.markers ? d.markers : [0] }
1811         , measures = function(d) { return d.measures }
1812         , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1813         , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
1814         , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
1815         , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1816         , width = 380
1817         , height = 30
1818         , tickFormat = null
1819         , color = nv.utils.getColor(['#1f77b4'])
1820         , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1821         ;
1822
1823     //============================================================
1824
1825
1826     function chart(selection) {
1827         selection.each(function(d, i) {
1828             var availableWidth = width - margin.left - margin.right,
1829                 availableHeight = height - margin.top - margin.bottom,
1830                 container = d3.select(this);
1831             nv.utils.initSVG(container);
1832
1833             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1834                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1835                 measurez = measures.call(this, d, i).slice().sort(d3.descending),
1836                 rangeLabelz = rangeLabels.call(this, d, i).slice(),
1837                 markerLabelz = markerLabels.call(this, d, i).slice(),
1838                 measureLabelz = measureLabels.call(this, d, i).slice();
1839
1840
1841             //------------------------------------------------------------
1842             // Setup Scales
1843
1844             // Compute the new x-scale.
1845             var x1 = d3.scale.linear()
1846                 .domain( d3.extent(d3.merge([forceX, rangez])) )
1847                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1848
1849             // Retrieve the old x-scale, if this is an update.
1850             var x0 = this.__chart__ || d3.scale.linear()
1851                 .domain([0, Infinity])
1852                 .range(x1.range());
1853
1854             // Stash the new scale.
1855             this.__chart__ = x1;
1856
1857
1858             var rangeMin = d3.min(rangez), //rangez[2]
1859                 rangeMax = d3.max(rangez), //rangez[0]
1860                 rangeAvg = rangez[1];
1861
1862             //------------------------------------------------------------
1863
1864
1865             //------------------------------------------------------------
1866             // Setup containers and skeleton of chart
1867
1868             var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1869             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1870             var gEnter = wrapEnter.append('g');
1871             var g = wrap.select('g');
1872
1873             gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1874             gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1875             gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1876             gEnter.append('rect').attr('class', 'nv-measure');
1877             gEnter.append('path').attr('class', 'nv-markerTriangle');
1878
1879             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1880
1881             //------------------------------------------------------------
1882
1883
1884
1885             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1886                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1887             var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1888                 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1889
1890
1891             g.select('rect.nv-rangeMax')
1892                 .attr('height', availableHeight)
1893                 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1894                 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1895                 .datum(rangeMax > 0 ? rangeMax : rangeMin)
1896             /*
1897              .attr('x', rangeMin < 0 ?
1898              rangeMax > 0 ?
1899              x1(rangeMin)
1900              : x1(rangeMax)
1901              : x1(0))
1902              */
1903
1904             g.select('rect.nv-rangeAvg')
1905                 .attr('height', availableHeight)
1906                 .attr('width', w1(rangeAvg))
1907                 .attr('x', xp1(rangeAvg))
1908                 .datum(rangeAvg)
1909             /*
1910              .attr('width', rangeMax <= 0 ?
1911              x1(rangeMax) - x1(rangeAvg)
1912              : x1(rangeAvg) - x1(rangeMin))
1913              .attr('x', rangeMax <= 0 ?
1914              x1(rangeAvg)
1915              : x1(rangeMin))
1916              */
1917
1918             g.select('rect.nv-rangeMin')
1919                 .attr('height', availableHeight)
1920                 .attr('width', w1(rangeMax))
1921                 .attr('x', xp1(rangeMax))
1922                 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1923                 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1924                 .datum(rangeMax > 0 ? rangeMin : rangeMax)
1925             /*
1926              .attr('width', rangeMax <= 0 ?
1927              x1(rangeAvg) - x1(rangeMin)
1928              : x1(rangeMax) - x1(rangeAvg))
1929              .attr('x', rangeMax <= 0 ?
1930              x1(rangeMin)
1931              : x1(rangeAvg))
1932              */
1933
1934             g.select('rect.nv-measure')
1935                 .style('fill', color)
1936                 .attr('height', availableHeight / 3)
1937                 .attr('y', availableHeight / 3)
1938                 .attr('width', measurez < 0 ?
1939                     x1(0) - x1(measurez[0])
1940                     : x1(measurez[0]) - x1(0))
1941                 .attr('x', xp1(measurez))
1942                 .on('mouseover', function() {
1943                     dispatch.elementMouseover({
1944                         value: measurez[0],
1945                         label: measureLabelz[0] || 'Current',
1946                         pos: [x1(measurez[0]), availableHeight/2]
1947                     })
1948                 })
1949                 .on('mouseout', function() {
1950                     dispatch.elementMouseout({
1951                         value: measurez[0],
1952                         label: measureLabelz[0] || 'Current'
1953                     })
1954                 })
1955
1956             var h3 =  availableHeight / 6;
1957             if (markerz[0]) {
1958                 g.selectAll('path.nv-markerTriangle')
1959                     .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1960                     .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1961                     .on('mouseover', function() {
1962                         dispatch.elementMouseover({
1963                             value: markerz[0],
1964                             label: markerLabelz[0] || 'Previous',
1965                             pos: [x1(markerz[0]), availableHeight/2]
1966                         })
1967                     })
1968                     .on('mouseout', function() {
1969                         dispatch.elementMouseout({
1970                             value: markerz[0],
1971                             label: markerLabelz[0] || 'Previous'
1972                         })
1973                     });
1974             } else {
1975                 g.selectAll('path.nv-markerTriangle').remove();
1976             }
1977
1978
1979             wrap.selectAll('.nv-range')
1980                 .on('mouseover', function(d,i) {
1981                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1982
1983                     dispatch.elementMouseover({
1984                         value: d,
1985                         label: label,
1986                         pos: [x1(d), availableHeight/2]
1987                     })
1988                 })
1989                 .on('mouseout', function(d,i) {
1990                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1991
1992                     dispatch.elementMouseout({
1993                         value: d,
1994                         label: label
1995                     })
1996                 });
1997         });
1998
1999         // d3.timer.flush();  // Not needed?
2000
2001         return chart;
2002     }
2003
2004
2005     //============================================================
2006     // Expose Public Variables
2007     //------------------------------------------------------------
2008
2009     chart.dispatch = dispatch;
2010
2011     chart.options = nv.utils.optionsFunc.bind(chart);
2012
2013     // left, right, top, bottom
2014     chart.orient = function(_) {
2015         if (!arguments.length) return orient;
2016         orient = _;
2017         reverse = orient == 'right' || orient == 'bottom';
2018         return chart;
2019     };
2020
2021     // ranges (bad, satisfactory, good)
2022     chart.ranges = function(_) {
2023         if (!arguments.length) return ranges;
2024         ranges = _;
2025         return chart;
2026     };
2027
2028     // markers (previous, goal)
2029     chart.markers = function(_) {
2030         if (!arguments.length) return markers;
2031         markers = _;
2032         return chart;
2033     };
2034
2035     // measures (actual, forecast)
2036     chart.measures = function(_) {
2037         if (!arguments.length) return measures;
2038         measures = _;
2039         return chart;
2040     };
2041
2042     chart.forceX = function(_) {
2043         if (!arguments.length) return forceX;
2044         forceX = _;
2045         return chart;
2046     };
2047
2048     chart.width = function(_) {
2049         if (!arguments.length) return width;
2050         width = _;
2051         return chart;
2052     };
2053
2054     chart.height = function(_) {
2055         if (!arguments.length) return height;
2056         height = _;
2057         return chart;
2058     };
2059
2060     chart.margin = function(_) {
2061         if (!arguments.length) return margin;
2062         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2063         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2064         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2065         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2066         return chart;
2067     };
2068
2069     chart.tickFormat = function(_) {
2070         if (!arguments.length) return tickFormat;
2071         tickFormat = _;
2072         return chart;
2073     };
2074
2075     chart.color = function(_) {
2076         if (!arguments.length) return color;
2077         color = nv.utils.getColor(_);
2078         return chart;
2079     };
2080
2081     //============================================================
2082
2083
2084     return chart;
2085 };
2086
2087
2088
2089 // Chart design based on the recommendations of Stephen Few. Implementation
2090 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2091 // http://projects.instantcognition.com/protovis/bulletchart/
2092 nv.models.bulletChart = function() {
2093     "use strict";
2094     //============================================================
2095     // Public Variables with Default Settings
2096     //------------------------------------------------------------
2097
2098     var bullet = nv.models.bullet()
2099         ;
2100
2101     var orient = 'left' // TODO top & bottom
2102         , reverse = false
2103         , margin = {top: 5, right: 40, bottom: 20, left: 120}
2104         , ranges = function(d) { return d.ranges }
2105         , markers = function(d) { return d.markers ? d.markers : [0] }
2106         , measures = function(d) { return d.measures }
2107         , width = null
2108         , height = 55
2109         , tickFormat = null
2110         , tooltips = true
2111         , tooltip = function(key, x, y, e, graph) {
2112             return '<h3>' + x + '</h3>' +
2113                 '<p>' + y + '</p>'
2114         }
2115         , noData = 'No Data Available.'
2116         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2117         ;
2118
2119     //============================================================
2120
2121
2122     //============================================================
2123     // Private Variables
2124     //------------------------------------------------------------
2125
2126     var showTooltip = function(e, offsetElement) {
2127         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
2128             top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
2129             content = tooltip(e.key, e.label, e.value, e, chart);
2130
2131         nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
2132     };
2133
2134     //============================================================
2135
2136
2137     function chart(selection) {
2138         selection.each(function(d, i) {
2139             var container = d3.select(this);
2140             nv.utils.initSVG(container);
2141
2142             var availableWidth = (width  || parseInt(container.style('width')) || 960)
2143                     - margin.left - margin.right,
2144                 availableHeight = height - margin.top - margin.bottom,
2145                 that = this;
2146
2147
2148             chart.update = function() { chart(selection) };
2149             chart.container = this;
2150
2151             //------------------------------------------------------------
2152             // Display No Data message if there's nothing to show.
2153             if (!d || !ranges.call(this, d, i)) {
2154                 var noDataText = container.selectAll('.nv-noData').data([noData]);
2155
2156                 noDataText.enter().append('text')
2157                     .attr('class', 'nvd3 nv-noData')
2158                     .attr('dy', '-.7em')
2159                     .style('text-anchor', 'middle');
2160
2161                 noDataText
2162                     .attr('x', margin.left + availableWidth / 2)
2163                     .attr('y', 18 + margin.top + availableHeight / 2)
2164                     .text(function(d) { return d });
2165
2166                 return chart;
2167             } else {
2168                 container.selectAll('.nv-noData').remove();
2169             }
2170
2171             //------------------------------------------------------------
2172
2173
2174
2175             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2176                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2177                 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2178
2179
2180             //------------------------------------------------------------
2181             // Setup containers and skeleton of chart
2182
2183             var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2184             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2185             var gEnter = wrapEnter.append('g');
2186             var g = wrap.select('g');
2187
2188             gEnter.append('g').attr('class', 'nv-bulletWrap');
2189             gEnter.append('g').attr('class', 'nv-titles');
2190
2191             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2192
2193             //------------------------------------------------------------
2194
2195
2196             // Compute the new x-scale.
2197             var x1 = d3.scale.linear()
2198                 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
2199                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2200
2201             // Retrieve the old x-scale, if this is an update.
2202             var x0 = this.__chart__ || d3.scale.linear()
2203                 .domain([0, Infinity])
2204                 .range(x1.range());
2205
2206             // Stash the new scale.
2207             this.__chart__ = x1;
2208
2209             /*
2210              // Derive width-scales from the x-scales.
2211              var w0 = bulletWidth(x0),
2212              w1 = bulletWidth(x1);
2213
2214              function bulletWidth(x) {
2215              var x0 = x(0);
2216              return function(d) {
2217              return Math.abs(x(d) - x(0));
2218              };
2219              }
2220
2221              function bulletTranslate(x) {
2222              return function(d) {
2223              return 'translate(' + x(d) + ',0)';
2224              };
2225              }
2226              */
2227
2228             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2229                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2230
2231
2232             var title = gEnter.select('.nv-titles').append('g')
2233                 .attr('text-anchor', 'end')
2234                 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2235             title.append('text')
2236                 .attr('class', 'nv-title')
2237                 .text(function(d) { return d.title; });
2238
2239             title.append('text')
2240                 .attr('class', 'nv-subtitle')
2241                 .attr('dy', '1em')
2242                 .text(function(d) { return d.subtitle; });
2243
2244
2245
2246             bullet
2247                 .width(availableWidth)
2248                 .height(availableHeight)
2249
2250             var bulletWrap = g.select('.nv-bulletWrap');
2251
2252             d3.transition(bulletWrap).call(bullet);
2253
2254
2255
2256             // Compute the tick format.
2257             var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2258
2259             // Update the tick groups.
2260             var tick = g.selectAll('g.nv-tick')
2261                 .data(x1.ticks( availableWidth / 50 ), function(d) {
2262                     return this.textContent || format(d);
2263                 });
2264
2265             // Initialize the ticks with the old scale, x0.
2266             var tickEnter = tick.enter().append('g')
2267                 .attr('class', 'nv-tick')
2268                 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2269                 .style('opacity', 1e-6);
2270
2271             tickEnter.append('line')
2272                 .attr('y1', availableHeight)
2273                 .attr('y2', availableHeight * 7 / 6);
2274
2275             tickEnter.append('text')
2276                 .attr('text-anchor', 'middle')
2277                 .attr('dy', '1em')
2278                 .attr('y', availableHeight * 7 / 6)
2279                 .text(format);
2280
2281
2282             // Transition the updating ticks to the new scale, x1.
2283             var tickUpdate = d3.transition(tick)
2284                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2285                 .style('opacity', 1);
2286
2287             tickUpdate.select('line')
2288                 .attr('y1', availableHeight)
2289                 .attr('y2', availableHeight * 7 / 6);
2290
2291             tickUpdate.select('text')
2292                 .attr('y', availableHeight * 7 / 6);
2293
2294             // Transition the exiting ticks to the new scale, x1.
2295             d3.transition(tick.exit())
2296                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2297                 .style('opacity', 1e-6)
2298                 .remove();
2299
2300
2301             //============================================================
2302             // Event Handling/Dispatching (in chart's scope)
2303             //------------------------------------------------------------
2304
2305             dispatch.on('tooltipShow', function(e) {
2306                 e.key = d.title;
2307                 if (tooltips) showTooltip(e, that.parentNode);
2308             });
2309
2310             //============================================================
2311
2312         });
2313
2314         d3.timer.flush();
2315
2316         return chart;
2317     }
2318
2319
2320     //============================================================
2321     // Event Handling/Dispatching (out of chart's scope)
2322     //------------------------------------------------------------
2323
2324     bullet.dispatch.on('elementMouseover.tooltip', function(e) {
2325         dispatch.tooltipShow(e);
2326     });
2327
2328     bullet.dispatch.on('elementMouseout.tooltip', function(e) {
2329         dispatch.tooltipHide(e);
2330     });
2331
2332     dispatch.on('tooltipHide', function() {
2333         if (tooltips) nv.tooltip.cleanup();
2334     });
2335
2336     //============================================================
2337
2338
2339     //============================================================
2340     // Expose Public Variables
2341     //------------------------------------------------------------
2342
2343     chart.dispatch = dispatch;
2344     chart.bullet = bullet;
2345
2346     d3.rebind(chart, bullet, 'color');
2347
2348     chart.options = nv.utils.optionsFunc.bind(chart);
2349
2350     // left, right, top, bottom
2351     chart.orient = function(x) {
2352         if (!arguments.length) return orient;
2353         orient = x;
2354         reverse = orient == 'right' || orient == 'bottom';
2355         return chart;
2356     };
2357
2358     // ranges (bad, satisfactory, good)
2359     chart.ranges = function(x) {
2360         if (!arguments.length) return ranges;
2361         ranges = x;
2362         return chart;
2363     };
2364
2365     // markers (previous, goal)
2366     chart.markers = function(x) {
2367         if (!arguments.length) return markers;
2368         markers = x;
2369         return chart;
2370     };
2371
2372     // measures (actual, forecast)
2373     chart.measures = function(x) {
2374         if (!arguments.length) return measures;
2375         measures = x;
2376         return chart;
2377     };
2378
2379     chart.width = function(x) {
2380         if (!arguments.length) return width;
2381         width = x;
2382         return chart;
2383     };
2384
2385     chart.height = function(x) {
2386         if (!arguments.length) return height;
2387         height = x;
2388         return chart;
2389     };
2390
2391     chart.margin = function(_) {
2392         if (!arguments.length) return margin;
2393         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2394         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2395         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2396         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2397         return chart;
2398     };
2399
2400     chart.tickFormat = function(x) {
2401         if (!arguments.length) return tickFormat;
2402         tickFormat = x;
2403         return chart;
2404     };
2405
2406     chart.tooltips = function(_) {
2407         if (!arguments.length) return tooltips;
2408         tooltips = _;
2409         return chart;
2410     };
2411
2412     chart.tooltipContent = function(_) {
2413         if (!arguments.length) return tooltip;
2414         tooltip = _;
2415         return chart;
2416     };
2417
2418     chart.noData = function(_) {
2419         if (!arguments.length) return noData;
2420         noData = _;
2421         return chart;
2422     };
2423
2424     //============================================================
2425
2426
2427     return chart;
2428 };
2429
2430
2431
2432 nv.models.cumulativeLineChart = function() {
2433     "use strict";
2434     //============================================================
2435     // Public Variables with Default Settings
2436     //------------------------------------------------------------
2437
2438     var lines = nv.models.line()
2439         , xAxis = nv.models.axis()
2440         , yAxis = nv.models.axis()
2441         , legend = nv.models.legend()
2442         , controls = nv.models.legend()
2443         , interactiveLayer = nv.interactiveGuideline()
2444         ;
2445
2446     var margin = {top: 30, right: 30, bottom: 50, left: 60}
2447         , color = nv.utils.defaultColor()
2448         , width = null
2449         , height = null
2450         , showLegend = true
2451         , showXAxis = true
2452         , showYAxis = true
2453         , rightAlignYAxis = false
2454         , tooltips = true
2455         , showControls = true
2456         , useInteractiveGuideline = false
2457         , rescaleY = true
2458         , tooltip = function(key, x, y, e, graph) {
2459             return '<h3>' + key + '</h3>' +
2460                 '<p>' +  y + ' at ' + x + '</p>'
2461         }
2462         , x //can be accessed via chart.xScale()
2463         , y //can be accessed via chart.yScale()
2464         , id = lines.id()
2465         , state = nv.utils.state()
2466         , defaultState = null
2467         , noData = 'No Data Available.'
2468         , average = function(d) { return d.average }
2469         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
2470         , transitionDuration = 250
2471         , duration = 250
2472         , noErrorCheck = false  //if set to TRUE, will bypass an error check in the indexify function.
2473         ;
2474
2475     state.index = 0;
2476     state.rescaleY = rescaleY;
2477
2478     xAxis
2479         .orient('bottom')
2480         .tickPadding(7)
2481     ;
2482     yAxis
2483         .orient((rightAlignYAxis) ? 'right' : 'left')
2484     ;
2485
2486     //============================================================
2487     controls.updateState(false);
2488
2489     //============================================================
2490     // Private Variables
2491     //------------------------------------------------------------
2492
2493     var dx = d3.scale.linear()
2494         , index = {i: 0, x: 0}
2495         , renderWatch = nv.utils.renderWatch(dispatch, duration)
2496         ;
2497
2498     var showTooltip = function(e, offsetElement) {
2499         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2500             top = e.pos[1] + ( offsetElement.offsetTop || 0),
2501             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2502             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2503             content = tooltip(e.series.key, x, y, e, chart);
2504
2505         nv.tooltip.show([left, top], content, null, null, offsetElement);
2506     };
2507
2508     var stateGetter = function(data) {
2509         return function(){
2510             return {
2511                 active: data.map(function(d) { return !d.disabled }),
2512                 index: index.i,
2513                 rescaleY: rescaleY
2514             };
2515         }
2516     };
2517
2518     var stateSetter = function(data) {
2519         return function(state) {
2520             if (state.index !== undefined)
2521                 index.i = state.index;
2522             if (state.rescaleY !== undefined)
2523                 rescaleY = state.rescaleY;
2524             if (state.active !== undefined)
2525                 data.forEach(function(series,i) {
2526                     series.disabled = !state.active[i];
2527                 });
2528         }
2529     };
2530
2531     //============================================================
2532
2533     function chart(selection) {
2534         renderWatch.reset();
2535         renderWatch.models(lines);
2536         if (showXAxis) renderWatch.models(xAxis);
2537         if (showYAxis) renderWatch.models(yAxis);
2538         selection.each(function(data) {
2539             var container = d3.select(this);
2540             nv.utils.initSVG(container);
2541             container.classed('nv-chart-' + id, true);
2542             var that = this;
2543
2544             var availableWidth = (width  || parseInt(container.style('width')) || 960)
2545                     - margin.left - margin.right,
2546                 availableHeight = (height || parseInt(container.style('height')) || 400)
2547                     - margin.top - margin.bottom;
2548
2549
2550             chart.update = function() {
2551                 if (duration === 0)
2552                     container.call(chart);
2553                 else
2554                     container.transition().duration(duration).call(chart)
2555             };
2556             chart.container = this;
2557
2558             state
2559                 .setter(stateSetter(data), chart.update)
2560                 .getter(stateGetter(data))
2561                 .update();
2562
2563             // DEPRECATED set state.disableddisabled
2564             state.disabled = data.map(function(d) { return !!d.disabled });
2565
2566             if (!defaultState) {
2567                 var key;
2568                 defaultState = {};
2569                 for (key in state) {
2570                     if (state[key] instanceof Array)
2571                         defaultState[key] = state[key].slice(0);
2572                     else
2573                         defaultState[key] = state[key];
2574                 }
2575             }
2576
2577             var indexDrag = d3.behavior.drag()
2578                 .on('dragstart', dragStart)
2579                 .on('drag', dragMove)
2580                 .on('dragend', dragEnd);
2581
2582
2583             function dragStart(d,i) {
2584                 d3.select(chart.container)
2585                     .style('cursor', 'ew-resize');
2586             }
2587
2588             function dragMove(d,i) {
2589                 index.x = d3.event.x;
2590                 index.i = Math.round(dx.invert(index.x));
2591                 updateZero();
2592             }
2593
2594             function dragEnd(d,i) {
2595                 d3.select(chart.container)
2596                     .style('cursor', 'auto');
2597
2598                 // update state and send stateChange with new index
2599                 state.index = index.i;
2600                 dispatch.stateChange(state);
2601             }
2602
2603             //------------------------------------------------------------
2604             // Display No Data message if there's nothing to show.
2605
2606             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2607                 var noDataText = container.selectAll('.nv-noData').data([noData]);
2608
2609                 noDataText.enter().append('text')
2610                     .attr('class', 'nvd3 nv-noData')
2611                     .attr('dy', '-.7em')
2612                     .style('text-anchor', 'middle');
2613
2614                 noDataText
2615                     .attr('x', margin.left + availableWidth / 2)
2616                     .attr('y', margin.top + availableHeight / 2)
2617                     .text(function(d) { return d });
2618
2619                 return chart;
2620             } else {
2621                 container.selectAll('.nv-noData').remove();
2622             }
2623
2624             //------------------------------------------------------------
2625
2626
2627             //------------------------------------------------------------
2628             // Setup Scales
2629
2630             x = lines.xScale();
2631             y = lines.yScale();
2632
2633
2634             if (!rescaleY) {
2635                 var seriesDomains = data
2636                     .filter(function(series) { return !series.disabled })
2637                     .map(function(series,i) {
2638                         var initialDomain = d3.extent(series.values, lines.y());
2639
2640                         //account for series being disabled when losing 95% or more
2641                         if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2642
2643                         return [
2644                                 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2645                                 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2646                         ];
2647                     });
2648
2649                 var completeDomain = [
2650                     d3.min(seriesDomains, function(d) { return d[0] }),
2651                     d3.max(seriesDomains, function(d) { return d[1] })
2652                 ];
2653
2654                 lines.yDomain(completeDomain);
2655             } else {
2656                 lines.yDomain(null);
2657             }
2658
2659
2660             dx  .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2661                 .range([0, availableWidth])
2662                 .clamp(true);
2663
2664             //------------------------------------------------------------
2665
2666
2667             var data = indexify(index.i, data);
2668
2669
2670             //------------------------------------------------------------
2671             // Setup containers and skeleton of chart
2672             var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2673             var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2674             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2675             var g = wrap.select('g');
2676
2677             gEnter.append('g').attr('class', 'nv-interactive');
2678             gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2679             gEnter.append('g').attr('class', 'nv-y nv-axis');
2680             gEnter.append('g').attr('class', 'nv-background');
2681             gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2682             gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2683             gEnter.append('g').attr('class', 'nv-legendWrap');
2684             gEnter.append('g').attr('class', 'nv-controlsWrap');
2685
2686
2687             //------------------------------------------------------------
2688             // Legend
2689
2690             if (showLegend) {
2691                 legend.width(availableWidth);
2692
2693                 g.select('.nv-legendWrap')
2694                     .datum(data)
2695                     .call(legend);
2696
2697                 if ( margin.top != legend.height()) {
2698                     margin.top = legend.height();
2699                     availableHeight = (height || parseInt(container.style('height')) || 400)
2700                         - margin.top - margin.bottom;
2701                 }
2702
2703                 g.select('.nv-legendWrap')
2704                     .attr('transform', 'translate(0,' + (-margin.top) +')')
2705             }
2706
2707             //------------------------------------------------------------
2708
2709
2710             //------------------------------------------------------------
2711             // Controls
2712
2713             if (showControls) {
2714                 var controlsData = [
2715                     { key: 'Re-scale y-axis', disabled: !rescaleY }
2716                 ];
2717
2718                 controls
2719                     .width(140)
2720                     .color(['#444', '#444', '#444'])
2721                     .rightAlign(false)
2722                     .margin({top: 5, right: 0, bottom: 5, left: 20})
2723                 ;
2724
2725                 g.select('.nv-controlsWrap')
2726                     .datum(controlsData)
2727                     .attr('transform', 'translate(0,' + (-margin.top) +')')
2728                     .call(controls);
2729             }
2730
2731             //------------------------------------------------------------
2732
2733
2734             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2735
2736             if (rightAlignYAxis) {
2737                 g.select(".nv-y.nv-axis")
2738                     .attr("transform", "translate(" + availableWidth + ",0)");
2739             }
2740
2741             // Show error if series goes below 100%
2742             var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2743
2744             wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2745             if (tempDisabled.length) {
2746                 wrap.append('text').attr('class', 'tempDisabled')
2747                     .attr('x', availableWidth / 2)
2748                     .attr('y', '-.71em')
2749                     .style('text-anchor', 'end')
2750                     .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2751             }
2752
2753             //------------------------------------------------------------
2754             // Main Chart Component(s)
2755
2756             //------------------------------------------------------------
2757             //Set up interactive layer
2758             if (useInteractiveGuideline) {
2759                 interactiveLayer
2760                     .width(availableWidth)
2761                     .height(availableHeight)
2762                     .margin({left:margin.left,top:margin.top})
2763                     .svgContainer(container)
2764                     .xScale(x);
2765                 wrap.select(".nv-interactive").call(interactiveLayer);
2766             }
2767
2768             gEnter.select('.nv-background')
2769                 .append('rect');
2770
2771             g.select('.nv-background rect')
2772                 .attr('width', availableWidth)
2773                 .attr('height', availableHeight);
2774
2775             lines
2776                 //.x(function(d) { return d.x })
2777                 .y(function(d) { return d.display.y })
2778                 .width(availableWidth)
2779                 .height(availableHeight)
2780                 .color(data.map(function(d,i) {
2781                     return d.color || color(d, i);
2782                 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2783
2784
2785
2786             var linesWrap = g.select('.nv-linesWrap')
2787                 .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
2788
2789             //d3.transition(linesWrap).call(lines);
2790             linesWrap.call(lines);
2791
2792             /*Handle average lines [AN-612] ----------------------------*/
2793
2794             //Store a series index number in the data array.
2795             data.forEach(function(d,i) {
2796                 d.seriesIndex = i;
2797             });
2798
2799             var avgLineData = data.filter(function(d) {
2800                 return !d.disabled && !!average(d);
2801             });
2802
2803             var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2804                 .data(avgLineData, function(d) { return d.key; });
2805
2806             var getAvgLineY = function(d) {
2807                 //If average lines go off the svg element, clamp them to the svg bounds.
2808                 var yVal = y(average(d));
2809                 if (yVal < 0) return 0;
2810                 if (yVal > availableHeight) return availableHeight;
2811                 return yVal;
2812             };
2813
2814             avgLines.enter()
2815                 .append('line')
2816                 .style('stroke-width',2)
2817                 .style('stroke-dasharray','10,10')
2818                 .style('stroke',function (d,i) {
2819                     return lines.color()(d,d.seriesIndex);
2820                 })
2821                 .attr('x1',0)
2822                 .attr('x2',availableWidth)
2823                 .attr('y1', getAvgLineY)
2824                 .attr('y2', getAvgLineY);
2825
2826             avgLines
2827                 .style('stroke-opacity',function(d){
2828                     //If average lines go offscreen, make them transparent
2829                     var yVal = y(average(d));
2830                     if (yVal < 0 || yVal > availableHeight) return 0;
2831                     return 1;
2832                 })
2833                 .attr('x1',0)
2834                 .attr('x2',availableWidth)
2835                 .attr('y1', getAvgLineY)
2836                 .attr('y2', getAvgLineY);
2837
2838             avgLines.exit().remove();
2839
2840             //Create index line -----------------------------------------
2841
2842             var indexLine = linesWrap.selectAll('.nv-indexLine')
2843                 .data([index]);
2844             indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2845                 .attr('width', 3)
2846                 .attr('x', -2)
2847                 .attr('fill', 'red')
2848                 .attr('fill-opacity', .5)
2849                 .style("pointer-events","all")
2850                 .call(indexDrag);
2851
2852             indexLine
2853                 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2854                 .attr('height', availableHeight);
2855
2856             //------------------------------------------------------------
2857
2858
2859             //------------------------------------------------------------
2860             // Setup Axes
2861
2862             if (showXAxis) {
2863                 xAxis
2864                     .scale(x)
2865                     .ticks( nv.utils.calcTicksX(availableWidth/70, data) )
2866                     .tickSize(-availableHeight, 0);
2867
2868                 g.select('.nv-x.nv-axis')
2869                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
2870                 g.select('.nv-x.nv-axis')
2871                     .call(xAxis);
2872             }
2873
2874
2875             if (showYAxis) {
2876                 yAxis
2877                     .scale(y)
2878                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
2879                     .tickSize( -availableWidth, 0);
2880
2881                 g.select('.nv-y.nv-axis')
2882                     .call(yAxis);
2883             }
2884             //------------------------------------------------------------
2885
2886
2887             //============================================================
2888             // Event Handling/Dispatching (in chart's scope)
2889             //------------------------------------------------------------
2890
2891
2892             function updateZero() {
2893                 indexLine
2894                     .data([index]);
2895
2896                 //When dragging the index line, turn off line transitions.
2897                 // Then turn them back on when done dragging.
2898                 var oldDuration = chart.duration();
2899                 chart.duration(0);
2900                 chart.update();
2901                 chart.duration(oldDuration);
2902             }
2903
2904             g.select('.nv-background rect')
2905                 .on('click', function() {
2906                     index.x = d3.mouse(this)[0];
2907                     index.i = Math.round(dx.invert(index.x));
2908
2909                     // update state and send stateChange with new index
2910                     state.index = index.i;
2911                     dispatch.stateChange(state);
2912
2913                     updateZero();
2914                 });
2915
2916             lines.dispatch.on('elementClick', function(e) {
2917                 index.i = e.pointIndex;
2918                 index.x = dx(index.i);
2919
2920                 // update state and send stateChange with new index
2921                 state.index = index.i;
2922                 dispatch.stateChange(state);
2923
2924                 updateZero();
2925             });
2926
2927             controls.dispatch.on('legendClick', function(d,i) {
2928                 d.disabled = !d.disabled;
2929                 rescaleY = !d.disabled;
2930
2931                 state.rescaleY = rescaleY;
2932                 dispatch.stateChange(state);
2933                 chart.update();
2934             });
2935
2936
2937             legend.dispatch.on('stateChange', function(newState) {
2938                 for (var key in newState)
2939                     state[key] = newState[key];
2940                 dispatch.stateChange(state);
2941                 chart.update();
2942             });
2943
2944             interactiveLayer.dispatch.on('elementMousemove', function(e) {
2945                 lines.clearHighlights();
2946                 var singlePoint, pointIndex, pointXLocation, allData = [];
2947
2948                 data
2949                     .filter(function(series, i) {
2950                         series.seriesIndex = i;
2951                         return !series.disabled;
2952                     })
2953                     .forEach(function(series,i) {
2954                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2955                         lines.highlightPoint(i, pointIndex, true);
2956                         var point = series.values[pointIndex];
2957                         if (typeof point === 'undefined') return;
2958                         if (typeof singlePoint === 'undefined') singlePoint = point;
2959                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2960                         allData.push({
2961                             key: series.key,
2962                             value: chart.y()(point, pointIndex),
2963                             color: color(series,series.seriesIndex)
2964                         });
2965                     });
2966
2967                 //Highlight the tooltip entry based on which point the mouse is closest to.
2968                 if (allData.length > 2) {
2969                     var yValue = chart.yScale().invert(e.mouseY);
2970                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
2971                     var threshold = 0.03 * domainExtent;
2972                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
2973                     if (indexToHighlight !== null)
2974                         allData[indexToHighlight].highlight = true;
2975                 }
2976
2977                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
2978                 interactiveLayer.tooltip
2979                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2980                     .chartContainer(that.parentNode)
2981                     .enabled(tooltips)
2982                     .valueFormatter(function(d,i) {
2983                         return yAxis.tickFormat()(d);
2984                     })
2985                     .data(
2986                     {
2987                         value: xValue,
2988                         series: allData
2989                     }
2990                 )();
2991
2992                 interactiveLayer.renderGuideLine(pointXLocation);
2993
2994             });
2995
2996             interactiveLayer.dispatch.on("elementMouseout",function(e) {
2997                 dispatch.tooltipHide();
2998                 lines.clearHighlights();
2999             });
3000
3001             dispatch.on('tooltipShow', function(e) {
3002                 if (tooltips) showTooltip(e, that.parentNode);
3003             });
3004
3005
3006             // Update chart from a state object passed to event handler
3007             dispatch.on('changeState', function(e) {
3008
3009                 if (typeof e.disabled !== 'undefined') {
3010                     data.forEach(function(series,i) {
3011                         series.disabled = e.disabled[i];
3012                     });
3013
3014                     state.disabled = e.disabled;
3015                 }
3016
3017
3018                 if (typeof e.index !== 'undefined') {
3019                     index.i = e.index;
3020                     index.x = dx(index.i);
3021
3022                     state.index = e.index;
3023
3024                     indexLine
3025                         .data([index]);
3026                 }
3027
3028                 if (typeof e.rescaleY !== 'undefined') {
3029                     rescaleY = e.rescaleY;
3030                 }
3031
3032                 chart.update();
3033             });
3034
3035             //============================================================
3036
3037         });
3038
3039         renderWatch.renderEnd('cumulativeLineChart immediate');
3040
3041         return chart;
3042     }
3043
3044
3045     //============================================================
3046     // Event Handling/Dispatching (out of chart's scope)
3047     //------------------------------------------------------------
3048
3049     lines.dispatch.on('elementMouseover.tooltip', function(e) {
3050         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
3051         dispatch.tooltipShow(e);
3052     });
3053
3054     lines.dispatch.on('elementMouseout.tooltip', function(e) {
3055         dispatch.tooltipHide(e);
3056     });
3057
3058     dispatch.on('tooltipHide', function() {
3059         if (tooltips) nv.tooltip.cleanup();
3060     });
3061
3062     //============================================================
3063
3064
3065     //============================================================
3066     // Expose Public Variables
3067     //------------------------------------------------------------
3068
3069     // expose chart's sub-components
3070     chart.dispatch = dispatch;
3071     chart.lines = lines;
3072     chart.legend = legend;
3073     chart.xAxis = xAxis;
3074     chart.yAxis = yAxis;
3075     chart.interactiveLayer = interactiveLayer;
3076
3077     // DO NOT DELETE. This is currently overridden below
3078     // until deprecated portions are removed.
3079     chart.state = state;
3080
3081     d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi',  'id');
3082
3083     chart.options = nv.utils.optionsFunc.bind(chart);
3084
3085     chart.margin = function(_) {
3086         if (!arguments.length) return margin;
3087         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3088         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3089         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3090         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3091         return chart;
3092     };
3093
3094     chart.width = function(_) {
3095         if (!arguments.length) return width;
3096         width = _;
3097         return chart;
3098     };
3099
3100     chart.height = function(_) {
3101         if (!arguments.length) return height;
3102         height = _;
3103         return chart;
3104     };
3105
3106     chart.color = function(_) {
3107         if (!arguments.length) return color;
3108         color = nv.utils.getColor(_);
3109         legend.color(color);
3110         return chart;
3111     };
3112
3113     chart.rescaleY = function(_) {
3114         if (!arguments.length) return rescaleY;
3115         rescaleY = _;
3116         return chart;
3117     };
3118
3119     chart.showControls = function(_) {
3120         if (!arguments.length) return showControls;
3121         showControls = _;
3122         return chart;
3123     };
3124
3125     chart.useInteractiveGuideline = function(_) {
3126         if(!arguments.length) return useInteractiveGuideline;
3127         useInteractiveGuideline = _;
3128         if (_ === true) {
3129             chart.interactive(false);
3130             chart.useVoronoi(false);
3131         }
3132         return chart;
3133     };
3134
3135     chart.showLegend = function(_) {
3136         if (!arguments.length) return showLegend;
3137         showLegend = _;
3138         return chart;
3139     };
3140
3141     chart.showXAxis = function(_) {
3142         if (!arguments.length) return showXAxis;
3143         showXAxis = _;
3144         return chart;
3145     };
3146
3147     chart.showYAxis = function(_) {
3148         if (!arguments.length) return showYAxis;
3149         showYAxis = _;
3150         return chart;
3151     };
3152
3153     chart.rightAlignYAxis = function(_) {
3154         if(!arguments.length) return rightAlignYAxis;
3155         rightAlignYAxis = _;
3156         yAxis.orient( (_) ? 'right' : 'left');
3157         return chart;
3158     };
3159
3160     chart.tooltips = function(_) {
3161         if (!arguments.length) return tooltips;
3162         tooltips = _;
3163         return chart;
3164     };
3165
3166     chart.tooltipContent = function(_) {
3167         if (!arguments.length) return tooltip;
3168         tooltip = _;
3169         return chart;
3170     };
3171
3172     // DEPRECATED
3173     chart.state = function(_) {
3174         nv.deprecated('cululativeLineChart.state');
3175         if (!arguments.length) return state;
3176         state = _;
3177         return chart;
3178     };
3179     for (var key in state) {
3180         chart.state[key] = state[key];
3181     }
3182     // END DEPRECATED
3183
3184     chart.defaultState = function(_) {
3185         if (!arguments.length) return defaultState;
3186         defaultState = _;
3187         return chart;
3188     };
3189
3190     chart.noData = function(_) {
3191         if (!arguments.length) return noData;
3192         noData = _;
3193         return chart;
3194     };
3195
3196     chart.average = function(_) {
3197         if(!arguments.length) return average;
3198         average = _;
3199         return chart;
3200     };
3201
3202     chart.transitionDuration = function(_) {
3203         nv.deprecated('cumulativeLineChart.transitionDuration');
3204         return chart.duration(_);
3205     };
3206
3207     chart.duration = function(_) {
3208         if(!arguments.length) return duration;
3209         duration = _;
3210         lines.duration(duration);
3211         xAxis.duration(duration);
3212         yAxis.duration(duration);
3213         renderWatch.reset(duration);
3214         return chart;
3215     };
3216
3217     chart.noErrorCheck = function(_) {
3218         if (!arguments.length) return noErrorCheck;
3219         noErrorCheck = _;
3220         return chart;
3221     };
3222
3223     //============================================================
3224
3225
3226     //============================================================
3227     // Functions
3228     //------------------------------------------------------------
3229
3230     var indexifyYGetter = null;
3231     /* Normalize the data according to an index point. */
3232     function indexify(idx, data) {
3233         if (!indexifyYGetter) indexifyYGetter = lines.y();
3234         return data.map(function(line, i) {
3235             if (!line.values) {
3236                 return line;
3237             }
3238             var indexValue = line.values[idx];
3239             if (indexValue == null) {
3240                 return line;
3241             }
3242             var v = indexifyYGetter(indexValue, idx);
3243
3244             //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
3245             if (v < -.95 && !noErrorCheck) {
3246                 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
3247
3248                 line.tempDisabled = true;
3249                 return line;
3250             }
3251
3252             line.tempDisabled = false;
3253
3254             line.values = line.values.map(function(point, pointIndex) {
3255                 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3256                 return point;
3257             });
3258
3259             return line;
3260         })
3261     }
3262
3263     //============================================================
3264
3265
3266     return chart;
3267 };//TODO: consider deprecating by adding necessary features to multiBar model
3268 nv.models.discreteBar = function() {
3269     "use strict";
3270     //============================================================
3271     // Public Variables with Default Settings
3272     //------------------------------------------------------------
3273
3274     var margin = {top: 0, right: 0, bottom: 0, left: 0}
3275         , width = 960
3276         , height = 500
3277         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3278         , x = d3.scale.ordinal()
3279         , y = d3.scale.linear()
3280         , getX = function(d) { return d.x }
3281         , getY = function(d) { return d.y }
3282         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
3283         , color = nv.utils.defaultColor()
3284         , showValues = false
3285         , valueFormat = d3.format(',.2f')
3286         , xDomain
3287         , yDomain
3288         , xRange
3289         , yRange
3290         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
3291         , rectClass = 'discreteBar'
3292         , duration = 250
3293         ;
3294
3295     //============================================================
3296
3297
3298     //============================================================
3299     // Private Variables
3300     //------------------------------------------------------------
3301
3302     var x0, y0;
3303     var renderWatch = nv.utils.renderWatch(dispatch, duration);
3304
3305     //============================================================
3306
3307
3308     function chart(selection) {
3309         renderWatch.reset();
3310         selection.each(function(data) {
3311             var availableWidth = width - margin.left - margin.right,
3312                 availableHeight = height - margin.top - margin.bottom,
3313                 container = d3.select(this);
3314             nv.utils.initSVG(container);
3315
3316
3317             //add series index to each data point for reference
3318             data.forEach(function(series, i) {
3319                 series.values.forEach(function(point) {
3320                     point.series = i;
3321                 });
3322             });
3323
3324
3325             //------------------------------------------------------------
3326             // Setup Scales
3327
3328             // remap and flatten the data for use in calculating the scales' domains
3329             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
3330                 data.map(function(d) {
3331                     return d.values.map(function(d,i) {
3332                         return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
3333                     })
3334                 });
3335
3336             x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
3337                 .rangeBands(xRange || [0, availableWidth], .1);
3338
3339             y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3340
3341
3342             // If showValues, pad the Y axis range to account for label height
3343             if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3344             else y.range(yRange || [availableHeight, 0]);
3345
3346             //store old scales if they exist
3347             x0 = x0 || x;
3348             y0 = y0 || y.copy().range([y(0),y(0)]);
3349
3350             //------------------------------------------------------------
3351
3352
3353             //------------------------------------------------------------
3354             // Setup containers and skeleton of chart
3355
3356             var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3357             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3358             var gEnter = wrapEnter.append('g');
3359             var g = wrap.select('g');
3360
3361             gEnter.append('g').attr('class', 'nv-groups');
3362
3363             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3364
3365             //------------------------------------------------------------
3366
3367
3368
3369             //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3370             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3371                 .data(function(d) { return d }, function(d) { return d.key });
3372             groups.enter().append('g')
3373                 .style('stroke-opacity', 1e-6)
3374                 .style('fill-opacity', 1e-6);
3375             groups.exit()
3376                 .watchTransition(renderWatch, 'discreteBar: exit groups')
3377                 .style('stroke-opacity', 1e-6)
3378                 .style('fill-opacity', 1e-6)
3379                 .remove();
3380             groups
3381                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3382                 .classed('hover', function(d) { return d.hover });
3383             groups
3384                 .watchTransition(renderWatch, 'discreteBar: groups')
3385                 .style('stroke-opacity', 1)
3386                 .style('fill-opacity', .75);
3387
3388
3389             var bars = groups.selectAll('g.nv-bar')
3390                 .data(function(d) { return d.values });
3391
3392             bars.exit().remove();
3393
3394
3395             var barsEnter = bars.enter().append('g')
3396                 .attr('transform', function(d,i,j) {
3397                     return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3398                 })
3399                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3400                     d3.select(this).classed('hover', true);
3401                     dispatch.elementMouseover({
3402                         value: getY(d,i),
3403                         point: d,
3404                         series: data[d.series],
3405                         pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3406                         pointIndex: i,
3407                         seriesIndex: d.series,
3408                         e: d3.event
3409                     });
3410                 })
3411                 .on('mouseout', function(d,i) {
3412                     d3.select(this).classed('hover', false);
3413                     dispatch.elementMouseout({
3414                         value: getY(d,i),
3415                         point: d,
3416                         series: data[d.series],
3417                         pointIndex: i,
3418                         seriesIndex: d.series,
3419                         e: d3.event
3420                     });
3421                 })
3422                 .on('click', function(d,i) {
3423                     dispatch.elementClick({
3424                         value: getY(d,i),
3425                         point: d,
3426                         series: data[d.series],
3427                         pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3428                         pointIndex: i,
3429                         seriesIndex: d.series,
3430                         e: d3.event
3431                     });
3432                     d3.event.stopPropagation();
3433                 })
3434                 .on('dblclick', function(d,i) {
3435                     dispatch.elementDblClick({
3436                         value: getY(d,i),
3437                         point: d,
3438                         series: data[d.series],
3439                         pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3440                         pointIndex: i,
3441                         seriesIndex: d.series,
3442                         e: d3.event
3443                     });
3444                     d3.event.stopPropagation();
3445                 });
3446
3447             barsEnter.append('rect')
3448                 .attr('height', 0)
3449                 .attr('width', x.rangeBand() * .9 / data.length )
3450
3451             if (showValues) {
3452                 barsEnter.append('text')
3453                     .attr('text-anchor', 'middle')
3454                 ;
3455
3456                 bars.select('text')
3457                     .text(function(d,i) { return valueFormat(getY(d,i)) })
3458                     .watchTransition(renderWatch, 'discreteBar: bars text')
3459                     .attr('x', x.rangeBand() * .9 / 2)
3460                     .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3461
3462                 ;
3463             } else {
3464                 bars.selectAll('text').remove();
3465             }
3466
3467             bars
3468                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3469                 .style('fill', function(d,i) { return d.color || color(d,i) })
3470                 .style('stroke', function(d,i) { return d.color || color(d,i) })
3471                 .select('rect')
3472                 .attr('class', rectClass)
3473                 .watchTransition(renderWatch, 'discreteBar: bars rect')
3474                 .attr('width', x.rangeBand() * .9 / data.length);
3475             bars.watchTransition(renderWatch, 'discreteBar: bars')
3476                 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3477                 .attr('transform', function(d,i) {
3478                     var left = x(getX(d,i)) + x.rangeBand() * .05,
3479                         top = getY(d,i) < 0 ?
3480                             y(0) :
3481                                 y(0) - y(getY(d,i)) < 1 ?
3482                             y(0) - 1 : //make 1 px positive bars show up above y=0
3483                             y(getY(d,i));
3484
3485                     return 'translate(' + left + ', ' + top + ')'
3486                 })
3487                 .select('rect')
3488                 .attr('height', function(d,i) {
3489                     return  Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3490                 });
3491
3492
3493             //store old scales for use in transitions on update
3494             x0 = x.copy();
3495             y0 = y.copy();
3496
3497         });
3498
3499         renderWatch.renderEnd('discreteBar immediate');
3500         return chart;
3501     }
3502
3503
3504     //============================================================
3505     // Expose Public Variables
3506     //------------------------------------------------------------
3507
3508     chart.dispatch = dispatch;
3509
3510     chart.options = nv.utils.optionsFunc.bind(chart);
3511
3512     chart.x = function(_) {
3513         if (!arguments.length) return getX;
3514         getX = _;
3515         return chart;
3516     };
3517
3518     chart.y = function(_) {
3519         if (!arguments.length) return getY;
3520         getY = _;
3521         return chart;
3522     };
3523
3524     chart.margin = function(_) {
3525         if (!arguments.length) return margin;
3526         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3527         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3528         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3529         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3530         return chart;
3531     };
3532
3533     chart.width = function(_) {
3534         if (!arguments.length) return width;
3535         width = _;
3536         return chart;
3537     };
3538
3539     chart.height = function(_) {
3540         if (!arguments.length) return height;
3541         height = _;
3542         return chart;
3543     };
3544
3545     chart.xScale = function(_) {
3546         if (!arguments.length) return x;
3547         x = _;
3548         return chart;
3549     };
3550
3551     chart.yScale = function(_) {
3552         if (!arguments.length) return y;
3553         y = _;
3554         return chart;
3555     };
3556
3557     chart.xDomain = function(_) {
3558         if (!arguments.length) return xDomain;
3559         xDomain = _;
3560         return chart;
3561     };
3562
3563     chart.yDomain = function(_) {
3564         if (!arguments.length) return yDomain;
3565         yDomain = _;
3566         return chart;
3567     };
3568
3569     chart.xRange = function(_) {
3570         if (!arguments.length) return xRange;
3571         xRange = _;
3572         return chart;
3573     };
3574
3575     chart.yRange = function(_) {
3576         if (!arguments.length) return yRange;
3577         yRange = _;
3578         return chart;
3579     };
3580
3581     chart.forceY = function(_) {
3582         if (!arguments.length) return forceY;
3583         forceY = _;
3584         return chart;
3585     };
3586
3587     chart.color = function(_) {
3588         if (!arguments.length) return color;
3589         color = nv.utils.getColor(_);
3590         return chart;
3591     };
3592
3593     chart.id = function(_) {
3594         if (!arguments.length) return id;
3595         id = _;
3596         return chart;
3597     };
3598
3599     chart.showValues = function(_) {
3600         if (!arguments.length) return showValues;
3601         showValues = _;
3602         return chart;
3603     };
3604
3605     chart.valueFormat= function(_) {
3606         if (!arguments.length) return valueFormat;
3607         valueFormat = _;
3608         return chart;
3609     };
3610
3611     chart.rectClass= function(_) {
3612         if (!arguments.length) return rectClass;
3613         rectClass = _;
3614         return chart;
3615     };
3616
3617     chart.duration = function(_) {
3618         if (!arguments.length) return duration;
3619         duration = _;
3620         renderWatch.reset(duration);
3621         return chart;
3622     };
3623     //============================================================
3624
3625
3626     return chart;
3627 }
3628
3629 nv.models.discreteBarChart = function() {
3630     "use strict";
3631     //============================================================
3632     // Public Variables with Default Settings
3633     //------------------------------------------------------------
3634
3635     var discretebar = nv.models.discreteBar()
3636         , xAxis = nv.models.axis()
3637         , yAxis = nv.models.axis()
3638         ;
3639
3640     var margin = {top: 15, right: 10, bottom: 50, left: 60}
3641         , width = null
3642         , height = null
3643         , color = nv.utils.getColor()
3644         , showXAxis = true
3645         , showYAxis = true
3646         , rightAlignYAxis = false
3647         , staggerLabels = false
3648         , tooltips = true
3649         , tooltip = function(key, x, y, e, graph) {
3650             return '<h3>' + x + '</h3>' +
3651                 '<p>' +  y + '</p>'
3652         }
3653         , x
3654         , y
3655         , noData = "No Data Available."
3656         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate','renderEnd')
3657         , duration = 250
3658         ;
3659
3660     xAxis
3661         .orient('bottom')
3662         .highlightZero(false)
3663         .showMaxMin(false)
3664         .tickFormat(function(d) { return d })
3665     ;
3666     yAxis
3667         .orient((rightAlignYAxis) ? 'right' : 'left')
3668         .tickFormat(d3.format(',.1f'))
3669     ;
3670
3671     //============================================================
3672
3673
3674     //============================================================
3675     // Private Variables
3676     //------------------------------------------------------------
3677
3678     var showTooltip = function(e, offsetElement) {
3679         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3680             top = e.pos[1] + ( offsetElement.offsetTop || 0),
3681             x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3682             y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3683             content = tooltip(e.series.key, x, y, e, chart);
3684
3685         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3686     };
3687
3688     var renderWatch = nv.utils.renderWatch(dispatch, duration);
3689     //============================================================
3690
3691
3692     function chart(selection) {
3693         renderWatch.reset();
3694         renderWatch.models(discretebar);
3695         if (showXAxis) renderWatch.models(xAxis);
3696         if (showYAxis) renderWatch.models(yAxis);
3697
3698         selection.each(function(data) {
3699             var container = d3.select(this),
3700                 that = this;
3701             nv.utils.initSVG(container);
3702             var availableWidth = (width  || parseInt(container.style('width')) || 960)
3703                     - margin.left - margin.right,
3704                 availableHeight = (height || parseInt(container.style('height')) || 400)
3705                     - margin.top - margin.bottom;
3706
3707
3708             chart.update = function() {
3709                 dispatch.beforeUpdate();
3710                 container.transition().duration(duration).call(chart);
3711             };
3712             chart.container = this;
3713
3714
3715             //------------------------------------------------------------
3716             // Display No Data message if there's nothing to show.
3717
3718             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3719                 var noDataText = container.selectAll('.nv-noData').data([noData]);
3720
3721                 noDataText.enter().append('text')
3722                     .attr('class', 'nvd3 nv-noData')
3723                     .attr('dy', '-.7em')
3724                     .style('text-anchor', 'middle');
3725
3726                 noDataText
3727                     .attr('x', margin.left + availableWidth / 2)
3728                     .attr('y', margin.top + availableHeight / 2)
3729                     .text(function(d) { return d });
3730
3731                 return chart;
3732             } else {
3733                 container.selectAll('.nv-noData').remove();
3734             }
3735
3736             //------------------------------------------------------------
3737
3738
3739             //------------------------------------------------------------
3740             // Setup Scales
3741
3742             x = discretebar.xScale();
3743             y = discretebar.yScale().clamp(true);
3744
3745             //------------------------------------------------------------
3746
3747
3748             //------------------------------------------------------------
3749             // Setup containers and skeleton of chart
3750
3751             var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3752             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3753             var defsEnter = gEnter.append('defs');
3754             var g = wrap.select('g');
3755
3756             gEnter.append('g').attr('class', 'nv-x nv-axis');
3757             gEnter.append('g').attr('class', 'nv-y nv-axis')
3758                 .append('g').attr('class', 'nv-zeroLine')
3759                 .append('line');
3760
3761             gEnter.append('g').attr('class', 'nv-barsWrap');
3762
3763             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3764
3765             if (rightAlignYAxis) {
3766                 g.select(".nv-y.nv-axis")
3767                     .attr("transform", "translate(" + availableWidth + ",0)");
3768             }
3769
3770             //------------------------------------------------------------
3771
3772
3773             //------------------------------------------------------------
3774             // Main Chart Component(s)
3775
3776             discretebar
3777                 .width(availableWidth)
3778                 .height(availableHeight);
3779
3780
3781             var barsWrap = g.select('.nv-barsWrap')
3782                 .datum(data.filter(function(d) { return !d.disabled }))
3783
3784             barsWrap.transition().call(discretebar);
3785
3786             //------------------------------------------------------------
3787
3788
3789
3790             defsEnter.append('clipPath')
3791                 .attr('id', 'nv-x-label-clip-' + discretebar.id())
3792                 .append('rect');
3793
3794             g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3795                 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3796                 .attr('height', 16)
3797                 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3798
3799
3800             //------------------------------------------------------------
3801             // Setup Axes
3802
3803             if (showXAxis) {
3804                 xAxis
3805                     .scale(x)
3806                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
3807                     .tickSize(-availableHeight, 0);
3808
3809                 g.select('.nv-x.nv-axis')
3810                     .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3811
3812                 g.select('.nv-x.nv-axis').call(xAxis);
3813
3814
3815                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3816
3817                 if (staggerLabels) {
3818                     xTicks
3819                         .selectAll('text')
3820                         .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3821                 }
3822             }
3823
3824             if (showYAxis) {
3825                 yAxis
3826                     .scale(y)
3827                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3828                     .tickSize( -availableWidth, 0);
3829
3830                 g.select('.nv-y.nv-axis').call(yAxis);
3831             }
3832
3833             // Zero line
3834             g.select(".nv-zeroLine line")
3835                 .attr("x1",0)
3836                 .attr("x2",availableWidth)
3837                 .attr("y1", y(0))
3838                 .attr("y2", y(0))
3839             ;
3840
3841             //------------------------------------------------------------
3842
3843
3844             //============================================================
3845             // Event Handling/Dispatching (in chart's scope)
3846             //------------------------------------------------------------
3847
3848             dispatch.on('tooltipShow', function(e) {
3849                 if (tooltips) showTooltip(e, that.parentNode);
3850             });
3851
3852             //============================================================
3853
3854
3855         });
3856
3857         renderWatch.renderEnd('discreteBar chart immediate');
3858         return chart;
3859     }
3860
3861     //============================================================
3862     // Event Handling/Dispatching (out of chart's scope)
3863     //------------------------------------------------------------
3864
3865     discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3866         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
3867         dispatch.tooltipShow(e);
3868     });
3869
3870     discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3871         dispatch.tooltipHide(e);
3872     });
3873
3874     dispatch.on('tooltipHide', function() {
3875         if (tooltips) nv.tooltip.cleanup();
3876     });
3877
3878     //============================================================
3879
3880
3881     //============================================================
3882     // Expose Public Variables
3883     //------------------------------------------------------------
3884
3885     // expose chart's sub-components
3886     chart.dispatch = dispatch;
3887     chart.discretebar = discretebar;
3888     chart.xAxis = xAxis;
3889     chart.yAxis = yAxis;
3890
3891     d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3892
3893     chart.options = nv.utils.optionsFunc.bind(chart);
3894
3895     chart.margin = function(_) {
3896         if (!arguments.length) return margin;
3897         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3898         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3899         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3900         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3901         return chart;
3902     };
3903
3904     chart.width = function(_) {
3905         if (!arguments.length) return width;
3906         width = _;
3907         return chart;
3908     };
3909
3910     chart.height = function(_) {
3911         if (!arguments.length) return height;
3912         height = _;
3913         return chart;
3914     };
3915
3916     chart.color = function(_) {
3917         if (!arguments.length) return color;
3918         color = nv.utils.getColor(_);
3919         discretebar.color(color);
3920         return chart;
3921     };
3922
3923     chart.showXAxis = function(_) {
3924         if (!arguments.length) return showXAxis;
3925         showXAxis = _;
3926         return chart;
3927     };
3928
3929     chart.showYAxis = function(_) {
3930         if (!arguments.length) return showYAxis;
3931         showYAxis = _;
3932         return chart;
3933     };
3934
3935     chart.rightAlignYAxis = function(_) {
3936         if(!arguments.length) return rightAlignYAxis;
3937         rightAlignYAxis = _;
3938         yAxis.orient( (_) ? 'right' : 'left');
3939         return chart;
3940     };
3941
3942     chart.staggerLabels = function(_) {
3943         if (!arguments.length) return staggerLabels;
3944         staggerLabels = _;
3945         return chart;
3946     };
3947
3948     chart.tooltips = function(_) {
3949         if (!arguments.length) return tooltips;
3950         tooltips = _;
3951         return chart;
3952     };
3953
3954     chart.tooltipContent = function(_) {
3955         if (!arguments.length) return tooltip;
3956         tooltip = _;
3957         return chart;
3958     };
3959
3960     chart.noData = function(_) {
3961         if (!arguments.length) return noData;
3962         noData = _;
3963         return chart;
3964     };
3965
3966     chart.transitionDuration = function(_) {
3967         nv.deprecated('discreteBar.transitionDuration');
3968         return chart.duration(_);
3969     };
3970
3971     chart.duration = function(_) {
3972         if (!arguments.length) return duration;
3973         duration = _;
3974         renderWatch.reset(duration);
3975         discretebar.duration(duration);
3976         xAxis.duration(duration);
3977         yAxis.duration(duration);
3978         return chart;
3979     };
3980
3981     //============================================================
3982
3983
3984     return chart;
3985 }
3986
3987 nv.models.distribution = function() {
3988     "use strict";
3989     //============================================================
3990     // Public Variables with Default Settings
3991     //------------------------------------------------------------
3992
3993     var margin = {top: 0, right: 0, bottom: 0, left: 0}
3994         , width = 400 //technically width or height depending on x or y....
3995         , size = 8
3996         , axis = 'x' // 'x' or 'y'... horizontal or vertical
3997         , getData = function(d) { return d[axis] }  // defaults d.x or d.y
3998         , color = nv.utils.defaultColor()
3999         , scale = d3.scale.linear()
4000         , domain
4001         , duration = 250
4002         , dispatch = d3.dispatch('renderEnd')
4003         ;
4004
4005     //============================================================
4006
4007
4008     //============================================================
4009     // Private Variables
4010     //------------------------------------------------------------
4011
4012     var scale0;
4013     var renderWatch = nv.utils.renderWatch(dispatch, duration);
4014
4015     //============================================================
4016
4017
4018     function chart(selection) {
4019         renderWatch.reset();
4020         selection.each(function(data) {
4021             var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
4022                 naxis = axis == 'x' ? 'y' : 'x',
4023                 container = d3.select(this);
4024             nv.utils.initSVG(container);
4025
4026             //------------------------------------------------------------
4027             // Setup Scales
4028
4029             scale0 = scale0 || scale;
4030
4031             //------------------------------------------------------------
4032
4033
4034             //------------------------------------------------------------
4035             // Setup containers and skeleton of chart
4036
4037             var wrap = container.selectAll('g.nv-distribution').data([data]);
4038             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
4039             var gEnter = wrapEnter.append('g');
4040             var g = wrap.select('g');
4041
4042             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4043
4044             //------------------------------------------------------------
4045
4046
4047             var distWrap = g.selectAll('g.nv-dist')
4048                 .data(function(d) { return d }, function(d) { return d.key });
4049
4050             distWrap.enter().append('g');
4051             distWrap
4052                 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
4053                 .style('stroke', function(d,i) { return color(d, i) });
4054
4055             var dist = distWrap.selectAll('line.nv-dist' + axis)
4056                 .data(function(d) { return d.values })
4057             dist.enter().append('line')
4058                 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
4059                 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
4060             renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
4061                 // .transition()
4062                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4063                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4064                 .style('stroke-opacity', 0)
4065                 .remove();
4066             dist
4067                 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
4068                 .attr(naxis + '1', 0)
4069                 .attr(naxis + '2', size);
4070             renderWatch.transition(dist, 'dist')
4071                 // .transition()
4072                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4073                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4074
4075
4076             scale0 = scale.copy();
4077
4078         });
4079         renderWatch.renderEnd('distribution immediate');
4080         return chart;
4081     }
4082
4083
4084     //============================================================
4085     // Expose Public Variables
4086     //------------------------------------------------------------
4087     chart.options = nv.utils.optionsFunc.bind(chart);
4088     chart.dispatch = dispatch;
4089
4090     chart.margin = function(_) {
4091         if (!arguments.length) return margin;
4092         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4093         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4094         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4095         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4096         return chart;
4097     };
4098
4099     chart.width = function(_) {
4100         if (!arguments.length) return width;
4101         width = _;
4102         return chart;
4103     };
4104
4105     chart.axis = function(_) {
4106         if (!arguments.length) return axis;
4107         axis = _;
4108         return chart;
4109     };
4110
4111     chart.size = function(_) {
4112         if (!arguments.length) return size;
4113         size = _;
4114         return chart;
4115     };
4116
4117     chart.getData = function(_) {
4118         if (!arguments.length) return getData;
4119         getData = d3.functor(_);
4120         return chart;
4121     };
4122
4123     chart.scale = function(_) {
4124         if (!arguments.length) return scale;
4125         scale = _;
4126         return chart;
4127     };
4128
4129     chart.color = function(_) {
4130         if (!arguments.length) return color;
4131         color = nv.utils.getColor(_);
4132         return chart;
4133     };
4134
4135     chart.duration = function(_) {
4136         if (!arguments.length) return duration;
4137         duration = _;
4138         renderWatch.reset(duration);
4139         return chart;
4140     };
4141     //============================================================
4142
4143
4144     return chart;
4145 }
4146 //TODO: consider deprecating and using multibar with single series for this
4147 nv.models.historicalBar = function() {
4148     "use strict";
4149     //============================================================
4150     // Public Variables with Default Settings
4151     //------------------------------------------------------------
4152
4153     var margin = {top: 0, right: 0, bottom: 0, left: 0}
4154         , width = 960
4155         , height = 500
4156         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
4157         , x = d3.scale.linear()
4158         , y = d3.scale.linear()
4159         , getX = function(d) { return d.x }
4160         , getY = function(d) { return d.y }
4161         , forceX = []
4162         , forceY = [0]
4163         , padData = false
4164         , clipEdge = true
4165         , color = nv.utils.defaultColor()
4166         , xDomain
4167         , yDomain
4168         , xRange
4169         , yRange
4170         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
4171         , interactive = true
4172         ;
4173
4174     //============================================================
4175     var renderWatch = nv.utils.renderWatch(dispatch, 0);
4176
4177     function chart(selection) {
4178         selection.each(function(data) {
4179             renderWatch.reset();
4180             var availableWidth = width - margin.left - margin.right,
4181                 availableHeight = height - margin.top - margin.bottom,
4182                 container = d3.select(this);
4183             nv.utils.initSVG(container);
4184
4185             //------------------------------------------------------------
4186             // Setup Scales
4187
4188             x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
4189
4190             if (padData)
4191                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
4192             else
4193                 x.range(xRange || [0, availableWidth]);
4194
4195             y   .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
4196                 .range(yRange || [availableHeight, 0]);
4197
4198             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
4199
4200             if (x.domain()[0] === x.domain()[1])
4201                 x.domain()[0] ?
4202                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
4203                     : x.domain([-1,1]);
4204
4205             if (y.domain()[0] === y.domain()[1])
4206                 y.domain()[0] ?
4207                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
4208                     : y.domain([-1,1]);
4209
4210             //------------------------------------------------------------
4211
4212
4213             //------------------------------------------------------------
4214             // Setup containers and skeleton of chart
4215
4216             var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
4217             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
4218             var defsEnter = wrapEnter.append('defs');
4219             var gEnter = wrapEnter.append('g');
4220             var g = wrap.select('g');
4221
4222             gEnter.append('g').attr('class', 'nv-bars');
4223
4224             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4225
4226             //------------------------------------------------------------
4227
4228
4229             container
4230                 .on('click', function(d,i) {
4231                     dispatch.chartClick({
4232                         data: d,
4233                         index: i,
4234                         pos: d3.event,
4235                         id: id
4236                     });
4237                 });
4238
4239
4240             defsEnter.append('clipPath')
4241                 .attr('id', 'nv-chart-clip-path-' + id)
4242                 .append('rect');
4243
4244             wrap.select('#nv-chart-clip-path-' + id + ' rect')
4245                 .attr('width', availableWidth)
4246                 .attr('height', availableHeight);
4247
4248             g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
4249
4250
4251
4252             var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
4253                 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
4254
4255             bars.exit().remove();
4256
4257
4258             var barsEnter = bars.enter().append('rect')
4259                 //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4260                 .attr('x', 0 )
4261                 .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
4262                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
4263                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4264                 .on('mouseover', function(d,i) {
4265                     if (!interactive) return;
4266                     d3.select(this).classed('hover', true);
4267                     dispatch.elementMouseover({
4268                         point: d,
4269                         series: data[0],
4270                         pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
4271                         pointIndex: i,
4272                         seriesIndex: 0,
4273                         e: d3.event
4274                     });
4275
4276                 })
4277                 .on('mouseout', function(d,i) {
4278                     if (!interactive) return;
4279                     d3.select(this).classed('hover', false);
4280                     dispatch.elementMouseout({
4281                         point: d,
4282                         series: data[0],
4283                         pointIndex: i,
4284                         seriesIndex: 0,
4285                         e: d3.event
4286                     });
4287                 })
4288                 .on('click', function(d,i) {
4289                     if (!interactive) return;
4290                     dispatch.elementClick({
4291                         //label: d[label],
4292                         value: getY(d,i),
4293                         data: d,
4294                         index: i,
4295                         pos: [x(getX(d,i)), y(getY(d,i))],
4296                         e: d3.event,
4297                         id: id
4298                     });
4299                     d3.event.stopPropagation();
4300                 })
4301                 .on('dblclick', function(d,i) {
4302                     if (!interactive) return;
4303                     dispatch.elementDblClick({
4304                         //label: d[label],
4305                         value: getY(d,i),
4306                         data: d,
4307                         index: i,
4308                         pos: [x(getX(d,i)), y(getY(d,i))],
4309                         e: d3.event,
4310                         id: id
4311                     });
4312                     d3.event.stopPropagation();
4313                 });
4314
4315             bars
4316                 .attr('fill', function(d,i) { return color(d, i); })
4317                 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4318                 .watchTransition(renderWatch, 'bars')
4319                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4320                 //TODO: better width calculations that don't assume always uniform data spacing;w
4321                 .attr('width', (availableWidth / data[0].values.length) * .9 );
4322
4323
4324             bars.watchTransition(renderWatch, 'bars')
4325                 .attr('y', function(d,i) {
4326                     var rval = getY(d,i) < 0 ?
4327                         y(0) :
4328                             y(0) - y(getY(d,i)) < 1 ?
4329                         y(0) - 1 :
4330                         y(getY(d,i));
4331                     return nv.utils.NaNtoZero(rval);
4332                 })
4333                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
4334
4335         });
4336
4337         renderWatch.renderEnd('historicalBar immediate');
4338         return chart;
4339     }
4340
4341     //Create methods to allow outside functions to highlight a specific bar.
4342     chart.highlightPoint = function(pointIndex, isHoverOver) {
4343         d3.select(".nv-historicalBar-" + id)
4344             .select(".nv-bars .nv-bar-0-" + pointIndex)
4345             .classed("hover", isHoverOver)
4346         ;
4347     };
4348
4349     chart.clearHighlights = function() {
4350         d3.select(".nv-historicalBar-" + id)
4351             .select(".nv-bars .nv-bar.hover")
4352             .classed("hover", false)
4353         ;
4354     };
4355     //============================================================
4356     // Expose Public Variables
4357     //------------------------------------------------------------
4358
4359     chart.dispatch = dispatch;
4360
4361     chart.options = nv.utils.optionsFunc.bind(chart);
4362
4363     chart.x = function(_) {
4364         if (!arguments.length) return getX;
4365         getX = _;
4366         return chart;
4367     };
4368
4369     chart.y = function(_) {
4370         if (!arguments.length) return getY;
4371         getY = _;
4372         return chart;
4373     };
4374
4375     chart.margin = function(_) {
4376         if (!arguments.length) return margin;
4377         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4378         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4379         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4380         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4381         return chart;
4382     };
4383
4384     chart.width = function(_) {
4385         if (!arguments.length) return width;
4386         width = _;
4387         return chart;
4388     };
4389
4390     chart.height = function(_) {
4391         if (!arguments.length) return height;
4392         height = _;
4393         return chart;
4394     };
4395
4396     chart.xScale = function(_) {
4397         if (!arguments.length) return x;
4398         x = _;
4399         return chart;
4400     };
4401
4402     chart.yScale = function(_) {
4403         if (!arguments.length) return y;
4404         y = _;
4405         return chart;
4406     };
4407
4408     chart.xDomain = function(_) {
4409         if (!arguments.length) return xDomain;
4410         xDomain = _;
4411         return chart;
4412     };
4413
4414     chart.yDomain = function(_) {
4415         if (!arguments.length) return yDomain;
4416         yDomain = _;
4417         return chart;
4418     };
4419
4420     chart.xRange = function(_) {
4421         if (!arguments.length) return xRange;
4422         xRange = _;
4423         return chart;
4424     };
4425
4426     chart.yRange = function(_) {
4427         if (!arguments.length) return yRange;
4428         yRange = _;
4429         return chart;
4430     };
4431
4432     chart.forceX = function(_) {
4433         if (!arguments.length) return forceX;
4434         forceX = _;
4435         return chart;
4436     };
4437
4438     chart.forceY = function(_) {
4439         if (!arguments.length) return forceY;
4440         forceY = _;
4441         return chart;
4442     };
4443
4444     chart.padData = function(_) {
4445         if (!arguments.length) return padData;
4446         padData = _;
4447         return chart;
4448     };
4449
4450     chart.clipEdge = function(_) {
4451         if (!arguments.length) return clipEdge;
4452         clipEdge = _;
4453         return chart;
4454     };
4455
4456     chart.color = function(_) {
4457         if (!arguments.length) return color;
4458         color = nv.utils.getColor(_);
4459         return chart;
4460     };
4461
4462     chart.id = function(_) {
4463         if (!arguments.length) return id;
4464         id = _;
4465         return chart;
4466     };
4467
4468     chart.interactive = function(_) {
4469         if(!arguments.length) return interactive;
4470         interactive = false;
4471         return chart;
4472     };
4473
4474     //============================================================
4475
4476
4477     return chart;
4478 }
4479
4480 nv.models.historicalBarChart = function() {
4481     "use strict";
4482     //============================================================
4483     // Public Variables with Default Settings
4484     //------------------------------------------------------------
4485
4486     var bars = nv.models.historicalBar()
4487         , xAxis = nv.models.axis()
4488         , yAxis = nv.models.axis()
4489         , legend = nv.models.legend()
4490         , interactiveLayer = nv.interactiveGuideline()
4491         ;
4492
4493
4494     var margin = {top: 30, right: 90, bottom: 50, left: 90}
4495         , color = nv.utils.defaultColor()
4496         , width = null
4497         , height = null
4498         , showLegend = false
4499         , showXAxis = true
4500         , showYAxis = true
4501         , rightAlignYAxis = false
4502         , useInteractiveGuideline = false
4503         , tooltips = true
4504         , tooltip = function(key, x, y, e, graph) {
4505             return '<h3>' + key + '</h3>' +
4506                 '<p>' +  y + ' at ' + x + '</p>'
4507         }
4508         , x
4509         , y
4510         , state = {}
4511         , defaultState = null
4512         , noData = 'No Data Available.'
4513         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
4514         , transitionDuration = 250
4515         ;
4516
4517     xAxis
4518         .orient('bottom')
4519         .tickPadding(7)
4520     ;
4521     yAxis
4522         .orient( (rightAlignYAxis) ? 'right' : 'left')
4523     ;
4524
4525     //============================================================
4526
4527
4528     //============================================================
4529     // Private Variables
4530     //------------------------------------------------------------
4531
4532     var showTooltip = function(e, offsetElement) {
4533
4534         // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4535         if (offsetElement) {
4536             var svg = d3.select(offsetElement).select('svg');
4537             var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4538             if (viewBox) {
4539                 viewBox = viewBox.split(' ');
4540                 var ratio = parseInt(svg.style('width')) / viewBox[2];
4541                 e.pos[0] = e.pos[0] * ratio;
4542                 e.pos[1] = e.pos[1] * ratio;
4543             }
4544         }
4545
4546         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4547             top = e.pos[1] + ( offsetElement.offsetTop || 0),
4548             x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4549             y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4550             content = tooltip(e.series.key, x, y, e, chart);
4551
4552         nv.tooltip.show([left, top], content, null, null, offsetElement);
4553     };
4554
4555     var renderWatch = nv.utils.renderWatch(dispatch, 0);
4556
4557     //============================================================
4558
4559
4560     function chart(selection) {
4561         selection.each(function(data) {
4562             renderWatch.reset();
4563             renderWatch.models(bars);
4564             if (showXAxis) renderWatch.models(xAxis);
4565             if (showYAxis) renderWatch.models(yAxis);
4566
4567             var container = d3.select(this),
4568                 that = this;
4569             nv.utils.initSVG(container);
4570             var availableWidth = (width  || parseInt(container.style('width')) || 960)
4571                     - margin.left - margin.right,
4572                 availableHeight = (height || parseInt(container.style('height')) || 400)
4573                     - margin.top - margin.bottom;
4574
4575
4576             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4577             chart.container = this;
4578
4579             //set state.disabled
4580             state.disabled = data.map(function(d) { return !!d.disabled });
4581
4582             if (!defaultState) {
4583                 var key;
4584                 defaultState = {};
4585                 for (key in state) {
4586                     if (state[key] instanceof Array)
4587                         defaultState[key] = state[key].slice(0);
4588                     else
4589                         defaultState[key] = state[key];
4590                 }
4591             }
4592
4593             //------------------------------------------------------------
4594             // Display noData message if there's nothing to show.
4595
4596             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4597                 var noDataText = container.selectAll('.nv-noData').data([noData]);
4598
4599                 noDataText.enter().append('text')
4600                     .attr('class', 'nvd3 nv-noData')
4601                     .attr('dy', '-.7em')
4602                     .style('text-anchor', 'middle');
4603
4604                 noDataText
4605                     .attr('x', margin.left + availableWidth / 2)
4606                     .attr('y', margin.top + availableHeight / 2)
4607                     .text(function(d) { return d });
4608
4609                 return chart;
4610             } else {
4611                 container.selectAll('.nv-noData').remove();
4612             }
4613
4614             //------------------------------------------------------------
4615
4616
4617             //------------------------------------------------------------
4618             // Setup Scales
4619
4620             x = bars.xScale();
4621             y = bars.yScale();
4622
4623             //------------------------------------------------------------
4624
4625
4626             //------------------------------------------------------------
4627             // Setup containers and skeleton of chart
4628
4629             var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4630             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4631             var g = wrap.select('g');
4632
4633             gEnter.append('g').attr('class', 'nv-x nv-axis');
4634             gEnter.append('g').attr('class', 'nv-y nv-axis');
4635             gEnter.append('g').attr('class', 'nv-barsWrap');
4636             gEnter.append('g').attr('class', 'nv-legendWrap');
4637             gEnter.append('g').attr('class', 'nv-interactive');
4638
4639             //------------------------------------------------------------
4640
4641
4642             //------------------------------------------------------------
4643             // Legend
4644
4645             if (showLegend) {
4646                 legend.width(availableWidth);
4647
4648                 g.select('.nv-legendWrap')
4649                     .datum(data)
4650                     .call(legend);
4651
4652                 if ( margin.top != legend.height()) {
4653                     margin.top = legend.height();
4654                     availableHeight = (height || parseInt(container.style('height')) || 400)
4655                         - margin.top - margin.bottom;
4656                 }
4657
4658                 wrap.select('.nv-legendWrap')
4659                     .attr('transform', 'translate(0,' + (-margin.top) +')')
4660             }
4661
4662             //------------------------------------------------------------
4663
4664             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4665
4666             if (rightAlignYAxis) {
4667                 g.select(".nv-y.nv-axis")
4668                     .attr("transform", "translate(" + availableWidth + ",0)");
4669             }
4670
4671
4672             //------------------------------------------------------------
4673             // Main Chart Component(s)
4674
4675             //------------------------------------------------------------
4676             //Set up interactive layer
4677             if (useInteractiveGuideline) {
4678                 interactiveLayer
4679                     .width(availableWidth)
4680                     .height(availableHeight)
4681                     .margin({left:margin.left, top:margin.top})
4682                     .svgContainer(container)
4683                     .xScale(x);
4684                 wrap.select(".nv-interactive").call(interactiveLayer);
4685             }
4686
4687             bars
4688                 .width(availableWidth)
4689                 .height(availableHeight)
4690                 .color(data.map(function(d,i) {
4691                     return d.color || color(d, i);
4692                 }).filter(function(d,i) { return !data[i].disabled }));
4693
4694
4695             var barsWrap = g.select('.nv-barsWrap')
4696                 .datum(data.filter(function(d) { return !d.disabled }))
4697
4698             barsWrap.transition().call(bars);
4699
4700             //------------------------------------------------------------
4701
4702
4703             //------------------------------------------------------------
4704             // Setup Axes
4705
4706             if (showXAxis) {
4707                 xAxis
4708                     .scale(x)
4709                     .tickSize(-availableHeight, 0);
4710
4711                 g.select('.nv-x.nv-axis')
4712                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
4713                 g.select('.nv-x.nv-axis')
4714                     .transition()
4715                     .call(xAxis);
4716             }
4717
4718             if (showYAxis) {
4719                 yAxis
4720                     .scale(y)
4721                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4722                     .tickSize( -availableWidth, 0);
4723
4724                 g.select('.nv-y.nv-axis')
4725                     .transition()
4726                     .call(yAxis);
4727             }
4728             //------------------------------------------------------------
4729
4730
4731             //============================================================
4732             // Event Handling/Dispatching (in chart's scope)
4733             //------------------------------------------------------------
4734
4735             interactiveLayer.dispatch.on('elementMousemove', function(e) {
4736                 bars.clearHighlights()
4737
4738                 var singlePoint, pointIndex, pointXLocation, allData = [];
4739                 data
4740                     .filter(function(series, i) {
4741                         series.seriesIndex = i;
4742                         return !series.disabled;
4743                     })
4744                     .forEach(function(series,i) {
4745                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4746                         bars.highlightPoint(pointIndex,true);
4747                         var point = series.values[pointIndex];
4748                         if (typeof point === 'undefined') return;
4749                         if (typeof singlePoint === 'undefined') singlePoint = point;
4750                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4751                         allData.push({
4752                             key: series.key,
4753                             value: chart.y()(point, pointIndex),
4754                             color: color(series,series.seriesIndex)
4755                         });
4756                     });
4757
4758                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4759                 interactiveLayer.tooltip
4760                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4761                     .chartContainer(that.parentNode)
4762                     .enabled(tooltips)
4763                     .valueFormatter(function(d,i) {
4764                         return yAxis.tickFormat()(d);
4765                     })
4766                     .data(
4767                     {
4768                         value: xValue,
4769                         series: allData
4770                     }
4771                 )();
4772
4773                 interactiveLayer.renderGuideLine(pointXLocation);
4774
4775             });
4776
4777             interactiveLayer.dispatch.on("elementMouseout",function(e) {
4778                 dispatch.tooltipHide();
4779                 bars.clearHighlights();
4780             });
4781
4782             legend.dispatch.on('legendClick', function(d,i) {
4783                 d.disabled = !d.disabled;
4784
4785                 if (!data.filter(function(d) { return !d.disabled }).length) {
4786                     data.map(function(d) {
4787                         d.disabled = false;
4788                         wrap.selectAll('.nv-series').classed('disabled', false);
4789                         return d;
4790                     });
4791                 }
4792
4793                 state.disabled = data.map(function(d) { return !!d.disabled });
4794                 dispatch.stateChange(state);
4795
4796                 selection.transition().call(chart);
4797             });
4798
4799             legend.dispatch.on('legendDblclick', function(d) {
4800                 //Double clicking should always enable current series, and disabled all others.
4801                 data.forEach(function(d) {
4802                     d.disabled = true;
4803                 });
4804                 d.disabled = false;
4805
4806                 state.disabled = data.map(function(d) { return !!d.disabled });
4807                 dispatch.stateChange(state);
4808                 chart.update();
4809             });
4810
4811             dispatch.on('tooltipShow', function(e) {
4812                 if (tooltips) showTooltip(e, that.parentNode);
4813             });
4814
4815
4816             dispatch.on('changeState', function(e) {
4817
4818                 if (typeof e.disabled !== 'undefined') {
4819                     data.forEach(function(series,i) {
4820                         series.disabled = e.disabled[i];
4821                     });
4822
4823                     state.disabled = e.disabled;
4824                 }
4825
4826                 chart.update();
4827             });
4828
4829             //============================================================
4830
4831         });
4832
4833         renderWatch.renderEnd('historicalBarChart immediate');
4834         return chart;
4835     }
4836
4837
4838     //============================================================
4839     // Event Handling/Dispatching (out of chart's scope)
4840     //------------------------------------------------------------
4841
4842     bars.dispatch.on('elementMouseover.tooltip', function(e) {
4843         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
4844         dispatch.tooltipShow(e);
4845     });
4846
4847     bars.dispatch.on('elementMouseout.tooltip', function(e) {
4848         dispatch.tooltipHide(e);
4849     });
4850
4851     dispatch.on('tooltipHide', function() {
4852         if (tooltips) nv.tooltip.cleanup();
4853     });
4854
4855     //============================================================
4856
4857
4858     //============================================================
4859     // Expose Public Variables
4860     //------------------------------------------------------------
4861
4862     // expose chart's sub-components
4863     chart.dispatch = dispatch;
4864     chart.bars = bars;
4865     chart.legend = legend;
4866     chart.xAxis = xAxis;
4867     chart.yAxis = yAxis;
4868     chart.interactiveLayer = interactiveLayer;
4869
4870     d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale',
4871         'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights');
4872
4873     chart.options = nv.utils.optionsFunc.bind(chart);
4874
4875     chart.margin = function(_) {
4876         if (!arguments.length) return margin;
4877         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4878         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4879         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4880         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4881         return chart;
4882     };
4883
4884     chart.width = function(_) {
4885         if (!arguments.length) return width;
4886         width = _;
4887         return chart;
4888     };
4889
4890     chart.height = function(_) {
4891         if (!arguments.length) return height;
4892         height = _;
4893         return chart;
4894     };
4895
4896     chart.color = function(_) {
4897         if (!arguments.length) return color;
4898         color = nv.utils.getColor(_);
4899         legend.color(color);
4900         return chart;
4901     };
4902
4903     chart.showLegend = function(_) {
4904         if (!arguments.length) return showLegend;
4905         showLegend = _;
4906         return chart;
4907     };
4908
4909     chart.showXAxis = function(_) {
4910         if (!arguments.length) return showXAxis;
4911         showXAxis = _;
4912         return chart;
4913     };
4914
4915     chart.showYAxis = function(_) {
4916         if (!arguments.length) return showYAxis;
4917         showYAxis = _;
4918         return chart;
4919     };
4920
4921     chart.rightAlignYAxis = function(_) {
4922         if(!arguments.length) return rightAlignYAxis;
4923         rightAlignYAxis = _;
4924         yAxis.orient( (_) ? 'right' : 'left');
4925         return chart;
4926     };
4927
4928     chart.tooltips = function(_) {
4929         if (!arguments.length) return tooltips;
4930         tooltips = _;
4931         return chart;
4932     };
4933
4934     chart.tooltipContent = function(_) {
4935         if (!arguments.length) return tooltip;
4936         tooltip = _;
4937         return chart;
4938     };
4939
4940     chart.state = function(_) {
4941         if (!arguments.length) return state;
4942         state = _;
4943         return chart;
4944     };
4945
4946     chart.defaultState = function(_) {
4947         if (!arguments.length) return defaultState;
4948         defaultState = _;
4949         return chart;
4950     };
4951
4952     chart.noData = function(_) {
4953         if (!arguments.length) return noData;
4954         noData = _;
4955         return chart;
4956     };
4957
4958     chart.transitionDuration = function(_) {
4959         if (!arguments.length) return transitionDuration;
4960         transitionDuration = _;
4961         return chart;
4962     };
4963
4964     chart.useInteractiveGuideline = function(_) {
4965         if(!arguments.length) return useInteractiveGuideline;
4966         useInteractiveGuideline = _;
4967         if (_ === true) {
4968             chart.interactive(false);
4969         }
4970         return chart;
4971     };
4972
4973     //============================================================
4974
4975
4976     return chart;
4977 }
4978 nv.models.legend = function() {
4979     "use strict";
4980     //============================================================
4981     // Public Variables with Default Settings
4982     //------------------------------------------------------------
4983
4984     var margin = {top: 5, right: 0, bottom: 5, left: 0}
4985         , width = 400
4986         , height = 20
4987         , getKey = function(d) { return d.key }
4988         , color = nv.utils.defaultColor()
4989         , align = true
4990         , rightAlign = true
4991         , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4992         , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4993         , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4994         ;
4995
4996     //============================================================
4997
4998
4999     function chart(selection) {
5000         selection.each(function(data) {
5001             var availableWidth = width - margin.left - margin.right,
5002                 container = d3.select(this);
5003             nv.utils.initSVG(container);
5004
5005             //------------------------------------------------------------
5006             // Setup containers and skeleton of chart
5007
5008             var wrap = container.selectAll('g.nv-legend').data([data]);
5009             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
5010             var g = wrap.select('g');
5011
5012             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5013
5014             //------------------------------------------------------------
5015
5016
5017             var series = g.selectAll('.nv-series')
5018                 .data(function(d) { return d });
5019             var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
5020                 .on('mouseover', function(d,i) {
5021                     dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
5022                 })
5023                 .on('mouseout', function(d,i) {
5024                     dispatch.legendMouseout(d,i);
5025                 })
5026                 .on('click', function(d,i) {
5027                     dispatch.legendClick(d,i);
5028                     if (updateState) {
5029                         if (radioButtonMode) {
5030                             //Radio button mode: set every series to disabled,
5031                             //  and enable the clicked series.
5032                             data.forEach(function(series) { series.disabled = true});
5033                             d.disabled = false;
5034                         }
5035                         else {
5036                             d.disabled = !d.disabled;
5037                             if (data.every(function(series) { return series.disabled})) {
5038                                 //the default behavior of NVD3 legends is, if every single series
5039                                 // is disabled, turn all series' back on.
5040                                 data.forEach(function(series) { series.disabled = false});
5041                             }
5042                         }
5043                         dispatch.stateChange({
5044                             disabled: data.map(function(d) { return !!d.disabled })
5045                         });
5046                     }
5047                 })
5048                 .on('dblclick', function(d,i) {
5049                     dispatch.legendDblclick(d,i);
5050                     if (updateState) {
5051                         //the default behavior of NVD3 legends, when double clicking one,
5052                         // is to set all other series' to false, and make the double clicked series enabled.
5053                         data.forEach(function(series) {
5054                             series.disabled = true;
5055                         });
5056                         d.disabled = false;
5057                         dispatch.stateChange({
5058                             disabled: data.map(function(d) { return !!d.disabled })
5059                         });
5060                     }
5061                 });
5062             seriesEnter.append('circle')
5063                 .style('stroke-width', 2)
5064                 .attr('class','nv-legend-symbol')
5065                 .attr('r', 5);
5066             seriesEnter.append('text')
5067                 .attr('text-anchor', 'start')
5068                 .attr('class','nv-legend-text')
5069                 .attr('dy', '.32em')
5070                 .attr('dx', '8');
5071             series.classed('disabled', function(d) { return d.disabled });
5072             series.exit().remove();
5073             series.select('circle')
5074                 .style('fill', function(d,i) { return d.color || color(d,i)})
5075                 .style('stroke', function(d,i) { return d.color || color(d, i) });
5076             series.select('text').text(getKey);
5077
5078
5079             //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
5080
5081             // NEW ALIGNING CODE, TODO: clean up
5082             if (align) {
5083
5084                 var seriesWidths = [];
5085                 series.each(function(d,i) {
5086                     var legendText = d3.select(this).select('text');
5087                     var nodeTextLength;
5088                     try {
5089                         nodeTextLength = legendText.node().getComputedTextLength();
5090                         // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
5091                         if(nodeTextLength <= 0) throw Error();
5092                     }
5093                     catch(e) {
5094                         nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5095                     }
5096
5097                     seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
5098                 });
5099
5100                 var seriesPerRow = 0;
5101                 var legendWidth = 0;
5102                 var columnWidths = [];
5103
5104                 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
5105                     columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
5106                     legendWidth += seriesWidths[seriesPerRow++];
5107                 }
5108                 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
5109
5110
5111                 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
5112                     columnWidths = [];
5113                     seriesPerRow--;
5114
5115                     for (var k = 0; k < seriesWidths.length; k++) {
5116                         if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
5117                             columnWidths[k % seriesPerRow] = seriesWidths[k];
5118                     }
5119
5120                     legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
5121                         return prev + cur;
5122                     });
5123                 }
5124
5125                 var xPositions = [];
5126                 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5127                     xPositions[i] = curX;
5128                     curX += columnWidths[i];
5129                 }
5130
5131                 series
5132                     .attr('transform', function(d, i) {
5133                         return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
5134                     });
5135
5136                 //position legend as far right as possible within the total width
5137                 if (rightAlign) {
5138                     g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5139                 }
5140                 else {
5141                     g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5142                 }
5143
5144                 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
5145
5146             } else {
5147
5148                 var ypos = 5,
5149                     newxpos = 5,
5150                     maxwidth = 0,
5151                     xpos;
5152                 series
5153                     .attr('transform', function(d, i) {
5154                         var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
5155                         xpos = newxpos;
5156
5157                         if (width < margin.left + margin.right + xpos + length) {
5158                             newxpos = xpos = 5;
5159                             ypos += 20;
5160                         }
5161
5162                         newxpos += length;
5163                         if (newxpos > maxwidth) maxwidth = newxpos;
5164
5165                         return 'translate(' + xpos + ',' + ypos + ')';
5166                     });
5167
5168                 //position legend as far right as possible within the total width
5169                 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5170
5171                 height = margin.top + margin.bottom + ypos + 15;
5172
5173             }
5174
5175         });
5176
5177         return chart;
5178     }
5179
5180
5181     //============================================================
5182     // Expose Public Variables
5183     //------------------------------------------------------------
5184
5185     chart.dispatch = dispatch;
5186     chart.options = nv.utils.optionsFunc.bind(chart);
5187
5188     chart.margin = function(_) {
5189         if (!arguments.length) return margin;
5190         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5191         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5192         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5193         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5194         return chart;
5195     };
5196
5197     chart.width = function(_) {
5198         if (!arguments.length) return width;
5199         width = _;
5200         return chart;
5201     };
5202
5203     chart.height = function(_) {
5204         if (!arguments.length) return height;
5205         height = _;
5206         return chart;
5207     };
5208
5209     chart.key = function(_) {
5210         if (!arguments.length) return getKey;
5211         getKey = _;
5212         return chart;
5213     };
5214
5215     chart.color = function(_) {
5216         if (!arguments.length) return color;
5217         color = nv.utils.getColor(_);
5218         return chart;
5219     };
5220
5221     chart.align = function(_) {
5222         if (!arguments.length) return align;
5223         align = _;
5224         return chart;
5225     };
5226
5227     chart.rightAlign = function(_) {
5228         if (!arguments.length) return rightAlign;
5229         rightAlign = _;
5230         return chart;
5231     };
5232
5233     chart.updateState = function(_) {
5234         if (!arguments.length) return updateState;
5235         updateState = _;
5236         return chart;
5237     };
5238
5239     chart.radioButtonMode = function(_) {
5240         if (!arguments.length) return radioButtonMode;
5241         radioButtonMode = _;
5242         return chart;
5243     };
5244
5245     //============================================================
5246
5247
5248     return chart;
5249 }
5250
5251 nv.models.line = function() {
5252     "use strict";
5253     //============================================================
5254     // Public Variables with Default Settings
5255     //------------------------------------------------------------
5256
5257     var  scatter = nv.models.scatter()
5258         ;
5259
5260     var margin = {top: 0, right: 0, bottom: 0, left: 0}
5261         , width = 960
5262         , height = 500
5263         , color = nv.utils.defaultColor() // a function that returns a color
5264         , getX = function(d) { return d.x } // accessor to get the x value from a data point
5265         , getY = function(d) { return d.y } // accessor to get the y value from a data point
5266         , 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
5267         , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5268         , clipEdge = false // if true, masks lines within x and y scale
5269         , x //can be accessed via chart.xScale()
5270         , y //can be accessed via chart.yScale()
5271         , interpolate = "linear" // controls the line interpolation
5272         , duration = 250
5273         , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
5274         ;
5275
5276     scatter
5277         .size(16) // default size
5278         .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5279     ;
5280
5281     //============================================================
5282
5283
5284     //============================================================
5285     // Private Variables
5286     //------------------------------------------------------------
5287
5288     var x0, y0 //used to store previous scales
5289         , renderWatch = nv.utils.renderWatch(dispatch, duration)
5290         ;
5291
5292     //============================================================
5293
5294
5295     function chart(selection) {
5296         renderWatch.reset();
5297         renderWatch.models(scatter);
5298         selection.each(function(data) {
5299             var availableWidth = width - margin.left - margin.right,
5300                 availableHeight = height - margin.top - margin.bottom,
5301                 container = d3.select(this);
5302             nv.utils.initSVG(container);
5303             //------------------------------------------------------------
5304             // Setup Scales
5305
5306             x = scatter.xScale();
5307             y = scatter.yScale();
5308
5309             x0 = x0 || x;
5310             y0 = y0 || y;
5311
5312             //------------------------------------------------------------
5313
5314
5315             //------------------------------------------------------------
5316             // Setup containers and skeleton of chart
5317
5318             var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5319             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5320             var defsEnter = wrapEnter.append('defs');
5321             var gEnter = wrapEnter.append('g');
5322             var g = wrap.select('g')
5323
5324             gEnter.append('g').attr('class', 'nv-groups');
5325             gEnter.append('g').attr('class', 'nv-scatterWrap');
5326
5327             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5328
5329             //------------------------------------------------------------
5330
5331
5332
5333
5334             scatter
5335                 .width(availableWidth)
5336                 .height(availableHeight)
5337
5338             var scatterWrap = wrap.select('.nv-scatterWrap');
5339             //.datum(data); // Data automatically trickles down from the wrap
5340
5341             scatterWrap.call(scatter);
5342
5343
5344
5345             defsEnter.append('clipPath')
5346                 .attr('id', 'nv-edge-clip-' + scatter.id())
5347                 .append('rect');
5348
5349             wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5350                 .attr('width', availableWidth)
5351                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
5352
5353             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5354             scatterWrap
5355                 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5356
5357
5358
5359             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5360                 .data(function(d) { return d }, function(d) { return d.key });
5361             groups.enter().append('g')
5362                 .style('stroke-opacity', 1e-6)
5363                 .style('fill-opacity', 1e-6);
5364
5365             groups.exit().remove();
5366
5367             groups
5368                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5369                 .classed('hover', function(d) { return d.hover })
5370                 .style('fill', function(d,i){ return color(d, i) })
5371                 .style('stroke', function(d,i){ return color(d, i)});
5372             groups.watchTransition(renderWatch, 'line: groups')
5373                 .style('stroke-opacity', 1)
5374                 .style('fill-opacity', .5);
5375
5376
5377
5378             var areaPaths = groups.selectAll('path.nv-area')
5379                 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5380             areaPaths.enter().append('path')
5381                 .attr('class', 'nv-area')
5382                 .attr('d', function(d) {
5383                     return d3.svg.area()
5384                         .interpolate(interpolate)
5385                         .defined(defined)
5386                         .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5387                         .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5388                         .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5389                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5390                         .apply(this, [d.values])
5391                 });
5392             groups.exit().selectAll('path.nv-area')
5393                 .remove();
5394
5395             areaPaths.watchTransition(renderWatch, 'line: areaPaths')
5396                 .attr('d', function(d) {
5397                     return d3.svg.area()
5398                         .interpolate(interpolate)
5399                         .defined(defined)
5400                         .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5401                         .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5402                         .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5403                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5404                         .apply(this, [d.values])
5405                 });
5406
5407
5408
5409             var linePaths = groups.selectAll('path.nv-line')
5410                 .data(function(d) { return [d.values] });
5411             linePaths.enter().append('path')
5412                 .attr('class', 'nv-line')
5413                 .attr('d',
5414                 d3.svg.line()
5415                     .interpolate(interpolate)
5416                     .defined(defined)
5417                     .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5418                     .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5419             );
5420
5421             linePaths.watchTransition(renderWatch, 'line: linePaths')
5422                 .attr('d',
5423                 d3.svg.line()
5424                     .interpolate(interpolate)
5425                     .defined(defined)
5426                     .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5427                     .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5428             );
5429
5430
5431
5432             //store old scales for use in transitions on update
5433             x0 = x.copy();
5434             y0 = y.copy();
5435
5436         });
5437         renderWatch.renderEnd('line immediate');
5438         return chart;
5439     }
5440
5441
5442     //============================================================
5443     // Expose Public Variables
5444     //------------------------------------------------------------
5445
5446     chart.dispatch = dispatch;
5447     chart.scatter = scatter;
5448     // Pass through events
5449     scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); })
5450     scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); })
5451     scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); })
5452
5453     d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
5454         'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5455
5456     chart.options = nv.utils.optionsFunc.bind(chart);
5457
5458     chart.margin = function(_) {
5459         if (!arguments.length) return margin;
5460         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5461         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5462         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5463         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5464         return chart;
5465     };
5466
5467     chart.width = function(_) {
5468         if (!arguments.length) return width;
5469         width = _;
5470         return chart;
5471     };
5472
5473     chart.height = function(_) {
5474         if (!arguments.length) return height;
5475         height = _;
5476         return chart;
5477     };
5478
5479     chart.x = function(_) {
5480         if (!arguments.length) return getX;
5481         getX = _;
5482         scatter.x(_);
5483         return chart;
5484     };
5485
5486     chart.y = function(_) {
5487         if (!arguments.length) return getY;
5488         getY = _;
5489         scatter.y(_);
5490         return chart;
5491     };
5492
5493     chart.clipEdge = function(_) {
5494         if (!arguments.length) return clipEdge;
5495         clipEdge = _;
5496         return chart;
5497     };
5498
5499     chart.color = function(_) {
5500         if (!arguments.length) return color;
5501         color = nv.utils.getColor(_);
5502         scatter.color(color);
5503         return chart;
5504     };
5505
5506     chart.interpolate = function(_) {
5507         if (!arguments.length) return interpolate;
5508         interpolate = _;
5509         return chart;
5510     };
5511
5512     chart.defined = function(_) {
5513         if (!arguments.length) return defined;
5514         defined = _;
5515         return chart;
5516     };
5517
5518     chart.isArea = function(_) {
5519         if (!arguments.length) return isArea;
5520         isArea = d3.functor(_);
5521         return chart;
5522     };
5523
5524     chart.duration = function(_) {
5525         if (!arguments.length) return duration;
5526         duration = _;
5527         renderWatch.reset(duration);
5528         scatter.duration(duration);
5529         return chart;
5530     };
5531
5532     //============================================================
5533
5534
5535     return chart;
5536 }
5537 nv.models.lineChart = function() {
5538     "use strict";
5539
5540     //============================================================
5541     // Public Variables with Default Settings
5542     //------------------------------------------------------------
5543
5544     var lines = nv.models.line()
5545         , xAxis = nv.models.axis()
5546         , yAxis = nv.models.axis()
5547         , legend = nv.models.legend()
5548         , interactiveLayer = nv.interactiveGuideline()
5549         ;
5550
5551     var margin = {top: 30, right: 20, bottom: 50, left: 60}
5552         , color = nv.utils.defaultColor()
5553         , width = null
5554         , height = null
5555         , showLegend = true
5556         , showXAxis = true
5557         , showYAxis = true
5558         , rightAlignYAxis = false
5559         , useInteractiveGuideline = false
5560         , tooltips = true
5561         , tooltip = function(key, x, y, e, graph) {
5562             return '<h3>' + key + '</h3>' +
5563                 '<p>' +  y + ' at ' + x + '</p>'
5564         }
5565         , x
5566         , y
5567         , state = nv.utils.state()
5568         , defaultState = null
5569         , noData = 'No Data Available.'
5570         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
5571         , duration = 250
5572         ;
5573
5574     xAxis
5575         .orient('bottom')
5576         .tickPadding(7)
5577     ;
5578     yAxis
5579         .orient((rightAlignYAxis) ? 'right' : 'left')
5580     ;
5581
5582     //============================================================
5583
5584
5585     //============================================================
5586     // Private Variables
5587     //------------------------------------------------------------
5588
5589     var showTooltip = function(e, offsetElement) {
5590         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5591             top = e.pos[1] + ( offsetElement.offsetTop || 0),
5592             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5593             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5594             content = tooltip(e.series.key, x, y, e, chart);
5595
5596         nv.tooltip.show([left, top], content, null, null, offsetElement);
5597     };
5598
5599     var renderWatch = nv.utils.renderWatch(dispatch, duration);
5600
5601     var stateGetter = function(data) {
5602         return function(){
5603             return {
5604                 active: data.map(function(d) { return !d.disabled })
5605             };
5606         }
5607     };
5608
5609     var stateSetter = function(data) {
5610         return function(state) {
5611             if (state.active !== undefined)
5612                 data.forEach(function(series,i) {
5613                     series.disabled = !state.active[i];
5614                 });
5615         }
5616     };
5617
5618     //============================================================
5619
5620
5621     function chart(selection) {
5622         renderWatch.reset();
5623         renderWatch.models(lines);
5624         if (showXAxis) renderWatch.models(xAxis);
5625         if (showYAxis) renderWatch.models(yAxis);
5626
5627         selection.each(function(data) {
5628             var container = d3.select(this),
5629                 that = this;
5630             nv.utils.initSVG(container);
5631             var availableWidth = (width  || parseInt(container.style('width')) || 960)
5632                     - margin.left - margin.right,
5633                 availableHeight = (height || parseInt(container.style('height')) || 400)
5634                     - margin.top - margin.bottom;
5635
5636
5637             chart.update = function() {
5638                 if (duration === 0)
5639                     container.call(chart);
5640                 else
5641                     container.transition().duration(duration).call(chart)
5642             };
5643             chart.container = this;
5644
5645             state
5646                 .setter(stateSetter(data), chart.update)
5647                 .getter(stateGetter(data))
5648                 .update();
5649
5650             // DEPRECATED set state.disableddisabled
5651             state.disabled = data.map(function(d) { return !!d.disabled });
5652
5653             if (!defaultState) {
5654                 var key;
5655                 defaultState = {};
5656                 for (key in state) {
5657                     if (state[key] instanceof Array)
5658                         defaultState[key] = state[key].slice(0);
5659                     else
5660                         defaultState[key] = state[key];
5661                 }
5662             }
5663
5664             //------------------------------------------------------------
5665             // Display noData message if there's nothing to show.
5666
5667             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5668                 var noDataText = container.selectAll('.nv-noData').data([noData]);
5669
5670                 noDataText.enter().append('text')
5671                     .attr('class', 'nvd3 nv-noData')
5672                     .attr('dy', '-.7em')
5673                     .style('text-anchor', 'middle');
5674
5675                 noDataText
5676                     .attr('x', margin.left + availableWidth / 2)
5677                     .attr('y', margin.top + availableHeight / 2)
5678                     .text(function(d) { return d });
5679
5680                 return chart;
5681             } else {
5682                 container.selectAll('.nv-noData').remove();
5683             }
5684
5685             //------------------------------------------------------------
5686
5687
5688             //------------------------------------------------------------
5689             // Setup Scales
5690
5691             x = lines.xScale();
5692             y = lines.yScale();
5693
5694             //------------------------------------------------------------
5695
5696
5697             //------------------------------------------------------------
5698             // Setup containers and skeleton of chart
5699
5700             var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5701             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5702             var g = wrap.select('g');
5703
5704             gEnter.append("rect").style("opacity",0);
5705             gEnter.append('g').attr('class', 'nv-x nv-axis');
5706             gEnter.append('g').attr('class', 'nv-y nv-axis');
5707             gEnter.append('g').attr('class', 'nv-linesWrap');
5708             gEnter.append('g').attr('class', 'nv-legendWrap');
5709             gEnter.append('g').attr('class', 'nv-interactive');
5710
5711             g.select("rect")
5712                 .attr("width",availableWidth)
5713                 .attr("height",(availableHeight > 0) ? availableHeight : 0);
5714             //------------------------------------------------------------
5715             // Legend
5716
5717             if (showLegend) {
5718                 legend.width(availableWidth);
5719
5720                 g.select('.nv-legendWrap')
5721                     .datum(data)
5722                     .call(legend);
5723
5724                 if ( margin.top != legend.height()) {
5725                     margin.top = legend.height();
5726                     availableHeight = (height || parseInt(container.style('height')) || 400)
5727                         - margin.top - margin.bottom;
5728                 }
5729
5730                 wrap.select('.nv-legendWrap')
5731                     .attr('transform', 'translate(0,' + (-margin.top) +')')
5732             }
5733
5734             //------------------------------------------------------------
5735
5736             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5737
5738             if (rightAlignYAxis) {
5739                 g.select(".nv-y.nv-axis")
5740                     .attr("transform", "translate(" + availableWidth + ",0)");
5741             }
5742
5743             //------------------------------------------------------------
5744             // Main Chart Component(s)
5745
5746
5747             //------------------------------------------------------------
5748             //Set up interactive layer
5749             if (useInteractiveGuideline) {
5750                 interactiveLayer
5751                     .width(availableWidth)
5752                     .height(availableHeight)
5753                     .margin({left:margin.left, top:margin.top})
5754                     .svgContainer(container)
5755                     .xScale(x);
5756                 wrap.select(".nv-interactive").call(interactiveLayer);
5757             }
5758
5759
5760             lines
5761                 .width(availableWidth)
5762                 .height(availableHeight)
5763                 .color(data.map(function(d,i) {
5764                     return d.color || color(d, i);
5765                 }).filter(function(d,i) { return !data[i].disabled }));
5766
5767
5768             var linesWrap = g.select('.nv-linesWrap')
5769                 .datum(data.filter(function(d) { return !d.disabled }))
5770
5771             linesWrap.call(lines);
5772
5773             //------------------------------------------------------------
5774
5775
5776             //------------------------------------------------------------
5777             // Setup Axes
5778
5779             if (showXAxis) {
5780                 xAxis
5781                     .scale(x)
5782                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5783                     .tickSize(-availableHeight, 0);
5784
5785                 g.select('.nv-x.nv-axis')
5786                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
5787                 g.select('.nv-x.nv-axis')
5788                     .call(xAxis);
5789             }
5790
5791             if (showYAxis) {
5792                 yAxis
5793                     .scale(y)
5794                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5795                     .tickSize( -availableWidth, 0);
5796
5797                 g.select('.nv-y.nv-axis')
5798                     .call(yAxis);
5799             }
5800             //------------------------------------------------------------
5801
5802
5803             //============================================================
5804             // Event Handling/Dispatching (in chart's scope)
5805             //------------------------------------------------------------
5806
5807             legend.dispatch.on('stateChange', function(newState) {
5808                 for (var key in newState)
5809                     state[key] = newState[key];
5810                 dispatch.stateChange(state);
5811                 chart.update();
5812             });
5813
5814             interactiveLayer.dispatch.on('elementMousemove', function(e) {
5815                 lines.clearHighlights();
5816                 var singlePoint, pointIndex, pointXLocation, allData = [];
5817                 data
5818                     .filter(function(series, i) {
5819                         series.seriesIndex = i;
5820                         return !series.disabled;
5821                     })
5822                     .forEach(function(series,i) {
5823                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5824                         lines.highlightPoint(i, pointIndex, true);
5825                         var point = series.values[pointIndex];
5826                         if (typeof point === 'undefined') return;
5827                         if (typeof singlePoint === 'undefined') singlePoint = point;
5828                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5829                         allData.push({
5830                             key: series.key,
5831                             value: chart.y()(point, pointIndex),
5832                             color: color(series,series.seriesIndex)
5833                         });
5834                     });
5835                 //Highlight the tooltip entry based on which point the mouse is closest to.
5836                 if (allData.length > 2) {
5837                     var yValue = chart.yScale().invert(e.mouseY);
5838                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
5839                     var threshold = 0.03 * domainExtent;
5840                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
5841                     if (indexToHighlight !== null)
5842                         allData[indexToHighlight].highlight = true;
5843                 }
5844
5845                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5846                 interactiveLayer.tooltip
5847                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5848                     .chartContainer(that.parentNode)
5849                     .enabled(tooltips)
5850                     .valueFormatter(function(d,i) {
5851                         return yAxis.tickFormat()(d);
5852                     })
5853                     .data(
5854                     {
5855                         value: xValue,
5856                         series: allData
5857                     }
5858                 )();
5859
5860                 interactiveLayer.renderGuideLine(pointXLocation);
5861
5862             });
5863
5864             interactiveLayer.dispatch.on('elementClick', function(e) {
5865                 var pointXLocation, allData = [];
5866
5867                 data.filter(function(series, i) {
5868                     series.seriesIndex = i;
5869                     return !series.disabled;
5870                 }).forEach(function(series) {
5871                     var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5872                     var point = series.values[pointIndex];
5873                     if (typeof point === 'undefined') return;
5874                     if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5875                     var yPos = chart.yScale()(chart.y()(point,pointIndex));
5876                     allData.push({
5877                         point: point,
5878                         pointIndex: pointIndex,
5879                         pos: [pointXLocation, yPos],
5880                         seriesIndex: series.seriesIndex,
5881                         series: series
5882                     });
5883                 });
5884
5885                 lines.dispatch.elementClick(allData);
5886             });
5887
5888             interactiveLayer.dispatch.on("elementMouseout",function(e) {
5889                 dispatch.tooltipHide();
5890                 lines.clearHighlights();
5891             });
5892
5893             dispatch.on('tooltipShow', function(e) {
5894                 if (tooltips) showTooltip(e, that.parentNode);
5895             });
5896
5897
5898             dispatch.on('changeState', function(e) {
5899
5900                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
5901                     data.forEach(function(series,i) {
5902                         series.disabled = e.disabled[i];
5903                     });
5904
5905                     state.disabled = e.disabled;
5906                 }
5907
5908                 chart.update();
5909             });
5910
5911             //============================================================
5912
5913         });
5914
5915         renderWatch.renderEnd('lineChart immediate');
5916         return chart;
5917     }
5918
5919
5920     //============================================================
5921     // Event Handling/Dispatching (out of chart's scope)
5922     //------------------------------------------------------------
5923
5924     lines.dispatch.on('elementMouseover.tooltip', function(e) {
5925         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5926         dispatch.tooltipShow(e);
5927     });
5928
5929     lines.dispatch.on('elementMouseout.tooltip', function(e) {
5930         dispatch.tooltipHide(e);
5931     });
5932
5933     dispatch.on('tooltipHide', function() {
5934         if (tooltips) nv.tooltip.cleanup();
5935     });
5936
5937     //============================================================
5938
5939
5940     //============================================================
5941     // Expose Public Variables
5942     //------------------------------------------------------------
5943
5944     // expose chart's sub-components
5945     chart.dispatch = dispatch;
5946     chart.lines = lines;
5947     chart.legend = legend;
5948     chart.xAxis = xAxis;
5949     chart.yAxis = yAxis;
5950     chart.interactiveLayer = interactiveLayer;
5951     // DO NOT DELETE. This is currently overridden below
5952     // until deprecated portions are removed.
5953     chart.state = state;
5954
5955     d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5956         , 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5957
5958     chart.options = nv.utils.optionsFunc.bind(chart);
5959
5960     chart.margin = function(_) {
5961         if (!arguments.length) return margin;
5962         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5963         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5964         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5965         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5966         return chart;
5967     };
5968
5969     chart.width = function(_) {
5970         if (!arguments.length) return width;
5971         width = _;
5972         return chart;
5973     };
5974
5975     chart.height = function(_) {
5976         if (!arguments.length) return height;
5977         height = _;
5978         return chart;
5979     };
5980
5981     chart.color = function(_) {
5982         if (!arguments.length) return color;
5983         color = nv.utils.getColor(_);
5984         legend.color(color);
5985         return chart;
5986     };
5987
5988     chart.showLegend = function(_) {
5989         if (!arguments.length) return showLegend;
5990         showLegend = _;
5991         return chart;
5992     };
5993
5994     chart.showXAxis = function(_) {
5995         if (!arguments.length) return showXAxis;
5996         showXAxis = _;
5997         return chart;
5998     };
5999
6000     chart.showYAxis = function(_) {
6001         if (!arguments.length) return showYAxis;
6002         showYAxis = _;
6003         return chart;
6004     };
6005
6006     chart.rightAlignYAxis = function(_) {
6007         if(!arguments.length) return rightAlignYAxis;
6008         rightAlignYAxis = _;
6009         yAxis.orient( (_) ? 'right' : 'left');
6010         return chart;
6011     };
6012
6013     chart.useInteractiveGuideline = function(_) {
6014         if(!arguments.length) return useInteractiveGuideline;
6015         useInteractiveGuideline = _;
6016         if (_ === true) {
6017             chart.interactive(false);
6018             chart.useVoronoi(false);
6019         }
6020         return chart;
6021     };
6022
6023     chart.tooltips = function(_) {
6024         if (!arguments.length) return tooltips;
6025         tooltips = _;
6026         return chart;
6027     };
6028
6029     chart.tooltipContent = function(_) {
6030         if (!arguments.length) return tooltip;
6031         tooltip = _;
6032         return chart;
6033     };
6034
6035     // DEPRECATED
6036     chart.state = function(_) {
6037         nv.deprecated('lineChart.state');
6038         if (!arguments.length) return state;
6039         state = _;
6040         return chart;
6041     };
6042     for (var key in state) {
6043         chart.state[key] = state[key];
6044     }
6045     // END DEPRECATED
6046
6047     chart.defaultState = function(_) {
6048         if (!arguments.length) return defaultState;
6049         defaultState = _;
6050         return chart;
6051     };
6052
6053     chart.noData = function(_) {
6054         if (!arguments.length) return noData;
6055         noData = _;
6056         return chart;
6057     };
6058
6059     chart.transitionDuration = function(_) {
6060         nv.deprecated('lineChart.transitionDuration');
6061         return chart.duration(_);
6062     };
6063
6064     chart.duration = function(_) {
6065         if (!arguments.length) return duration;
6066         duration = _;
6067         renderWatch.reset(duration);
6068         lines.duration(duration);
6069         xAxis.duration(duration);
6070         yAxis.duration(duration);
6071         return chart;
6072     };
6073
6074     //============================================================
6075
6076     return chart;
6077 };
6078
6079 nv.models.linePlusBarChart = function() {
6080     "use strict";
6081     //============================================================
6082     // Public Variables with Default Settings
6083     //------------------------------------------------------------
6084
6085     var lines = nv.models.line()
6086         , bars = nv.models.historicalBar()
6087         , xAxis = nv.models.axis()
6088         , y1Axis = nv.models.axis()
6089         , y2Axis = nv.models.axis()
6090         , legend = nv.models.legend()
6091         ;
6092
6093     var margin = {top: 30, right: 60, bottom: 50, left: 60}
6094         , width = null
6095         , height = null
6096         , getX = function(d) { return d.x }
6097         , getY = function(d) { return d.y }
6098         , color = nv.utils.defaultColor()
6099         , showLegend = true
6100         , tooltips = true
6101         , tooltip = function(key, x, y, e, graph) {
6102             return '<h3>' + key + '</h3>' +
6103                 '<p>' +  y + ' at ' + x + '</p>';
6104         }
6105         , x
6106         , y1
6107         , y2
6108         , state = nv.utils.state()
6109         , defaultState = null
6110         , noData = "No Data Available."
6111         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
6112         ;
6113
6114     bars
6115         .padData(true)
6116     ;
6117     lines
6118         .clipEdge(false)
6119         .padData(true)
6120     ;
6121     xAxis
6122         .orient('bottom')
6123         .tickPadding(7)
6124         .highlightZero(false)
6125     ;
6126     y1Axis
6127         .orient('left')
6128     ;
6129     y2Axis
6130         .orient('right')
6131     ;
6132
6133     //============================================================
6134
6135
6136     //============================================================
6137     // Private Variables
6138     //------------------------------------------------------------
6139
6140     var showTooltip = function(e, offsetElement) {
6141             var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6142                 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6143                 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6144                 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6145                 content = tooltip(e.series.key, x, y, e, chart);
6146
6147             nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6148         }
6149         ;
6150
6151     var stateGetter = function(data) {
6152         return function(){
6153             return {
6154                 active: data.map(function(d) { return !d.disabled })
6155             };
6156         }
6157     };
6158
6159     var stateSetter = function(data) {
6160         return function(state) {
6161             if (state.active !== undefined)
6162                 data.forEach(function(series,i) {
6163                     series.disabled = !state.active[i];
6164                 });
6165         }
6166     };
6167
6168     //------------------------------------------------------------
6169
6170
6171
6172     function chart(selection) {
6173         selection.each(function(data) {
6174             var container = d3.select(this),
6175                 that = this;
6176             nv.utils.initSVG(container);
6177             var availableWidth = (width  || parseInt(container.style('width')) || 960)
6178                     - margin.left - margin.right,
6179                 availableHeight = (height || parseInt(container.style('height')) || 400)
6180                     - margin.top - margin.bottom;
6181
6182             chart.update = function() { container.transition().call(chart); };
6183             // chart.container = this;
6184
6185             state
6186                 .setter(stateSetter(data), chart.update)
6187                 .getter(stateGetter(data))
6188                 .update();
6189
6190             // DEPRECATED set state.disableddisabled
6191             state.disabled = data.map(function(d) { return !!d.disabled });
6192
6193             if (!defaultState) {
6194                 var key;
6195                 defaultState = {};
6196                 for (key in state) {
6197                     if (state[key] instanceof Array)
6198                         defaultState[key] = state[key].slice(0);
6199                     else
6200                         defaultState[key] = state[key];
6201                 }
6202             }
6203
6204             //------------------------------------------------------------
6205             // Display No Data message if there's nothing to show.
6206
6207             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6208                 var noDataText = container.selectAll('.nv-noData').data([noData]);
6209
6210                 noDataText.enter().append('text')
6211                     .attr('class', 'nvd3 nv-noData')
6212                     .attr('dy', '-.7em')
6213                     .style('text-anchor', 'middle');
6214
6215                 noDataText
6216                     .attr('x', margin.left + availableWidth / 2)
6217                     .attr('y', margin.top + availableHeight / 2)
6218                     .text(function(d) { return d });
6219
6220                 return chart;
6221             } else {
6222                 container.selectAll('.nv-noData').remove();
6223             }
6224
6225             //------------------------------------------------------------
6226
6227
6228             //------------------------------------------------------------
6229             // Setup Scales
6230
6231             var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6232             var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6233
6234             //x = xAxis.scale();
6235             x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
6236             //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
6237             y1 = bars.yScale();
6238             y2 = lines.yScale();
6239
6240             //------------------------------------------------------------
6241
6242             //------------------------------------------------------------
6243             // Setup containers and skeleton of chart
6244
6245             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6246             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6247             var g = wrap.select('g');
6248
6249             gEnter.append('g').attr('class', 'nv-x nv-axis');
6250             gEnter.append('g').attr('class', 'nv-y1 nv-axis');
6251             gEnter.append('g').attr('class', 'nv-y2 nv-axis');
6252             gEnter.append('g').attr('class', 'nv-barsWrap');
6253             gEnter.append('g').attr('class', 'nv-linesWrap');
6254             gEnter.append('g').attr('class', 'nv-legendWrap');
6255
6256             //------------------------------------------------------------
6257
6258
6259             //------------------------------------------------------------
6260             // Legend
6261
6262             if (showLegend) {
6263                 legend.width( availableWidth / 2 );
6264
6265                 g.select('.nv-legendWrap')
6266                     .datum(data.map(function(series) {
6267                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6268                         series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6269                         return series;
6270                     }))
6271                     .call(legend);
6272
6273                 if ( margin.top != legend.height()) {
6274                     margin.top = legend.height();
6275                     availableHeight = (height || parseInt(container.style('height')) || 400)
6276                         - margin.top - margin.bottom;
6277                 }
6278
6279                 g.select('.nv-legendWrap')
6280                     .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6281             }
6282
6283             //------------------------------------------------------------
6284
6285
6286             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6287
6288
6289             //------------------------------------------------------------
6290             // Main Chart Component(s)
6291
6292
6293             lines
6294                 .width(availableWidth)
6295                 .height(availableHeight)
6296                 .color(data.map(function(d,i) {
6297                     return d.color || color(d, i);
6298                 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6299
6300             bars
6301                 .width(availableWidth)
6302                 .height(availableHeight)
6303                 .color(data.map(function(d,i) {
6304                     return d.color || color(d, i);
6305                 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6306
6307
6308
6309             var barsWrap = g.select('.nv-barsWrap')
6310                 .datum(dataBars.length ? dataBars : [{values:[]}]);
6311
6312             var linesWrap = g.select('.nv-linesWrap')
6313                 .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
6314             //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
6315
6316             d3.transition(barsWrap).call(bars);
6317             d3.transition(linesWrap).call(lines);
6318
6319             //------------------------------------------------------------
6320
6321
6322             //------------------------------------------------------------
6323             // Setup Axes
6324
6325             xAxis
6326                 .scale(x)
6327                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6328                 .tickSize(-availableHeight, 0);
6329
6330             g.select('.nv-x.nv-axis')
6331                 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6332             d3.transition(g.select('.nv-x.nv-axis'))
6333                 .call(xAxis);
6334
6335
6336             y1Axis
6337                 .scale(y1)
6338                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6339                 .tickSize(-availableWidth, 0);
6340
6341             d3.transition(g.select('.nv-y1.nv-axis'))
6342                 .style('opacity', dataBars.length ? 1 : 0)
6343                 .call(y1Axis);
6344
6345
6346             y2Axis
6347                 .scale(y2)
6348                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6349                 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6350
6351             g.select('.nv-y2.nv-axis')
6352                 .style('opacity', dataLines.length ? 1 : 0)
6353                 .attr('transform', 'translate(' + availableWidth + ',0)');
6354             //.attr('transform', 'translate(' + x.range()[1] + ',0)');
6355
6356             d3.transition(g.select('.nv-y2.nv-axis'))
6357                 .call(y2Axis);
6358
6359             //------------------------------------------------------------
6360
6361
6362             //============================================================
6363             // Event Handling/Dispatching (in chart's scope)
6364             //------------------------------------------------------------
6365
6366             legend.dispatch.on('stateChange', function(newState) {
6367                 for (var key in newState)
6368                     state[key] = newState[key];
6369                 dispatch.stateChange(state);
6370                 chart.update();
6371             });
6372
6373             dispatch.on('tooltipShow', function(e) {
6374                 if (tooltips) showTooltip(e, that.parentNode);
6375             });
6376
6377
6378             // Update chart from a state object passed to event handler
6379             dispatch.on('changeState', function(e) {
6380
6381                 if (typeof e.disabled !== 'undefined') {
6382                     data.forEach(function(series,i) {
6383                         series.disabled = e.disabled[i];
6384                     });
6385
6386                     state.disabled = e.disabled;
6387                 }
6388
6389                 chart.update();
6390             });
6391
6392             //============================================================
6393
6394
6395         });
6396
6397         return chart;
6398     }
6399
6400
6401     //============================================================
6402     // Event Handling/Dispatching (out of chart's scope)
6403     //------------------------------------------------------------
6404
6405     lines.dispatch.on('elementMouseover.tooltip', function(e) {
6406         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6407         dispatch.tooltipShow(e);
6408     });
6409
6410     lines.dispatch.on('elementMouseout.tooltip', function(e) {
6411         dispatch.tooltipHide(e);
6412     });
6413
6414     bars.dispatch.on('elementMouseover.tooltip', function(e) {
6415         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6416         dispatch.tooltipShow(e);
6417     });
6418
6419     bars.dispatch.on('elementMouseout.tooltip', function(e) {
6420         dispatch.tooltipHide(e);
6421     });
6422
6423     dispatch.on('tooltipHide', function() {
6424         if (tooltips) nv.tooltip.cleanup();
6425     });
6426
6427     //============================================================
6428
6429
6430     //============================================================
6431     // Expose Public Variables
6432     //------------------------------------------------------------
6433
6434     // expose chart's sub-components
6435     chart.dispatch = dispatch;
6436     chart.legend = legend;
6437     chart.lines = lines;
6438     chart.bars = bars;
6439     chart.xAxis = xAxis;
6440     chart.y1Axis = y1Axis;
6441     chart.y2Axis = y2Axis;
6442
6443     // DO NOT DELETE. This is currently overridden below
6444     // until deprecated portions are removed.
6445     chart.state = state;
6446
6447     d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6448     //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6449     //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6450
6451     chart.options = nv.utils.optionsFunc.bind(chart);
6452
6453     chart.x = function(_) {
6454         if (!arguments.length) return getX;
6455         getX = _;
6456         lines.x(_);
6457         bars.x(_);
6458         return chart;
6459     };
6460
6461     chart.y = function(_) {
6462         if (!arguments.length) return getY;
6463         getY = _;
6464         lines.y(_);
6465         bars.y(_);
6466         return chart;
6467     };
6468
6469     chart.margin = function(_) {
6470         if (!arguments.length) return margin;
6471         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
6472         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
6473         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6474         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
6475         return chart;
6476     };
6477
6478     chart.width = function(_) {
6479         if (!arguments.length) return width;
6480         width = _;
6481         return chart;
6482     };
6483
6484     chart.height = function(_) {
6485         if (!arguments.length) return height;
6486         height = _;
6487         return chart;
6488     };
6489
6490     chart.color = function(_) {
6491         if (!arguments.length) return color;
6492         color = nv.utils.getColor(_);
6493         legend.color(color);
6494         return chart;
6495     };
6496
6497     chart.showLegend = function(_) {
6498         if (!arguments.length) return showLegend;
6499         showLegend = _;
6500         return chart;
6501     };
6502
6503     chart.tooltips = function(_) {
6504         if (!arguments.length) return tooltips;
6505         tooltips = _;
6506         return chart;
6507     };
6508
6509     chart.tooltipContent = function(_) {
6510         if (!arguments.length) return tooltip;
6511         tooltip = _;
6512         return chart;
6513     };
6514
6515     // DEPRECATED
6516     chart.state = function(_) {
6517         nv.deprecated('linePlusBarChart.state');
6518         if (!arguments.length) return state;
6519         state = _;
6520         return chart;
6521     };
6522     for (var key in state) {
6523         chart.state[key] = state[key];
6524     }
6525     // END DEPRECATED
6526
6527     chart.defaultState = function(_) {
6528         if (!arguments.length) return defaultState;
6529         defaultState = _;
6530         return chart;
6531     };
6532
6533     chart.noData = function(_) {
6534         if (!arguments.length) return noData;
6535         noData = _;
6536         return chart;
6537     };
6538
6539     //============================================================
6540
6541     return chart;
6542 };
6543 nv.models.linePlusBarWithFocusChart = function() {
6544     "use strict";
6545     //============================================================
6546     // Public Variables with Default Settings
6547     //------------------------------------------------------------
6548
6549     var lines = nv.models.line()
6550         , lines2 = nv.models.line()
6551         , bars = nv.models.historicalBar()
6552         , bars2 = nv.models.historicalBar()
6553         , xAxis = nv.models.axis()
6554         , x2Axis = nv.models.axis()
6555         , y1Axis = nv.models.axis()
6556         , y2Axis = nv.models.axis()
6557         , y3Axis = nv.models.axis()
6558         , y4Axis = nv.models.axis()
6559         , legend = nv.models.legend()
6560         , brush = d3.svg.brush()
6561         ;
6562
6563     var margin = {top: 30, right: 30, bottom: 30, left: 60}
6564         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6565         , width = null
6566         , height = null
6567         , height2 = 100
6568         , getX = function(d) { return d.x }
6569         , getY = function(d) { return d.y }
6570         , color = nv.utils.defaultColor()
6571         , showLegend = true
6572         , extent
6573         , brushExtent = null
6574         , tooltips = true
6575         , tooltip = function(key, x, y, e, graph) {
6576             return '<h3>' + key + '</h3>' +
6577                 '<p>' +  y + ' at ' + x + '</p>';
6578         }
6579         , x
6580         , x2
6581         , y1
6582         , y2
6583         , y3
6584         , y4
6585         , noData = "No Data Available."
6586         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
6587         , transitionDuration = 0
6588         , state = nv.utils.state()
6589         , defaultState = null
6590         ;
6591
6592     lines
6593         .clipEdge(true)
6594     ;
6595     lines2
6596         .interactive(false)
6597     ;
6598     xAxis
6599         .orient('bottom')
6600         .tickPadding(5)
6601     ;
6602     y1Axis
6603         .orient('left')
6604     ;
6605     y2Axis
6606         .orient('right')
6607     ;
6608     x2Axis
6609         .orient('bottom')
6610         .tickPadding(5)
6611     ;
6612     y3Axis
6613         .orient('left')
6614     ;
6615     y4Axis
6616         .orient('right')
6617     ;
6618
6619     //============================================================
6620
6621
6622     //============================================================
6623     // Private Variables
6624     //------------------------------------------------------------
6625
6626     var showTooltip = function(e, offsetElement) {
6627         if (extent) {
6628             e.pointIndex += Math.ceil(extent[0]);
6629         }
6630         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6631             top = e.pos[1] + ( offsetElement.offsetTop || 0),
6632             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6633             y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6634             content = tooltip(e.series.key, x, y, e, chart);
6635
6636         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6637     };
6638
6639     var stateGetter = function(data) {
6640         return function(){
6641             return {
6642                 active: data.map(function(d) { return !d.disabled })
6643             };
6644         }
6645     };
6646
6647     var stateSetter = function(data) {
6648         return function(state) {
6649             if (state.active !== undefined)
6650                 data.forEach(function(series,i) {
6651                     series.disabled = !state.active[i];
6652                 });
6653         }
6654     };
6655
6656     //------------------------------------------------------------
6657
6658
6659
6660     function chart(selection) {
6661         selection.each(function(data) {
6662             var container = d3.select(this),
6663                 that = this;
6664             nv.utils.initSVG(container);
6665             var availableWidth = (width  || parseInt(container.style('width')) || 960)
6666                     - margin.left - margin.right,
6667                 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6668                     - margin.top - margin.bottom - height2,
6669                 availableHeight2 = height2 - margin2.top - margin2.bottom;
6670
6671             chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6672             chart.container = this;
6673
6674             state
6675                 .setter(stateSetter(data), chart.update)
6676                 .getter(stateGetter(data))
6677                 .update();
6678
6679             // DEPRECATED set state.disableddisabled
6680             state.disabled = data.map(function(d) { return !!d.disabled });
6681
6682             if (!defaultState) {
6683                 var key;
6684                 defaultState = {};
6685                 for (key in state) {
6686                     if (state[key] instanceof Array)
6687                         defaultState[key] = state[key].slice(0);
6688                     else
6689                         defaultState[key] = state[key];
6690                 }
6691             }
6692
6693             //------------------------------------------------------------
6694             // Display No Data message if there's nothing to show.
6695
6696             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6697                 var noDataText = container.selectAll('.nv-noData').data([noData]);
6698
6699                 noDataText.enter().append('text')
6700                     .attr('class', 'nvd3 nv-noData')
6701                     .attr('dy', '-.7em')
6702                     .style('text-anchor', 'middle');
6703
6704                 noDataText
6705                     .attr('x', margin.left + availableWidth / 2)
6706                     .attr('y', margin.top + availableHeight1 / 2)
6707                     .text(function(d) { return d });
6708
6709                 return chart;
6710             } else {
6711                 container.selectAll('.nv-noData').remove();
6712             }
6713
6714             //------------------------------------------------------------
6715
6716
6717             //------------------------------------------------------------
6718             // Setup Scales
6719
6720             var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6721             var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6722
6723             x = bars.xScale();
6724             x2 = x2Axis.scale();
6725             y1 = bars.yScale();
6726             y2 = lines.yScale();
6727             y3 = bars2.yScale();
6728             y4 = lines2.yScale();
6729
6730             var series1 = data
6731                 .filter(function(d) { return !d.disabled && d.bar })
6732                 .map(function(d) {
6733                     return d.values.map(function(d,i) {
6734                         return { x: getX(d,i), y: getY(d,i) }
6735                     })
6736                 });
6737
6738             var series2 = data
6739                 .filter(function(d) { return !d.disabled && !d.bar })
6740                 .map(function(d) {
6741                     return d.values.map(function(d,i) {
6742                         return { x: getX(d,i), y: getY(d,i) }
6743                     })
6744                 });
6745
6746             x   .range([0, availableWidth]);
6747
6748             x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6749                 .range([0, availableWidth]);
6750
6751
6752             //------------------------------------------------------------
6753
6754
6755             //------------------------------------------------------------
6756             // Setup containers and skeleton of chart
6757
6758             var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6759             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6760             var g = wrap.select('g');
6761
6762             gEnter.append('g').attr('class', 'nv-legendWrap');
6763
6764             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6765             focusEnter.append('g').attr('class', 'nv-x nv-axis');
6766             focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6767             focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6768             focusEnter.append('g').attr('class', 'nv-barsWrap');
6769             focusEnter.append('g').attr('class', 'nv-linesWrap');
6770
6771             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6772             contextEnter.append('g').attr('class', 'nv-x nv-axis');
6773             contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6774             contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6775             contextEnter.append('g').attr('class', 'nv-barsWrap');
6776             contextEnter.append('g').attr('class', 'nv-linesWrap');
6777             contextEnter.append('g').attr('class', 'nv-brushBackground');
6778             contextEnter.append('g').attr('class', 'nv-x nv-brush');
6779
6780
6781             //------------------------------------------------------------
6782
6783
6784             //------------------------------------------------------------
6785             // Legend
6786
6787             if (showLegend) {
6788                 legend.width( availableWidth / 2 );
6789
6790                 g.select('.nv-legendWrap')
6791                     .datum(data.map(function(series) {
6792                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6793                         series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6794                         return series;
6795                     }))
6796                     .call(legend);
6797
6798                 if ( margin.top != legend.height()) {
6799                     margin.top = legend.height();
6800                     availableHeight1 = (height || parseInt(container.style('height')) || 400)
6801                         - margin.top - margin.bottom - height2;
6802                 }
6803
6804                 g.select('.nv-legendWrap')
6805                     .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6806             }
6807
6808             //------------------------------------------------------------
6809
6810
6811             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6812
6813
6814             //------------------------------------------------------------
6815             // Context Components
6816
6817             bars2
6818                 .width(availableWidth)
6819                 .height(availableHeight2)
6820                 .color(data.map(function(d,i) {
6821                     return d.color || color(d, i);
6822                 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6823
6824             lines2
6825                 .width(availableWidth)
6826                 .height(availableHeight2)
6827                 .color(data.map(function(d,i) {
6828                     return d.color || color(d, i);
6829                 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6830
6831             var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6832                 .datum(dataBars.length ? dataBars : [{values:[]}]);
6833
6834             var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6835                 .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
6836
6837             g.select('.nv-context')
6838                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6839
6840             bars2Wrap.transition().call(bars2);
6841             lines2Wrap.transition().call(lines2);
6842
6843             //------------------------------------------------------------
6844
6845
6846
6847             //------------------------------------------------------------
6848             // Setup Brush
6849
6850             brush
6851                 .x(x2)
6852                 .on('brush', onBrush);
6853
6854             if (brushExtent) brush.extent(brushExtent);
6855
6856             var brushBG = g.select('.nv-brushBackground').selectAll('g')
6857                 .data([brushExtent || brush.extent()]);
6858
6859             var brushBGenter = brushBG.enter()
6860                 .append('g');
6861
6862             brushBGenter.append('rect')
6863                 .attr('class', 'left')
6864                 .attr('x', 0)
6865                 .attr('y', 0)
6866                 .attr('height', availableHeight2);
6867
6868             brushBGenter.append('rect')
6869                 .attr('class', 'right')
6870                 .attr('x', 0)
6871                 .attr('y', 0)
6872                 .attr('height', availableHeight2);
6873
6874             var gBrush = g.select('.nv-x.nv-brush')
6875                 .call(brush);
6876             gBrush.selectAll('rect')
6877                 //.attr('y', -5)
6878                 .attr('height', availableHeight2);
6879             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6880
6881             //------------------------------------------------------------
6882
6883             //------------------------------------------------------------
6884             // Setup Secondary (Context) Axes
6885
6886             x2Axis
6887                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6888                 .tickSize(-availableHeight2, 0);
6889
6890             g.select('.nv-context .nv-x.nv-axis')
6891                 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
6892             g.select('.nv-context .nv-x.nv-axis').transition()
6893                 .call(x2Axis);
6894
6895
6896             y3Axis
6897                 .scale(y3)
6898                 .ticks( availableHeight2 / 36 )
6899                 .tickSize( -availableWidth, 0);
6900
6901             g.select('.nv-context .nv-y1.nv-axis')
6902                 .style('opacity', dataBars.length ? 1 : 0)
6903                 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6904
6905             g.select('.nv-context .nv-y1.nv-axis').transition()
6906                 .call(y3Axis);
6907
6908
6909             y4Axis
6910                 .scale(y4)
6911                 .ticks( availableHeight2 / 36 )
6912                 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6913
6914             g.select('.nv-context .nv-y2.nv-axis')
6915                 .style('opacity', dataLines.length ? 1 : 0)
6916                 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6917
6918             g.select('.nv-context .nv-y2.nv-axis').transition()
6919                 .call(y4Axis);
6920
6921             //------------------------------------------------------------
6922
6923             //============================================================
6924             // Event Handling/Dispatching (in chart's scope)
6925             //------------------------------------------------------------
6926
6927             legend.dispatch.on('stateChange', function(newState) {
6928                 for (var key in newState)
6929                     state[key] = newState[key];
6930                 dispatch.stateChange(state);
6931                 chart.update();
6932             });
6933
6934             dispatch.on('tooltipShow', function(e) {
6935                 if (tooltips) showTooltip(e, that.parentNode);
6936             });
6937
6938             // Update chart from a state object passed to event handler
6939             dispatch.on('changeState', function(e) {
6940                 if (typeof e.disabled !== 'undefined') {
6941                     data.forEach(function(series,i) {
6942                         series.disabled = e.disabled[i];
6943                     });
6944                     state.disabled = e.disabled;
6945                 }
6946                 chart.update();
6947             });
6948
6949             //============================================================
6950
6951
6952             //============================================================
6953             // Functions
6954             //------------------------------------------------------------
6955
6956             // Taken from crossfilter (http://square.github.com/crossfilter/)
6957             function resizePath(d) {
6958                 var e = +(d == 'e'),
6959                     x = e ? 1 : -1,
6960                     y = availableHeight2 / 3;
6961                 return 'M' + (.5 * x) + ',' + y
6962                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6963                     + 'V' + (2 * y - 6)
6964                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6965                     + 'Z'
6966                     + 'M' + (2.5 * x) + ',' + (y + 8)
6967                     + 'V' + (2 * y - 8)
6968                     + 'M' + (4.5 * x) + ',' + (y + 8)
6969                     + 'V' + (2 * y - 8);
6970             }
6971
6972
6973             function updateBrushBG() {
6974                 if (!brush.empty()) brush.extent(brushExtent);
6975                 brushBG
6976                     .data([brush.empty() ? x2.domain() : brushExtent])
6977                     .each(function(d,i) {
6978                         var leftWidth = x2(d[0]) - x2.range()[0],
6979                             rightWidth = x2.range()[1] - x2(d[1]);
6980                         d3.select(this).select('.left')
6981                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
6982
6983                         d3.select(this).select('.right')
6984                             .attr('x', x2(d[1]))
6985                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
6986                     });
6987             }
6988
6989
6990             function onBrush() {
6991                 brushExtent = brush.empty() ? null : brush.extent();
6992                 extent = brush.empty() ? x2.domain() : brush.extent();
6993
6994
6995                 dispatch.brush({extent: extent, brush: brush});
6996
6997                 updateBrushBG();
6998
6999
7000                 //------------------------------------------------------------
7001                 // Prepare Main (Focus) Bars and Lines
7002
7003                 bars
7004                     .width(availableWidth)
7005                     .height(availableHeight1)
7006                     .color(data.map(function(d,i) {
7007                         return d.color || color(d, i);
7008                     }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7009
7010
7011                 lines
7012                     .width(availableWidth)
7013                     .height(availableHeight1)
7014                     .color(data.map(function(d,i) {
7015                         return d.color || color(d, i);
7016                     }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7017
7018                 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7019                     .datum(!dataBars.length ? [{values:[]}] :
7020                         dataBars
7021                             .map(function(d,i) {
7022                                 return {
7023                                     key: d.key,
7024                                     values: d.values.filter(function(d,i) {
7025                                         return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7026                                     })
7027                                 }
7028                             })
7029                 );
7030
7031                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7032                     .datum(dataLines[0].disabled ? [{values:[]}] :
7033                         dataLines
7034                             .map(function(d,i) {
7035                                 return {
7036                                     key: d.key,
7037                                     values: d.values.filter(function(d,i) {
7038                                         return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7039                                     })
7040                                 }
7041                             })
7042                 );
7043
7044                 //------------------------------------------------------------
7045
7046
7047                 //------------------------------------------------------------
7048                 // Update Main (Focus) X Axis
7049
7050                 if (dataBars.length) {
7051                     x = bars.xScale();
7052                 } else {
7053                     x = lines.xScale();
7054                 }
7055
7056                 xAxis
7057                     .scale(x)
7058                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7059                     .tickSize(-availableHeight1, 0);
7060
7061                 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7062
7063                 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7064                     .call(xAxis);
7065                 //------------------------------------------------------------
7066
7067
7068                 //------------------------------------------------------------
7069                 // Update Main (Focus) Bars and Lines
7070
7071                 focusBarsWrap.transition().duration(transitionDuration).call(bars);
7072                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7073
7074                 //------------------------------------------------------------
7075
7076
7077                 //------------------------------------------------------------
7078                 // Setup and Update Main (Focus) Y Axes
7079
7080                 g.select('.nv-focus .nv-x.nv-axis')
7081                     .attr('transform', 'translate(0,' + y1.range()[0] + ')');
7082
7083
7084                 y1Axis
7085                     .scale(y1)
7086                     .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7087                     .tickSize(-availableWidth, 0);
7088
7089                 g.select('.nv-focus .nv-y1.nv-axis')
7090                     .style('opacity', dataBars.length ? 1 : 0);
7091
7092
7093                 y2Axis
7094                     .scale(y2)
7095                     .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7096                     .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7097
7098                 g.select('.nv-focus .nv-y2.nv-axis')
7099                     .style('opacity', dataLines.length ? 1 : 0)
7100                     .attr('transform', 'translate(' + x.range()[1] + ',0)');
7101
7102                 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7103                     .call(y1Axis);
7104                 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7105                     .call(y2Axis);
7106             }
7107
7108             //============================================================
7109
7110             onBrush();
7111
7112         });
7113
7114         return chart;
7115     }
7116
7117
7118     //============================================================
7119     // Event Handling/Dispatching (out of chart's scope)
7120     //------------------------------------------------------------
7121
7122     lines.dispatch.on('elementMouseover.tooltip', function(e) {
7123         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7124         dispatch.tooltipShow(e);
7125     });
7126
7127     lines.dispatch.on('elementMouseout.tooltip', function(e) {
7128         dispatch.tooltipHide(e);
7129     });
7130
7131     bars.dispatch.on('elementMouseover.tooltip', function(e) {
7132         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7133         dispatch.tooltipShow(e);
7134     });
7135
7136     bars.dispatch.on('elementMouseout.tooltip', function(e) {
7137         dispatch.tooltipHide(e);
7138     });
7139
7140     dispatch.on('tooltipHide', function() {
7141         if (tooltips) nv.tooltip.cleanup();
7142     });
7143
7144     //============================================================
7145
7146
7147     //============================================================
7148     // Expose Public Variables
7149     //------------------------------------------------------------
7150
7151     // expose chart's sub-components
7152     chart.dispatch = dispatch;
7153     chart.legend = legend;
7154     chart.lines = lines;
7155     chart.lines2 = lines2;
7156     chart.bars = bars;
7157     chart.bars2 = bars2;
7158     chart.xAxis = xAxis;
7159     chart.x2Axis = x2Axis;
7160     chart.y1Axis = y1Axis;
7161     chart.y2Axis = y2Axis;
7162     chart.y3Axis = y3Axis;
7163     chart.y4Axis = y4Axis;
7164
7165     // DO NOT DELETE. This is currently overridden below
7166     // until deprecated portions are removed.
7167     chart.state = state;
7168
7169     d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7170     //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7171     //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7172
7173     chart.options = nv.utils.optionsFunc.bind(chart);
7174
7175     chart.x = function(_) {
7176         if (!arguments.length) return getX;
7177         getX = _;
7178         lines.x(_);
7179         bars.x(_);
7180         return chart;
7181     };
7182
7183     chart.y = function(_) {
7184         if (!arguments.length) return getY;
7185         getY = _;
7186         lines.y(_);
7187         bars.y(_);
7188         return chart;
7189     };
7190
7191     chart.margin = function(_) {
7192         if (!arguments.length) return margin;
7193         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7194         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7195         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7196         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7197         return chart;
7198     };
7199
7200     chart.width = function(_) {
7201         if (!arguments.length) return width;
7202         width = _;
7203         return chart;
7204     };
7205
7206     chart.height = function(_) {
7207         if (!arguments.length) return height;
7208         height = _;
7209         return chart;
7210     };
7211
7212     chart.color = function(_) {
7213         if (!arguments.length) return color;
7214         color = nv.utils.getColor(_);
7215         legend.color(color);
7216         return chart;
7217     };
7218
7219     chart.showLegend = function(_) {
7220         if (!arguments.length) return showLegend;
7221         showLegend = _;
7222         return chart;
7223     };
7224
7225     chart.tooltips = function(_) {
7226         if (!arguments.length) return tooltips;
7227         tooltips = _;
7228         return chart;
7229     };
7230
7231     chart.tooltipContent = function(_) {
7232         if (!arguments.length) return tooltip;
7233         tooltip = _;
7234         return chart;
7235     };
7236
7237     // DEPRECATED
7238     chart.state = function(_) {
7239         nv.deprecated('linePlusBarWithFocusChart.state');
7240         if (!arguments.length) return state;
7241         state = _;
7242         return chart;
7243     };
7244     for (var key in state) {
7245         chart.state[key] = state[key];
7246     }
7247     // END DEPRECATED
7248
7249     chart.noData = function(_) {
7250         if (!arguments.length) return noData;
7251         noData = _;
7252         return chart;
7253     };
7254
7255     chart.brushExtent = function(_) {
7256         if (!arguments.length) return brushExtent;
7257         brushExtent = _;
7258         return chart;
7259     };
7260
7261
7262     //============================================================
7263
7264
7265     return chart;
7266 };
7267 nv.models.lineWithFocusChart = function() {
7268     "use strict";
7269     //============================================================
7270     // Public Variables with Default Settings
7271     //------------------------------------------------------------
7272
7273     var lines = nv.models.line()
7274         , lines2 = nv.models.line()
7275         , xAxis = nv.models.axis()
7276         , yAxis = nv.models.axis()
7277         , x2Axis = nv.models.axis()
7278         , y2Axis = nv.models.axis()
7279         , legend = nv.models.legend()
7280         , brush = d3.svg.brush()
7281         ;
7282
7283     var margin = {top: 30, right: 30, bottom: 30, left: 60}
7284         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
7285         , color = nv.utils.defaultColor()
7286         , width = null
7287         , height = null
7288         , height2 = 100
7289         , x
7290         , y
7291         , x2
7292         , y2
7293         , showLegend = true
7294         , brushExtent = null
7295         , tooltips = true
7296         , tooltip = function(key, x, y, e, graph) {
7297             return '<h3>' + key + '</h3>' +
7298                 '<p>' +  y + ' at ' + x + '</p>'
7299         }
7300         , noData = "No Data Available."
7301         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
7302         , transitionDuration = 250
7303         , state = nv.utils.state()
7304         , defaultState = null
7305         ;
7306
7307     lines
7308         .clipEdge(true)
7309     ;
7310     lines2
7311         .interactive(false)
7312     ;
7313     xAxis
7314         .orient('bottom')
7315         .tickPadding(5)
7316     ;
7317     yAxis
7318         .orient('left')
7319     ;
7320     x2Axis
7321         .orient('bottom')
7322         .tickPadding(5)
7323     ;
7324     y2Axis
7325         .orient('left')
7326     ;
7327     //============================================================
7328
7329
7330     //============================================================
7331     // Private Variables
7332     //------------------------------------------------------------
7333
7334     var showTooltip = function(e, offsetElement) {
7335         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7336             top = e.pos[1] + ( offsetElement.offsetTop || 0),
7337             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
7338             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
7339             content = tooltip(e.series.key, x, y, e, chart);
7340
7341         nv.tooltip.show([left, top], content, null, null, offsetElement);
7342     };
7343
7344     var stateGetter = function(data) {
7345         return function(){
7346             return {
7347                 active: data.map(function(d) { return !d.disabled })
7348             };
7349         }
7350     };
7351
7352     var stateSetter = function(data) {
7353         return function(state) {
7354             if (state.active !== undefined)
7355                 data.forEach(function(series,i) {
7356                     series.disabled = !state.active[i];
7357                 });
7358         }
7359     };
7360
7361     //============================================================
7362
7363
7364     function chart(selection) {
7365         selection.each(function(data) {
7366             var container = d3.select(this),
7367                 that = this;
7368             nv.utils.initSVG(container);
7369             var availableWidth = (width  || parseInt(container.style('width')) || 960)
7370                     - margin.left - margin.right,
7371                 availableHeight1 = (height || parseInt(container.style('height')) || 400)
7372                     - margin.top - margin.bottom - height2,
7373                 availableHeight2 = height2 - margin2.top - margin2.bottom;
7374
7375             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7376             chart.container = this;
7377
7378             state
7379                 .setter(stateSetter(data), chart.update)
7380                 .getter(stateGetter(data))
7381                 .update();
7382
7383             // DEPRECATED set state.disableddisabled
7384             state.disabled = data.map(function(d) { return !!d.disabled });
7385
7386             if (!defaultState) {
7387                 var key;
7388                 defaultState = {};
7389                 for (key in state) {
7390                     if (state[key] instanceof Array)
7391                         defaultState[key] = state[key].slice(0);
7392                     else
7393                         defaultState[key] = state[key];
7394                 }
7395             }
7396
7397             //------------------------------------------------------------
7398             // Display No Data message if there's nothing to show.
7399
7400             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7401                 var noDataText = container.selectAll('.nv-noData').data([noData]);
7402
7403                 noDataText.enter().append('text')
7404                     .attr('class', 'nvd3 nv-noData')
7405                     .attr('dy', '-.7em')
7406                     .style('text-anchor', 'middle');
7407
7408                 noDataText
7409                     .attr('x', margin.left + availableWidth / 2)
7410                     .attr('y', margin.top + availableHeight1 / 2)
7411                     .text(function(d) { return d });
7412
7413                 return chart;
7414             } else {
7415                 container.selectAll('.nv-noData').remove();
7416             }
7417
7418             //------------------------------------------------------------
7419
7420
7421             //------------------------------------------------------------
7422             // Setup Scales
7423
7424             x = lines.xScale();
7425             y = lines.yScale();
7426             x2 = lines2.xScale();
7427             y2 = lines2.yScale();
7428
7429             //------------------------------------------------------------
7430
7431
7432             //------------------------------------------------------------
7433             // Setup containers and skeleton of chart
7434
7435             var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
7436             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
7437             var g = wrap.select('g');
7438
7439             gEnter.append('g').attr('class', 'nv-legendWrap');
7440
7441             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
7442             focusEnter.append('g').attr('class', 'nv-x nv-axis');
7443             focusEnter.append('g').attr('class', 'nv-y nv-axis');
7444             focusEnter.append('g').attr('class', 'nv-linesWrap');
7445
7446             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
7447             contextEnter.append('g').attr('class', 'nv-x nv-axis');
7448             contextEnter.append('g').attr('class', 'nv-y nv-axis');
7449             contextEnter.append('g').attr('class', 'nv-linesWrap');
7450             contextEnter.append('g').attr('class', 'nv-brushBackground');
7451             contextEnter.append('g').attr('class', 'nv-x nv-brush');
7452
7453             //------------------------------------------------------------
7454
7455
7456             //------------------------------------------------------------
7457             // Legend
7458
7459             if (showLegend) {
7460                 legend.width(availableWidth);
7461
7462                 g.select('.nv-legendWrap')
7463                     .datum(data)
7464                     .call(legend);
7465
7466                 if ( margin.top != legend.height()) {
7467                     margin.top = legend.height();
7468                     availableHeight1 = (height || parseInt(container.style('height')) || 400)
7469                         - margin.top - margin.bottom - height2;
7470                 }
7471
7472                 g.select('.nv-legendWrap')
7473                     .attr('transform', 'translate(0,' + (-margin.top) +')')
7474             }
7475
7476             //------------------------------------------------------------
7477
7478
7479             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7480
7481
7482             //------------------------------------------------------------
7483             // Main Chart Component(s)
7484
7485             lines
7486                 .width(availableWidth)
7487                 .height(availableHeight1)
7488                 .color(
7489                 data
7490                     .map(function(d,i) {
7491                         return d.color || color(d, i);
7492                     })
7493                     .filter(function(d,i) {
7494                         return !data[i].disabled;
7495                     })
7496             );
7497
7498             lines2
7499                 .defined(lines.defined())
7500                 .width(availableWidth)
7501                 .height(availableHeight2)
7502                 .color(
7503                 data
7504                     .map(function(d,i) {
7505                         return d.color || color(d, i);
7506                     })
7507                     .filter(function(d,i) {
7508                         return !data[i].disabled;
7509                     })
7510             );
7511
7512             g.select('.nv-context')
7513                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7514
7515             var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
7516                 .datum(data.filter(function(d) { return !d.disabled }))
7517
7518             d3.transition(contextLinesWrap).call(lines2);
7519
7520             //------------------------------------------------------------
7521
7522
7523             /*
7524              var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7525              .datum(data.filter(function(d) { return !d.disabled }))
7526
7527              d3.transition(focusLinesWrap).call(lines);
7528              */
7529
7530
7531             //------------------------------------------------------------
7532             // Setup Main (Focus) Axes
7533
7534             xAxis
7535                 .scale(x)
7536                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7537                 .tickSize(-availableHeight1, 0);
7538
7539             yAxis
7540                 .scale(y)
7541                 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7542                 .tickSize( -availableWidth, 0);
7543
7544             g.select('.nv-focus .nv-x.nv-axis')
7545                 .attr('transform', 'translate(0,' + availableHeight1 + ')');
7546
7547             //------------------------------------------------------------
7548
7549
7550             //------------------------------------------------------------
7551             // Setup Brush
7552
7553             brush
7554                 .x(x2)
7555                 .on('brush', function() {
7556                     //When brushing, turn off transitions because chart needs to change immediately.
7557                     var oldTransition = chart.transitionDuration();
7558                     chart.transitionDuration(0);
7559                     onBrush();
7560                     chart.transitionDuration(oldTransition);
7561                 });
7562
7563             if (brushExtent) brush.extent(brushExtent);
7564
7565             var brushBG = g.select('.nv-brushBackground').selectAll('g')
7566                 .data([brushExtent || brush.extent()])
7567
7568             var brushBGenter = brushBG.enter()
7569                 .append('g');
7570
7571             brushBGenter.append('rect')
7572                 .attr('class', 'left')
7573                 .attr('x', 0)
7574                 .attr('y', 0)
7575                 .attr('height', availableHeight2);
7576
7577             brushBGenter.append('rect')
7578                 .attr('class', 'right')
7579                 .attr('x', 0)
7580                 .attr('y', 0)
7581                 .attr('height', availableHeight2);
7582
7583             var gBrush = g.select('.nv-x.nv-brush')
7584                 .call(brush);
7585             gBrush.selectAll('rect')
7586                 //.attr('y', -5)
7587                 .attr('height', availableHeight2);
7588             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7589
7590             onBrush();
7591
7592             //------------------------------------------------------------
7593
7594
7595             //------------------------------------------------------------
7596             // Setup Secondary (Context) Axes
7597
7598             x2Axis
7599                 .scale(x2)
7600                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7601                 .tickSize(-availableHeight2, 0);
7602
7603             g.select('.nv-context .nv-x.nv-axis')
7604                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7605             d3.transition(g.select('.nv-context .nv-x.nv-axis'))
7606                 .call(x2Axis);
7607
7608             y2Axis
7609                 .scale(y2)
7610                 .ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7611                 .tickSize( -availableWidth, 0);
7612
7613             d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7614                 .call(y2Axis);
7615
7616             g.select('.nv-context .nv-x.nv-axis')
7617                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7618
7619             //------------------------------------------------------------
7620
7621
7622             //============================================================
7623             // Event Handling/Dispatching (in chart's scope)
7624             //------------------------------------------------------------
7625
7626             legend.dispatch.on('stateChange', function(newState) {
7627                 for (var key in newState)
7628                     state[key] = newState[key];
7629                 dispatch.stateChange(state);
7630                 chart.update();
7631             });
7632
7633             dispatch.on('tooltipShow', function(e) {
7634                 if (tooltips) showTooltip(e, that.parentNode);
7635             });
7636
7637             dispatch.on('changeState', function(e) {
7638                 if (typeof e.disabled !== 'undefined') {
7639                     data.forEach(function(series,i) {
7640                         series.disabled = e.disabled[i];
7641                     });
7642                 }
7643                 chart.update();
7644             });
7645
7646             //============================================================
7647
7648
7649             //============================================================
7650             // Functions
7651             //------------------------------------------------------------
7652
7653             // Taken from crossfilter (http://square.github.com/crossfilter/)
7654             function resizePath(d) {
7655                 var e = +(d == 'e'),
7656                     x = e ? 1 : -1,
7657                     y = availableHeight2 / 3;
7658                 return 'M' + (.5 * x) + ',' + y
7659                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7660                     + 'V' + (2 * y - 6)
7661                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7662                     + 'Z'
7663                     + 'M' + (2.5 * x) + ',' + (y + 8)
7664                     + 'V' + (2 * y - 8)
7665                     + 'M' + (4.5 * x) + ',' + (y + 8)
7666                     + 'V' + (2 * y - 8);
7667             }
7668
7669
7670             function updateBrushBG() {
7671                 if (!brush.empty()) brush.extent(brushExtent);
7672                 brushBG
7673                     .data([brush.empty() ? x2.domain() : brushExtent])
7674                     .each(function(d,i) {
7675                         var leftWidth = x2(d[0]) - x.range()[0],
7676                             rightWidth = x.range()[1] - x2(d[1]);
7677                         d3.select(this).select('.left')
7678                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
7679
7680                         d3.select(this).select('.right')
7681                             .attr('x', x2(d[1]))
7682                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
7683                     });
7684             }
7685
7686
7687             function onBrush() {
7688                 brushExtent = brush.empty() ? null : brush.extent();
7689                 var extent = brush.empty() ? x2.domain() : brush.extent();
7690
7691                 //The brush extent cannot be less than one.  If it is, don't update the line chart.
7692                 if (Math.abs(extent[0] - extent[1]) <= 1) {
7693                     return;
7694                 }
7695
7696                 dispatch.brush({extent: extent, brush: brush});
7697
7698
7699                 updateBrushBG();
7700
7701                 // Update Main (Focus)
7702                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7703                     .datum(
7704                     data
7705                         .filter(function(d) { return !d.disabled })
7706                         .map(function(d,i) {
7707                             return {
7708                                 key: d.key,
7709                                 area: d.area,
7710                                 values: d.values.filter(function(d,i) {
7711                                     return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7712                                 })
7713                             }
7714                         })
7715                 );
7716                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7717
7718
7719                 // Update Main (Focus) Axes
7720                 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
7721                     .call(xAxis);
7722                 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
7723                     .call(yAxis);
7724             }
7725
7726             //============================================================
7727
7728
7729         });
7730
7731         return chart;
7732     }
7733
7734
7735     //============================================================
7736     // Event Handling/Dispatching (out of chart's scope)
7737     //------------------------------------------------------------
7738
7739     lines.dispatch.on('elementMouseover.tooltip', function(e) {
7740         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7741         dispatch.tooltipShow(e);
7742     });
7743
7744     lines.dispatch.on('elementMouseout.tooltip', function(e) {
7745         dispatch.tooltipHide(e);
7746     });
7747
7748     dispatch.on('tooltipHide', function() {
7749         if (tooltips) nv.tooltip.cleanup();
7750     });
7751
7752     //============================================================
7753
7754
7755     //============================================================
7756     // Expose Public Variables
7757     //------------------------------------------------------------
7758
7759     // expose chart's sub-components
7760     chart.dispatch = dispatch;
7761     chart.legend = legend;
7762     chart.lines = lines;
7763     chart.lines2 = lines2;
7764     chart.xAxis = xAxis;
7765     chart.yAxis = yAxis;
7766     chart.x2Axis = x2Axis;
7767     chart.y2Axis = y2Axis;
7768
7769     // DO NOT DELETE. This is currently overridden below
7770     // until deprecated portions are removed.
7771     chart.state = state;
7772
7773     d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7774
7775     chart.options = nv.utils.optionsFunc.bind(chart);
7776
7777     chart.x = function(_) {
7778         if (!arguments.length) return lines.x;
7779         lines.x(_);
7780         lines2.x(_);
7781         return chart;
7782     };
7783
7784     chart.y = function(_) {
7785         if (!arguments.length) return lines.y;
7786         lines.y(_);
7787         lines2.y(_);
7788         return chart;
7789     };
7790
7791     chart.margin = function(_) {
7792         if (!arguments.length) return margin;
7793         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7794         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7795         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7796         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7797         return chart;
7798     };
7799
7800     chart.margin2 = function(_) {
7801         if (!arguments.length) return margin2;
7802         margin2 = _;
7803         return chart;
7804     };
7805
7806     chart.width = function(_) {
7807         if (!arguments.length) return width;
7808         width = _;
7809         return chart;
7810     };
7811
7812     chart.height = function(_) {
7813         if (!arguments.length) return height;
7814         height = _;
7815         return chart;
7816     };
7817
7818     chart.height2 = function(_) {
7819         if (!arguments.length) return height2;
7820         height2 = _;
7821         return chart;
7822     };
7823
7824     chart.color = function(_) {
7825         if (!arguments.length) return color;
7826         color =nv.utils.getColor(_);
7827         legend.color(color);
7828         return chart;
7829     };
7830
7831     chart.showLegend = function(_) {
7832         if (!arguments.length) return showLegend;
7833         showLegend = _;
7834         return chart;
7835     };
7836
7837     chart.tooltips = function(_) {
7838         if (!arguments.length) return tooltips;
7839         tooltips = _;
7840         return chart;
7841     };
7842
7843     chart.tooltipContent = function(_) {
7844         if (!arguments.length) return tooltip;
7845         tooltip = _;
7846         return chart;
7847     };
7848
7849     // DEPRECATED
7850     chart.state = function(_) {
7851         nv.deprecated('lineWithFocusChart.state');
7852         if (!arguments.length) return state;
7853         state = _;
7854         return chart;
7855     };
7856     for (var key in state) {
7857         chart.state[key] = state[key];
7858     }
7859     // END DEPRECATED
7860
7861     chart.defaultState = function(_) {
7862         if (!arguments.length) return defaultState;
7863         defaultState = _;
7864         return chart;
7865     };
7866
7867     chart.interpolate = function(_) {
7868         if (!arguments.length) return lines.interpolate();
7869         lines.interpolate(_);
7870         lines2.interpolate(_);
7871         return chart;
7872     };
7873
7874     chart.noData = function(_) {
7875         if (!arguments.length) return noData;
7876         noData = _;
7877         return chart;
7878     };
7879
7880     // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
7881     chart.xTickFormat = function(_) {
7882         if (!arguments.length) return xAxis.tickFormat();
7883         xAxis.tickFormat(_);
7884         x2Axis.tickFormat(_);
7885         return chart;
7886     };
7887
7888     chart.yTickFormat = function(_) {
7889         if (!arguments.length) return yAxis.tickFormat();
7890         yAxis.tickFormat(_);
7891         y2Axis.tickFormat(_);
7892         return chart;
7893     };
7894
7895     chart.brushExtent = function(_) {
7896         if (!arguments.length) return brushExtent;
7897         brushExtent = _;
7898         return chart;
7899     };
7900
7901     chart.transitionDuration = function(_) {
7902         if (!arguments.length) return transitionDuration;
7903         transitionDuration = _;
7904         return chart;
7905     };
7906
7907     //============================================================
7908
7909
7910     return chart;
7911 }
7912
7913 nv.models.multiBar = function() {
7914     "use strict";
7915     //============================================================
7916     // Public Variables with Default Settings
7917     //------------------------------------------------------------
7918
7919     var margin = {top: 0, right: 0, bottom: 0, left: 0}
7920         , width = 960
7921         , height = 500
7922         , x = d3.scale.ordinal()
7923         , y = d3.scale.linear()
7924         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7925         , getX = function(d) { return d.x }
7926         , getY = function(d) { return d.y }
7927         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7928         , clipEdge = true
7929         , stacked = false
7930         , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7931         , color = nv.utils.defaultColor()
7932         , hideable = false
7933         , barColor = null // adding the ability to set the color for each rather than the whole group
7934         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7935         , duration = 1000
7936         , xDomain
7937         , yDomain
7938         , xRange
7939         , yRange
7940         , groupSpacing = 0.1
7941         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
7942         ;
7943
7944     //============================================================
7945
7946
7947     //============================================================
7948     // Private Variables
7949     //------------------------------------------------------------
7950
7951     var x0, y0 //used to store previous scales
7952         , renderWatch = nv.utils.renderWatch(dispatch, duration)
7953         ;
7954
7955
7956     //============================================================
7957
7958
7959
7960     function chart(selection) {
7961         renderWatch.reset();
7962         selection.each(function(data) {
7963             var availableWidth = width - margin.left - margin.right,
7964                 availableHeight = height - margin.top - margin.bottom,
7965                 container = d3.select(this);
7966             nv.utils.initSVG(container);
7967
7968             // This function defines the requirements for render complete
7969             var endFn = function(d, i) {
7970                 if (d.series === data.length - 1 && i === data[0].values.length - 1)
7971                     return true;
7972                 return false;
7973             }
7974
7975
7976             if(hideable && data.length) hideable = [{
7977                 values: data[0].values.map(function(d) {
7978                         return {
7979                             x: d.x,
7980                             y: 0,
7981                             series: d.series,
7982                             size: 0.01
7983                         };}
7984                 )}];
7985
7986             if (stacked)
7987                 data = d3.layout.stack()
7988                     .offset(stackOffset)
7989                     .values(function(d){ return d.values })
7990                     .y(getY)
7991                 (!data.length && hideable ? hideable : data);
7992
7993
7994             //add series index to each data point for reference
7995             data.forEach(function(series, i) {
7996                 series.values.forEach(function(point) {
7997                     point.series = i;
7998                 });
7999             });
8000
8001
8002             //------------------------------------------------------------
8003             // HACK for negative value stacking
8004             if (stacked)
8005                 data[0].values.map(function(d,i) {
8006                     var posBase = 0, negBase = 0;
8007                     data.map(function(d) {
8008                         var f = d.values[i]
8009                         f.size = Math.abs(f.y);
8010                         if (f.y<0)  {
8011                             f.y1 = negBase;
8012                             negBase = negBase - f.size;
8013                         } else
8014                         {
8015                             f.y1 = f.size + posBase;
8016                             posBase = posBase + f.size;
8017                         }
8018                     });
8019                 });
8020
8021             //------------------------------------------------------------
8022             // Setup Scales
8023
8024             // remap and flatten the data for use in calculating the scales' domains
8025             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8026                 data.map(function(d) {
8027                     return d.values.map(function(d,i) {
8028                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8029                     })
8030                 });
8031
8032             x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8033                 .rangeBands(xRange || [0, availableWidth], groupSpacing);
8034
8035             //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
8036             y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
8037                 .range(yRange || [availableHeight, 0]);
8038
8039             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
8040             if (x.domain()[0] === x.domain()[1])
8041                 x.domain()[0] ?
8042                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8043                     : x.domain([-1,1]);
8044
8045             if (y.domain()[0] === y.domain()[1])
8046                 y.domain()[0] ?
8047                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8048                     : y.domain([-1,1]);
8049
8050
8051             x0 = x0 || x;
8052             y0 = y0 || y;
8053
8054             //------------------------------------------------------------
8055
8056
8057             //------------------------------------------------------------
8058             // Setup containers and skeleton of chart
8059
8060             var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
8061             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
8062             var defsEnter = wrapEnter.append('defs');
8063             var gEnter = wrapEnter.append('g');
8064             var g = wrap.select('g')
8065
8066             gEnter.append('g').attr('class', 'nv-groups');
8067
8068             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8069
8070             //------------------------------------------------------------
8071
8072
8073
8074             defsEnter.append('clipPath')
8075                 .attr('id', 'nv-edge-clip-' + id)
8076                 .append('rect');
8077             wrap.select('#nv-edge-clip-' + id + ' rect')
8078                 .attr('width', availableWidth)
8079                 .attr('height', availableHeight);
8080
8081             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
8082
8083             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8084                 .data(function(d) { return d }, function(d,i) { return i });
8085             groups.enter().append('g')
8086                 .style('stroke-opacity', 1e-6)
8087                 .style('fill-opacity', 1e-6);
8088
8089             var exitTransition = renderWatch
8090                 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(250, duration))
8091                 .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
8092                 .attr('height', 0)
8093                 .remove();
8094             if (exitTransition.delay)
8095                 exitTransition.delay(function(d,i) {
8096                     return i * duration / data[0].values.length;
8097                 });
8098             groups
8099                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8100                 .classed('hover', function(d) { return d.hover })
8101                 .style('fill', function(d,i){ return color(d, i) })
8102                 .style('stroke', function(d,i){ return color(d, i) });
8103             groups
8104                 .style('stroke-opacity', 1)
8105                 .style('fill-opacity', 0.75);
8106
8107
8108             var bars = groups.selectAll('rect.nv-bar')
8109                 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
8110
8111             bars.exit().remove();
8112
8113
8114             var barsEnter = bars.enter().append('rect')
8115                     .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8116                     .attr('x', function(d,i,j) {
8117                         return stacked ? 0 : (j * x.rangeBand() / data.length )
8118                     })
8119                     .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
8120                     .attr('height', 0)
8121                     .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
8122                     .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
8123                 ;
8124             bars
8125                 .style('fill', function(d,i,j){ return color(d, j, i);  })
8126                 .style('stroke', function(d,i,j){ return color(d, j, i); })
8127                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8128                     d3.select(this).classed('hover', true);
8129                     dispatch.elementMouseover({
8130                         value: getY(d,i),
8131                         point: d,
8132                         series: data[d.series],
8133                         pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
8134                         pointIndex: i,
8135                         seriesIndex: d.series,
8136                         e: d3.event
8137                     });
8138                 })
8139                 .on('mouseout', function(d,i) {
8140                     d3.select(this).classed('hover', false);
8141                     dispatch.elementMouseout({
8142                         value: getY(d,i),
8143                         point: d,
8144                         series: data[d.series],
8145                         pointIndex: i,
8146                         seriesIndex: d.series,
8147                         e: d3.event
8148                     });
8149                 })
8150                 .on('click', function(d,i) {
8151                     dispatch.elementClick({
8152                         value: getY(d,i),
8153                         point: d,
8154                         series: data[d.series],
8155                         pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
8156                         pointIndex: i,
8157                         seriesIndex: d.series,
8158                         e: d3.event
8159                     });
8160                     d3.event.stopPropagation();
8161                 })
8162                 .on('dblclick', function(d,i) {
8163                     dispatch.elementDblClick({
8164                         value: getY(d,i),
8165                         point: d,
8166                         series: data[d.series],
8167                         pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
8168                         pointIndex: i,
8169                         seriesIndex: d.series,
8170                         e: d3.event
8171                     });
8172                     d3.event.stopPropagation();
8173                 });
8174             bars
8175                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8176                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
8177
8178             if (barColor) {
8179                 if (!disabled) disabled = data.map(function() { return true });
8180                 bars
8181                     .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(); })
8182                     .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(); });
8183             }
8184
8185             var barSelection =
8186                 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
8187                     .delay(function(d,i) {
8188                         return i * duration / data[0].values.length;
8189                     });
8190             if (stacked)
8191                 barSelection
8192                     .attr('y', function(d,i) {
8193                         return y((stacked ? d.y1 : 0));
8194                     })
8195                     .attr('height', function(d,i) {
8196                         return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
8197                     })
8198                     .attr('x', function(d,i) {
8199                         return stacked ? 0 : (d.series * x.rangeBand() / data.length )
8200                     })
8201                     .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
8202             else
8203                 barSelection
8204                     .attr('x', function(d,i) {
8205                         return d.series * x.rangeBand() / data.length
8206                     })
8207                     .attr('width', x.rangeBand() / data.length)
8208                     .attr('y', function(d,i) {
8209                         return getY(d,i) < 0 ?
8210                             y(0) :
8211                                 y(0) - y(getY(d,i)) < 1 ?
8212                             y(0) - 1 :
8213                             y(getY(d,i)) || 0;
8214                     })
8215                     .attr('height', function(d,i) {
8216                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
8217                     });
8218
8219             //store old scales for use in transitions on update
8220             x0 = x.copy();
8221             y0 = y.copy();
8222
8223         });
8224
8225         renderWatch.renderEnd('multibar immediate');
8226
8227         return chart;
8228     }
8229
8230     //============================================================
8231     // Expose Public Variables
8232     //------------------------------------------------------------
8233
8234     // As a getter, returns a new instance of d3 dispatch and sets appropriate vars.
8235     // As a setter, sets dispatch.
8236     // Useful when same chart instance is used to render several data models.
8237     // Since dispatch is instance-specific, it cannot be contained inside chart model.
8238
8239     chart.dispatch = dispatch;
8240
8241     chart.x = function(_) {
8242         if (!arguments.length) return getX;
8243         getX = _;
8244         return chart;
8245     };
8246
8247     chart.y = function(_) {
8248         if (!arguments.length) return getY;
8249         getY = _;
8250         return chart;
8251     };
8252
8253     chart.margin = function(_) {
8254         if (!arguments.length) return margin;
8255         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8256         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8257         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8258         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8259         return chart;
8260     };
8261
8262     chart.width = function(_) {
8263         if (!arguments.length) return width;
8264         width = _;
8265         return chart;
8266     };
8267
8268     chart.height = function(_) {
8269         if (!arguments.length) return height;
8270         height = _;
8271         return chart;
8272     };
8273
8274     chart.xScale = function(_) {
8275         if (!arguments.length) return x;
8276         x = _;
8277         return chart;
8278     };
8279
8280     chart.yScale = function(_) {
8281         if (!arguments.length) return y;
8282         y = _;
8283         return chart;
8284     };
8285
8286     chart.xDomain = function(_) {
8287         if (!arguments.length) return xDomain;
8288         xDomain = _;
8289         return chart;
8290     };
8291
8292     chart.yDomain = function(_) {
8293         if (!arguments.length) return yDomain;
8294         yDomain = _;
8295         return chart;
8296     };
8297
8298     chart.xRange = function(_) {
8299         if (!arguments.length) return xRange;
8300         xRange = _;
8301         return chart;
8302     };
8303
8304     chart.yRange = function(_) {
8305         if (!arguments.length) return yRange;
8306         yRange = _;
8307         return chart;
8308     };
8309
8310     chart.forceY = function(_) {
8311         if (!arguments.length) return forceY;
8312         forceY = _;
8313         return chart;
8314     };
8315
8316     chart.stacked = function(_) {
8317         if (!arguments.length) return stacked;
8318         stacked = _;
8319         return chart;
8320     };
8321
8322     chart.stackOffset = function(_) {
8323         if (!arguments.length) return stackOffset;
8324         stackOffset = _;
8325         return chart;
8326     };
8327
8328     chart.clipEdge = function(_) {
8329         if (!arguments.length) return clipEdge;
8330         clipEdge = _;
8331         return chart;
8332     };
8333
8334     chart.color = function(_) {
8335         if (!arguments.length) return color;
8336         color = nv.utils.getColor(_);
8337         return chart;
8338     };
8339
8340     chart.barColor = function(_) {
8341         if (!arguments.length) return barColor;
8342         barColor = nv.utils.getColor(_);
8343         return chart;
8344     };
8345
8346     chart.disabled = function(_) {
8347         if (!arguments.length) return disabled;
8348         disabled = _;
8349         return chart;
8350     };
8351
8352     chart.id = function(_) {
8353         if (!arguments.length) return id;
8354         id = _;
8355         return chart;
8356     };
8357
8358     chart.hideable = function(_) {
8359         if (!arguments.length) return hideable;
8360         hideable = _;
8361         return chart;
8362     };
8363
8364     chart.groupSpacing = function(_) {
8365         if (!arguments.length) return groupSpacing;
8366         groupSpacing = _;
8367         return chart;
8368     };
8369
8370     chart.duration = function(_) {
8371         if (!arguments.length) return duration;
8372         duration = _;
8373         renderWatch.reset(duration);
8374         return chart;
8375     }
8376
8377
8378
8379     //============================================================
8380     // Deprecated Methods
8381     //------------------------------------------------------------
8382
8383     chart.delay = function(_) {
8384         nv.deprecated('multiBar.delay');
8385         return chart.duration(_);
8386     };
8387
8388     chart.options = nv.utils.optionsFunc.bind(chart);
8389
8390     //============================================================
8391
8392     return chart;
8393 }
8394
8395 nv.models.multiBarChart = function() {
8396     "use strict";
8397     //============================================================
8398     // Public Variables with Default Settings
8399     //------------------------------------------------------------
8400
8401     var multibar = nv.models.multiBar()
8402         , xAxis = nv.models.axis()
8403         , yAxis = nv.models.axis()
8404         , legend = nv.models.legend()
8405         , controls = nv.models.legend()
8406         ;
8407
8408     var margin = {top: 30, right: 20, bottom: 50, left: 60}
8409         , width = null
8410         , height = null
8411         , color = nv.utils.defaultColor()
8412         , showControls = true
8413         , showLegend = true
8414         , showXAxis = true
8415         , showYAxis = true
8416         , rightAlignYAxis = false
8417         , reduceXTicks = true // if false a tick will show for every data point
8418         , staggerLabels = false
8419         , rotateLabels = 0
8420         , tooltips = true
8421         , tooltip = function(key, x, y, e, graph) {
8422             return '<h3>' + key + '</h3>' +
8423                 '<p>' +  y + ' on ' + x + '</p>'
8424         }
8425         , x //can be accessed via chart.xScale()
8426         , y //can be accessed via chart.yScale()
8427         , state = nv.utils.state()
8428         , defaultState = null
8429         , noData = "No Data Available."
8430         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
8431         , controlWidth = function() { return showControls ? 180 : 0 }
8432         , duration = 250
8433         ;
8434
8435     state.stacked = false // DEPRECATED Maintained for backward compatibility
8436
8437     multibar
8438         .stacked(false)
8439     ;
8440     xAxis
8441         .orient('bottom')
8442         .tickPadding(7)
8443         .highlightZero(true)
8444         .showMaxMin(false)
8445         .tickFormat(function(d) { return d })
8446     ;
8447     yAxis
8448         .orient((rightAlignYAxis) ? 'right' : 'left')
8449         .tickFormat(d3.format(',.1f'))
8450     ;
8451
8452     controls.updateState(false);
8453     //============================================================
8454
8455
8456     //============================================================
8457     // Private Variables
8458     //------------------------------------------------------------
8459     var renderWatch = nv.utils.renderWatch(dispatch);
8460     var stacked = false;
8461
8462     var showTooltip = function(e, offsetElement) {
8463         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8464             top = e.pos[1] + ( offsetElement.offsetTop || 0),
8465             x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8466             y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8467             content = tooltip(e.series.key, x, y, e, chart);
8468
8469         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
8470     };
8471
8472     var stateGetter = function(data) {
8473         return function(){
8474             return {
8475                 active: data.map(function(d) { return !d.disabled }),
8476                 stacked: stacked
8477             };
8478         }
8479     };
8480
8481     var stateSetter = function(data) {
8482         return function(state) {
8483             if (state.stacked !== undefined)
8484                 stacked = state.stacked;
8485             if (state.active !== undefined)
8486                 data.forEach(function(series,i) {
8487                     series.disabled = !state.active[i];
8488                 });
8489         }
8490     }
8491
8492     //============================================================
8493
8494
8495     function chart(selection) {
8496         renderWatch.reset();
8497         renderWatch.models(multibar);
8498         if (showXAxis) renderWatch.models(xAxis);
8499         if (showYAxis) renderWatch.models(yAxis);
8500
8501         selection.each(function(data) {
8502             var container = d3.select(this),
8503                 that = this;
8504             nv.utils.initSVG(container);
8505             var availableWidth = (width  || parseInt(container.style('width')) || 960)
8506                     - margin.left - margin.right,
8507                 availableHeight = (height || parseInt(container.style('height')) || 400)
8508                     - margin.top - margin.bottom;
8509
8510             chart.update = function() {
8511                 if (duration === 0)
8512                     container.call(chart);
8513                 else
8514                     container.transition()
8515                         .duration(duration)
8516                         .call(chart);
8517             };
8518             chart.container = this;
8519
8520             state
8521                 .setter(stateSetter(data), chart.update)
8522                 .getter(stateGetter(data))
8523                 .update();
8524
8525             // DEPRECATED set state.disableddisabled
8526             state.disabled = data.map(function(d) { return !!d.disabled });
8527
8528             if (!defaultState) {
8529                 var key;
8530                 defaultState = {};
8531                 for (key in state) {
8532                     if (state[key] instanceof Array)
8533                         defaultState[key] = state[key].slice(0);
8534                     else
8535                         defaultState[key] = state[key];
8536                 }
8537             }
8538             //------------------------------------------------------------
8539             // Display noData message if there's nothing to show.
8540
8541             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8542                 var noDataText = container.selectAll('.nv-noData').data([noData]);
8543
8544                 noDataText.enter().append('text')
8545                     .attr('class', 'nvd3 nv-noData')
8546                     .attr('dy', '-.7em')
8547                     .style('text-anchor', 'middle');
8548
8549                 noDataText
8550                     .attr('x', margin.left + availableWidth / 2)
8551                     .attr('y', margin.top + availableHeight / 2)
8552                     .text(function(d) { return d });
8553
8554                 return chart;
8555             } else {
8556                 container.selectAll('.nv-noData').remove();
8557             }
8558
8559             //------------------------------------------------------------
8560
8561
8562             //------------------------------------------------------------
8563             // Setup Scales
8564
8565             x = multibar.xScale();
8566             y = multibar.yScale();
8567
8568             //------------------------------------------------------------
8569
8570
8571             //------------------------------------------------------------
8572             // Setup containers and skeleton of chart
8573
8574             var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8575             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8576             var g = wrap.select('g');
8577
8578             gEnter.append('g').attr('class', 'nv-x nv-axis');
8579             gEnter.append('g').attr('class', 'nv-y nv-axis');
8580             gEnter.append('g').attr('class', 'nv-barsWrap');
8581             gEnter.append('g').attr('class', 'nv-legendWrap');
8582             gEnter.append('g').attr('class', 'nv-controlsWrap');
8583
8584             //------------------------------------------------------------
8585
8586
8587             //------------------------------------------------------------
8588             // Legend
8589
8590             if (showLegend) {
8591                 legend.width(availableWidth - controlWidth());
8592
8593                 if (multibar.barColor())
8594                     data.forEach(function(series,i) {
8595                         series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8596                     })
8597
8598                 g.select('.nv-legendWrap')
8599                     .datum(data)
8600                     .call(legend);
8601
8602                 if ( margin.top != legend.height()) {
8603                     margin.top = legend.height();
8604                     availableHeight = (height || parseInt(container.style('height')) || 400)
8605                         - margin.top - margin.bottom;
8606                 }
8607
8608                 g.select('.nv-legendWrap')
8609                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8610             }
8611
8612             //------------------------------------------------------------
8613
8614
8615             //------------------------------------------------------------
8616             // Controls
8617
8618             if (showControls) {
8619                 var controlsData = [
8620                     { key: 'Grouped', disabled: multibar.stacked() },
8621                     { key: 'Stacked', disabled: !multibar.stacked() }
8622                 ];
8623
8624                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8625                 g.select('.nv-controlsWrap')
8626                     .datum(controlsData)
8627                     .attr('transform', 'translate(0,' + (-margin.top) +')')
8628                     .call(controls);
8629             }
8630
8631             //------------------------------------------------------------
8632
8633
8634             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8635
8636             if (rightAlignYAxis) {
8637                 g.select(".nv-y.nv-axis")
8638                     .attr("transform", "translate(" + availableWidth + ",0)");
8639             }
8640
8641             //------------------------------------------------------------
8642             // Main Chart Component(s)
8643
8644             multibar
8645                 .disabled(data.map(function(series) { return series.disabled }))
8646                 .width(availableWidth)
8647                 .height(availableHeight)
8648                 .color(data.map(function(d,i) {
8649                     return d.color || color(d, i);
8650                 }).filter(function(d,i) { return !data[i].disabled }))
8651
8652
8653             var barsWrap = g.select('.nv-barsWrap')
8654                 .datum(data.filter(function(d) { return !d.disabled }))
8655
8656             barsWrap.call(multibar);
8657
8658             //------------------------------------------------------------
8659
8660
8661             //------------------------------------------------------------
8662             // Setup Axes
8663
8664             if (showXAxis) {
8665                 xAxis
8666                     .scale(x)
8667                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8668                     .tickSize(-availableHeight, 0);
8669
8670                 g.select('.nv-x.nv-axis')
8671                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
8672                 g.select('.nv-x.nv-axis')
8673                     .call(xAxis);
8674
8675                 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8676
8677                 xTicks
8678                     .selectAll('line, text')
8679                     .style('opacity', 1)
8680
8681                 if (staggerLabels) {
8682                     var getTranslate = function(x,y) {
8683                         return "translate(" + x + "," + y + ")";
8684                     };
8685
8686                     var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
8687                     // Issue #140
8688                     xTicks
8689                         .selectAll("text")
8690                         .attr('transform', function(d,i,j) {
8691                             return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8692                         });
8693
8694                     var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8695                     g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8696                         .attr("transform", function(d,i) {
8697                             return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8698                         });
8699                 }
8700
8701                 if (reduceXTicks)
8702                     xTicks
8703                         .filter(function(d,i) {
8704                             return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8705                         })
8706                         .selectAll('text, line')
8707                         .style('opacity', 0);
8708
8709                 if(rotateLabels)
8710                     xTicks
8711                         .selectAll('.tick text')
8712                         .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8713                         .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8714
8715                 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8716                     .style('opacity', 1);
8717             }
8718
8719
8720             if (showYAxis) {
8721                 yAxis
8722                     .scale(y)
8723                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8724                     .tickSize( -availableWidth, 0);
8725
8726                 g.select('.nv-y.nv-axis')
8727                     .call(yAxis);
8728             }
8729
8730
8731             //------------------------------------------------------------
8732
8733
8734
8735             //============================================================
8736             // Event Handling/Dispatching (in chart's scope)
8737             //------------------------------------------------------------
8738
8739             legend.dispatch.on('stateChange', function(newState) {
8740                 for (var key in newState)
8741                     state[key] = newState[key];
8742                 dispatch.stateChange(state);
8743                 chart.update();
8744             });
8745
8746             controls.dispatch.on('legendClick', function(d,i) {
8747                 if (!d.disabled) return;
8748                 controlsData = controlsData.map(function(s) {
8749                     s.disabled = true;
8750                     return s;
8751                 });
8752                 d.disabled = false;
8753
8754                 switch (d.key) {
8755                     case 'Grouped':
8756                         multibar.stacked(false);
8757                         break;
8758                     case 'Stacked':
8759                         multibar.stacked(true);
8760                         break;
8761                 }
8762
8763                 state.stacked = multibar.stacked();
8764                 dispatch.stateChange(state);
8765
8766                 chart.update();
8767             });
8768
8769             dispatch.on('tooltipShow', function(e) {
8770                 if (tooltips) showTooltip(e, that.parentNode)
8771             });
8772
8773             // Update chart from a state object passed to event handler
8774             dispatch.on('changeState', function(e) {
8775
8776                 if (typeof e.disabled !== 'undefined') {
8777                     data.forEach(function(series,i) {
8778                         series.disabled = e.disabled[i];
8779                     });
8780
8781                     state.disabled = e.disabled;
8782                 }
8783
8784                 if (typeof e.stacked !== 'undefined') {
8785                     multibar.stacked(e.stacked);
8786                     state.stacked = e.stacked;
8787                     stacked = e.stacked;
8788                 }
8789
8790                 chart.update();
8791             });
8792
8793             //============================================================
8794
8795
8796         });
8797
8798         renderWatch.renderEnd('multibarchart immediate');
8799
8800         return chart;
8801     }
8802
8803
8804     //============================================================
8805     // Event Handling/Dispatching (out of chart's scope)
8806     //------------------------------------------------------------
8807
8808     multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8809         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8810         dispatch.tooltipShow(e);
8811     });
8812
8813     multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8814         dispatch.tooltipHide(e);
8815     });
8816     dispatch.on('tooltipHide', function() {
8817         if (tooltips) nv.tooltip.cleanup();
8818     });
8819
8820
8821     //============================================================
8822
8823
8824     //============================================================
8825     // Expose Public Variables
8826     //------------------------------------------------------------
8827
8828     // expose chart's sub-components
8829     chart.dispatch = dispatch;
8830     chart.multibar = multibar;
8831     chart.legend = legend;
8832     chart.xAxis = xAxis;
8833     chart.yAxis = yAxis;
8834     // DO NOT DELETE. This is currently overridden below
8835     // until deprecated portions are removed.
8836     chart.state = state;
8837
8838     d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8839         'id', 'stacked', 'stackOffset', 'delay', 'barColor','groupSpacing');
8840
8841     chart.options = nv.utils.optionsFunc.bind(chart);
8842
8843     chart.margin = function(_) {
8844         if (!arguments.length) return margin;
8845         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8846         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8847         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8848         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8849         return chart;
8850     };
8851
8852     chart.width = function(_) {
8853         if (!arguments.length) return width;
8854         width = _;
8855         return chart;
8856     };
8857
8858     chart.height = function(_) {
8859
8860         if (!arguments.length) return height;
8861         height = _;
8862         return chart;
8863     };
8864
8865     chart.color = function(_) {
8866
8867         if (!arguments.length) return color;
8868         color = nv.utils.getColor(_);
8869         legend.color(color);
8870         return chart;
8871     };
8872
8873     chart.showControls = function(_) {
8874         if (!arguments.length) return showControls;
8875         showControls = _;
8876         return chart;
8877     };
8878
8879     chart.showLegend = function(_) {
8880         if (!arguments.length) return showLegend;
8881         showLegend = _;
8882         return chart;
8883     };
8884
8885     chart.showXAxis = function(_) {
8886         if (!arguments.length) return showXAxis;
8887         showXAxis = _;
8888         return chart;
8889     };
8890
8891     chart.showYAxis = function(_) {
8892         if (!arguments.length) return showYAxis;
8893         showYAxis = _;
8894         return chart;
8895     };
8896
8897     chart.rightAlignYAxis = function(_) {
8898         if(!arguments.length) return rightAlignYAxis;
8899         rightAlignYAxis = _;
8900         yAxis.orient( (_) ? 'right' : 'left');
8901         return chart;
8902     };
8903
8904     chart.reduceXTicks= function(_) {
8905         if (!arguments.length) return reduceXTicks;
8906         reduceXTicks = _;
8907         return chart;
8908     };
8909
8910     chart.rotateLabels = function(_) {
8911         if (!arguments.length) return rotateLabels;
8912         rotateLabels = _;
8913         return chart;
8914     }
8915
8916     chart.staggerLabels = function(_) {
8917         if (!arguments.length) return staggerLabels;
8918         staggerLabels = _;
8919         return chart;
8920     };
8921
8922     chart.tooltip = function(_) {
8923         if (!arguments.length) return tooltip;
8924         tooltip = _;
8925         return chart;
8926     };
8927
8928     chart.tooltips = function(_) {
8929         if (!arguments.length) return tooltips;
8930         tooltips = _;
8931         return chart;
8932     };
8933
8934     chart.tooltipContent = function(_) {
8935         if (!arguments.length) return tooltip;
8936         tooltip = _;
8937         return chart;
8938     };
8939
8940     // DEPRECATED
8941     chart.state = function(_) {
8942         nv.deprecated('multiBarChart.state');
8943         if (!arguments.length) return state;
8944         state = _;
8945         return chart;
8946     };
8947     for (var key in state) {
8948         chart.state[key] = state[key];
8949     }
8950     // END DEPRECATED
8951
8952     chart.defaultState = function(_) {
8953         if (!arguments.length) return defaultState;
8954         defaultState = _;
8955         return chart;
8956     };
8957
8958     chart.noData = function(_) {
8959         if (!arguments.length) return noData;
8960         noData = _;
8961         return chart;
8962     };
8963
8964     chart.transitionDuration = function(_) {
8965         nv.deprecated('multiBarChart.transitionDuration');
8966         return chart.duration(_);
8967     };
8968
8969     chart.duration = function(_) {
8970         if (!arguments.length) return duration;
8971         duration = _;
8972         multibar.duration(duration);
8973         xAxis.duration(duration);
8974         yAxis.duration(duration);
8975         renderWatch.reset(duration);
8976         return chart;
8977     }
8978
8979     //============================================================
8980
8981
8982     return chart;
8983 }
8984
8985 nv.models.multiBarHorizontal = function() {
8986     "use strict";
8987     //============================================================
8988     // Public Variables with Default Settings
8989     //------------------------------------------------------------
8990
8991     var margin = {top: 0, right: 0, bottom: 0, left: 0}
8992         , width = 960
8993         , height = 500
8994         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8995         , x = d3.scale.ordinal()
8996         , y = d3.scale.linear()
8997         , getX = function(d) { return d.x }
8998         , getY = function(d) { return d.y }
8999         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
9000         , color = nv.utils.defaultColor()
9001         , barColor = null // adding the ability to set the color for each rather than the whole group
9002         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
9003         , stacked = false
9004         , showValues = false
9005         , showBarLabels = false
9006         , valuePadding = 60
9007         , valueFormat = d3.format(',.2f')
9008         , delay = 1200
9009         , xDomain
9010         , yDomain
9011         , xRange
9012         , yRange
9013         , duration = 250
9014         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
9015         ;
9016
9017     //============================================================
9018
9019
9020     //============================================================
9021     // Private Variables
9022     //------------------------------------------------------------
9023
9024     var x0, y0; //used to store previous scales
9025     var renderWatch = nv.utils.renderWatch(dispatch, duration);
9026
9027
9028     //============================================================
9029
9030
9031     function chart(selection) {
9032         renderWatch.reset();
9033         selection.each(function(data) {
9034             var availableWidth = width - margin.left - margin.right,
9035                 availableHeight = height - margin.top - margin.bottom,
9036                 container = d3.select(this);
9037             nv.utils.initSVG(container);
9038
9039             if (stacked)
9040                 data = d3.layout.stack()
9041                     .offset('zero')
9042                     .values(function(d){ return d.values })
9043                     .y(getY)
9044                 (data);
9045
9046
9047             //add series index to each data point for reference
9048             data.forEach(function(series, i) {
9049                 series.values.forEach(function(point) {
9050                     point.series = i;
9051                 });
9052             });
9053
9054
9055
9056             //------------------------------------------------------------
9057             // HACK for negative value stacking
9058             if (stacked)
9059                 data[0].values.map(function(d,i) {
9060                     var posBase = 0, negBase = 0;
9061                     data.map(function(d) {
9062                         var f = d.values[i]
9063                         f.size = Math.abs(f.y);
9064                         if (f.y<0)  {
9065                             f.y1 = negBase - f.size;
9066                             negBase = negBase - f.size;
9067                         } else
9068                         {
9069                             f.y1 = posBase;
9070                             posBase = posBase + f.size;
9071                         }
9072                     });
9073                 });
9074
9075
9076
9077             //------------------------------------------------------------
9078             // Setup Scales
9079
9080             // remap and flatten the data for use in calculating the scales' domains
9081             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
9082                 data.map(function(d) {
9083                     return d.values.map(function(d,i) {
9084                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
9085                     })
9086                 });
9087
9088             x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
9089                 .rangeBands(xRange || [0, availableHeight], .1);
9090
9091             //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
9092             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)))
9093
9094             if (showValues && !stacked)
9095                 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
9096             else
9097                 y.range(yRange || [0, availableWidth]);
9098
9099             x0 = x0 || x;
9100             y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
9101
9102             //------------------------------------------------------------
9103
9104
9105             //------------------------------------------------------------
9106             // Setup containers and skeleton of chart
9107
9108             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
9109             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
9110             var defsEnter = wrapEnter.append('defs');
9111             var gEnter = wrapEnter.append('g');
9112             var g = wrap.select('g');
9113
9114             gEnter.append('g').attr('class', 'nv-groups');
9115
9116             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9117
9118             //------------------------------------------------------------
9119
9120
9121
9122             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
9123                 .data(function(d) { return d }, function(d,i) { return i });
9124             groups.enter().append('g')
9125                 .style('stroke-opacity', 1e-6)
9126                 .style('fill-opacity', 1e-6);
9127             groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
9128                 .style('stroke-opacity', 1e-6)
9129                 .style('fill-opacity', 1e-6)
9130                 .remove();
9131             groups
9132                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
9133                 .classed('hover', function(d) { return d.hover })
9134                 .style('fill', function(d,i){ return color(d, i) })
9135                 .style('stroke', function(d,i){ return color(d, i) });
9136             groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
9137                 .style('stroke-opacity', 1)
9138                 .style('fill-opacity', .75);
9139
9140
9141             var bars = groups.selectAll('g.nv-bar')
9142                 .data(function(d) { return d.values });
9143
9144             bars.exit().remove();
9145
9146
9147             var barsEnter = bars.enter().append('g')
9148                 .attr('transform', function(d,i,j) {
9149                     return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
9150                 });
9151
9152             barsEnter.append('rect')
9153                 .attr('width', 0)
9154                 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
9155
9156             bars
9157                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
9158                     d3.select(this).classed('hover', true);
9159                     dispatch.elementMouseover({
9160                         value: getY(d,i),
9161                         point: d,
9162                         series: data[d.series],
9163                         pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
9164                         pointIndex: i,
9165                         seriesIndex: d.series,
9166                         e: d3.event
9167                     });
9168                 })
9169                 .on('mouseout', function(d,i) {
9170                     d3.select(this).classed('hover', false);
9171                     dispatch.elementMouseout({
9172                         value: getY(d,i),
9173                         point: d,
9174                         series: data[d.series],
9175                         pointIndex: i,
9176                         seriesIndex: d.series,
9177                         e: d3.event
9178                     });
9179                 })
9180                 .on('click', function(d,i) {
9181                     dispatch.elementClick({
9182                         value: getY(d,i),
9183                         point: d,
9184                         series: data[d.series],
9185                         pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
9186                         pointIndex: i,
9187                         seriesIndex: d.series,
9188                         e: d3.event
9189                     });
9190                     d3.event.stopPropagation();
9191                 })
9192                 .on('dblclick', function(d,i) {
9193                     dispatch.elementDblClick({
9194                         value: getY(d,i),
9195                         point: d,
9196                         series: data[d.series],
9197                         pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
9198                         pointIndex: i,
9199                         seriesIndex: d.series,
9200                         e: d3.event
9201                     });
9202                     d3.event.stopPropagation();
9203                 });
9204
9205
9206             barsEnter.append('text');
9207
9208             if (showValues && !stacked) {
9209                 bars.select('text')
9210                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
9211                     .attr('y', x.rangeBand() / (data.length * 2))
9212                     .attr('dy', '.32em')
9213                     .text(function(d,i) { return valueFormat(getY(d,i)) })
9214                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9215                     .select('text')
9216                     .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
9217             } else {
9218                 bars.selectAll('text').text('');
9219             }
9220
9221             if (showBarLabels && !stacked) {
9222                 barsEnter.append('text').classed('nv-bar-label',true);
9223                 bars.select('text.nv-bar-label')
9224                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
9225                     .attr('y', x.rangeBand() / (data.length * 2))
9226                     .attr('dy', '.32em')
9227                     .text(function(d,i) { return getX(d,i) });
9228                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9229                     .select('text.nv-bar-label')
9230                     .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
9231             }
9232             else {
9233                 bars.selectAll('text.nv-bar-label').text('');
9234             }
9235
9236             bars
9237                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
9238
9239             if (barColor) {
9240                 if (!disabled) disabled = data.map(function() { return true });
9241                 bars
9242                     .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(); })
9243                     .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(); });
9244             }
9245
9246             if (stacked)
9247                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9248                     .attr('transform', function(d,i) {
9249                         return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
9250                     })
9251                     .select('rect')
9252                     .attr('width', function(d,i) {
9253                         return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
9254                     })
9255                     .attr('height', x.rangeBand() );
9256             else
9257                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9258                     .attr('transform', function(d,i) {
9259                         //TODO: stacked must be all positive or all negative, not both?
9260                         return 'translate(' +
9261                             (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
9262                             + ',' +
9263                             (d.series * x.rangeBand() / data.length
9264                                 +
9265                                 x(getX(d,i)) )
9266                             + ')'
9267                     })
9268                     .select('rect')
9269                     .attr('height', x.rangeBand() / data.length )
9270                     .attr('width', function(d,i) {
9271                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
9272                     });
9273
9274
9275             //store old scales for use in transitions on update
9276             x0 = x.copy();
9277             y0 = y.copy();
9278
9279         });
9280
9281         renderWatch.renderEnd('multibarHorizontal immediate');
9282         return chart;
9283     }
9284
9285
9286     //============================================================
9287     // Expose Public Variables
9288     //------------------------------------------------------------
9289
9290     chart.dispatch = dispatch;
9291
9292     chart.options = nv.utils.optionsFunc.bind(chart);
9293
9294     chart.x = function(_) {
9295         if (!arguments.length) return getX;
9296         getX = _;
9297         return chart;
9298     };
9299
9300     chart.y = function(_) {
9301         if (!arguments.length) return getY;
9302         getY = _;
9303         return chart;
9304     };
9305
9306     chart.margin = function(_) {
9307         if (!arguments.length) return margin;
9308         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9309         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9310         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9311         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9312         return chart;
9313     };
9314
9315     chart.width = function(_) {
9316         if (!arguments.length) return width;
9317         width = _;
9318         return chart;
9319     };
9320
9321     chart.height = function(_) {
9322         if (!arguments.length) return height;
9323         height = _;
9324         return chart;
9325     };
9326
9327     chart.xScale = function(_) {
9328         if (!arguments.length) return x;
9329         x = _;
9330         return chart;
9331     };
9332
9333     chart.yScale = function(_) {
9334         if (!arguments.length) return y;
9335         y = _;
9336         return chart;
9337     };
9338
9339     chart.xDomain = function(_) {
9340         if (!arguments.length) return xDomain;
9341         xDomain = _;
9342         return chart;
9343     };
9344
9345     chart.yDomain = function(_) {
9346         if (!arguments.length) return yDomain;
9347         yDomain = _;
9348         return chart;
9349     };
9350
9351     chart.xRange = function(_) {
9352         if (!arguments.length) return xRange;
9353         xRange = _;
9354         return chart;
9355     };
9356
9357     chart.yRange = function(_) {
9358         if (!arguments.length) return yRange;
9359         yRange = _;
9360         return chart;
9361     };
9362
9363     chart.forceY = function(_) {
9364         if (!arguments.length) return forceY;
9365         forceY = _;
9366         return chart;
9367     };
9368
9369     chart.stacked = function(_) {
9370         if (!arguments.length) return stacked;
9371         stacked = _;
9372         return chart;
9373     };
9374
9375     chart.color = function(_) {
9376         if (!arguments.length) return color;
9377         color = nv.utils.getColor(_);
9378         return chart;
9379     };
9380
9381     chart.barColor = function(_) {
9382         if (!arguments.length) return barColor;
9383         barColor = nv.utils.getColor(_);
9384         return chart;
9385     };
9386
9387     chart.disabled = function(_) {
9388         if (!arguments.length) return disabled;
9389         disabled = _;
9390         return chart;
9391     };
9392
9393     chart.id = function(_) {
9394         if (!arguments.length) return id;
9395         id = _;
9396         return chart;
9397     };
9398
9399     chart.delay = function(_) {
9400         if (!arguments.length) return delay;
9401         delay = _;
9402         return chart;
9403     };
9404
9405     chart.showValues = function(_) {
9406         if (!arguments.length) return showValues;
9407         showValues = _;
9408         return chart;
9409     };
9410
9411     chart.showBarLabels = function(_) {
9412         if (!arguments.length) return showBarLabels;
9413         showBarLabels = _;
9414         return chart;
9415     };
9416
9417
9418     chart.valueFormat= function(_) {
9419         if (!arguments.length) return valueFormat;
9420         valueFormat = _;
9421         return chart;
9422     };
9423
9424     chart.valuePadding = function(_) {
9425         if (!arguments.length) return valuePadding;
9426         valuePadding = _;
9427         return chart;
9428     };
9429
9430     chart.duration = function(_) {
9431         if (!arguments.length) return duration;
9432         duration = _;
9433         renderWatch.reset(duration);
9434         return chart;
9435     };
9436
9437     //============================================================
9438
9439
9440     return chart;
9441 }
9442
9443 nv.models.multiBarHorizontalChart = function() {
9444     "use strict";
9445     //============================================================
9446     // Public Variables with Default Settings
9447     //------------------------------------------------------------
9448
9449     var multibar = nv.models.multiBarHorizontal()
9450         , xAxis = nv.models.axis()
9451         , yAxis = nv.models.axis()
9452         , legend = nv.models.legend().height(30)
9453         , controls = nv.models.legend().height(30)
9454         ;
9455
9456     var margin = {top: 30, right: 20, bottom: 50, left: 60}
9457         , width = null
9458         , height = null
9459         , color = nv.utils.defaultColor()
9460         , showControls = true
9461         , showLegend = true
9462         , showXAxis = true
9463         , showYAxis = true
9464         , stacked = false
9465         , tooltips = true
9466         , tooltip = function(key, x, y, e, graph) {
9467             return '<h3>' + key + ' - ' + x + '</h3>' +
9468                 '<p>' +  y + '</p>'
9469         }
9470         , x //can be accessed via chart.xScale()
9471         , y //can be accessed via chart.yScale()
9472         , state = nv.utils.state()
9473         , defaultState = null
9474         , noData = 'No Data Available.'
9475         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
9476         , controlWidth = function() { return showControls ? 180 : 0 }
9477         , duration = 250
9478         ;
9479
9480     state.stacked = false; // DEPRECATED Maintained for backward compatibility
9481
9482     multibar
9483         .stacked(stacked)
9484     ;
9485     xAxis
9486         .orient('left')
9487         .tickPadding(5)
9488         .highlightZero(false)
9489         .showMaxMin(false)
9490         .tickFormat(function(d) { return d })
9491     ;
9492     yAxis
9493         .orient('bottom')
9494         .tickFormat(d3.format(',.1f'))
9495     ;
9496
9497     controls.updateState(false);
9498     //============================================================
9499
9500
9501     //============================================================
9502     // Private Variables
9503     //------------------------------------------------------------
9504
9505
9506     var showTooltip = function(e, offsetElement) {
9507         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9508             top = e.pos[1] + ( offsetElement.offsetTop || 0),
9509             x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
9510             y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
9511             content = tooltip(e.series.key, x, y, e, chart);
9512
9513         nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
9514     };
9515
9516     var stateGetter = function(data) {
9517         return function(){
9518             return {
9519                 active: data.map(function(d) { return !d.disabled }),
9520                 stacked: stacked
9521             };
9522         }
9523     };
9524
9525     var stateSetter = function(data) {
9526         return function(state) {
9527             if (state.stacked !== undefined)
9528                 stacked = state.stacked;
9529             if (state.active !== undefined)
9530                 data.forEach(function(series,i) {
9531                     series.disabled = !state.active[i];
9532                 });
9533         }
9534     };
9535
9536     //============================================================
9537     var renderWatch = nv.utils.renderWatch(dispatch, duration);
9538
9539     function chart(selection) {
9540         renderWatch.reset();
9541         renderWatch.models(multibar);
9542         if (showXAxis) renderWatch.models(xAxis);
9543         if (showYAxis) renderWatch.models(yAxis);
9544
9545         selection.each(function(data) {
9546             var container = d3.select(this),
9547                 that = this;
9548             nv.utils.initSVG(container);
9549             var availableWidth = (width  || parseInt(container.style('width')) || 960)
9550                     - margin.left - margin.right,
9551                 availableHeight = (height || parseInt(container.style('height')) || 400)
9552                     - margin.top - margin.bottom;
9553
9554             chart.update = function() { container.transition().duration(duration).call(chart) };
9555             chart.container = this;
9556
9557             stacked = multibar.stacked();
9558
9559             state
9560                 .setter(stateSetter(data), chart.update)
9561                 .getter(stateGetter(data))
9562                 .update();
9563
9564             // DEPRECATED set state.disableddisabled
9565             state.disabled = data.map(function(d) { return !!d.disabled });
9566
9567             if (!defaultState) {
9568                 var key;
9569                 defaultState = {};
9570                 for (key in state) {
9571                     if (state[key] instanceof Array)
9572                         defaultState[key] = state[key].slice(0);
9573                     else
9574                         defaultState[key] = state[key];
9575                 }
9576             }
9577
9578             //------------------------------------------------------------
9579             // Display No Data message if there's nothing to show.
9580
9581             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9582                 var noDataText = container.selectAll('.nv-noData').data([noData]);
9583
9584                 noDataText.enter().append('text')
9585                     .attr('class', 'nvd3 nv-noData')
9586                     .attr('dy', '-.7em')
9587                     .style('text-anchor', 'middle');
9588
9589                 noDataText
9590                     .attr('x', margin.left + availableWidth / 2)
9591                     .attr('y', margin.top + availableHeight / 2)
9592                     .text(function(d) { return d });
9593
9594                 return chart;
9595             } else {
9596                 container.selectAll('.nv-noData').remove();
9597             }
9598
9599             //------------------------------------------------------------
9600
9601
9602             //------------------------------------------------------------
9603             // Setup Scales
9604
9605             x = multibar.xScale();
9606             y = multibar.yScale();
9607
9608             //------------------------------------------------------------
9609
9610
9611             //------------------------------------------------------------
9612             // Setup containers and skeleton of chart
9613
9614             var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
9615             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
9616             var g = wrap.select('g');
9617
9618             gEnter.append('g').attr('class', 'nv-x nv-axis');
9619             gEnter.append('g').attr('class', 'nv-y nv-axis')
9620                 .append('g').attr('class', 'nv-zeroLine')
9621                 .append('line');
9622             gEnter.append('g').attr('class', 'nv-barsWrap');
9623             gEnter.append('g').attr('class', 'nv-legendWrap');
9624             gEnter.append('g').attr('class', 'nv-controlsWrap');
9625
9626             //------------------------------------------------------------
9627
9628
9629             //------------------------------------------------------------
9630             // Legend
9631
9632             if (showLegend) {
9633                 legend.width(availableWidth - controlWidth());
9634
9635                 if (multibar.barColor())
9636                     data.forEach(function(series,i) {
9637                         series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
9638                     });
9639
9640                 g.select('.nv-legendWrap')
9641                     .datum(data)
9642                     .call(legend);
9643
9644                 if ( margin.top != legend.height()) {
9645                     margin.top = legend.height();
9646                     availableHeight = (height || parseInt(container.style('height')) || 400)
9647                         - margin.top - margin.bottom;
9648                 }
9649
9650                 g.select('.nv-legendWrap')
9651                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9652             }
9653
9654             //------------------------------------------------------------
9655
9656
9657             //------------------------------------------------------------
9658             // Controls
9659
9660             if (showControls) {
9661                 var controlsData = [
9662                     { key: 'Grouped', disabled: multibar.stacked() },
9663                     { key: 'Stacked', disabled: !multibar.stacked() }
9664                 ];
9665
9666                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
9667                 g.select('.nv-controlsWrap')
9668                     .datum(controlsData)
9669                     .attr('transform', 'translate(0,' + (-margin.top) +')')
9670                     .call(controls);
9671             }
9672
9673             //------------------------------------------------------------
9674
9675
9676             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9677
9678
9679             //------------------------------------------------------------
9680             // Main Chart Component(s)
9681
9682             multibar
9683                 .disabled(data.map(function(series) { return series.disabled }))
9684                 .width(availableWidth)
9685                 .height(availableHeight)
9686                 .color(data.map(function(d,i) {
9687                     return d.color || color(d, i);
9688                 }).filter(function(d,i) { return !data[i].disabled }));
9689
9690             var barsWrap = g.select('.nv-barsWrap')
9691                 .datum(data.filter(function(d) { return !d.disabled }));
9692
9693             barsWrap.transition().call(multibar);
9694
9695             //------------------------------------------------------------
9696
9697
9698             //------------------------------------------------------------
9699             // Setup Axes
9700
9701             if (showXAxis) {
9702                 xAxis
9703                     .scale(x)
9704                     .ticks( nv.utils.calcTicksY(availableHeight/24, data) )
9705                     .tickSize(-availableWidth, 0);
9706
9707                 g.select('.nv-x.nv-axis').call(xAxis);
9708
9709                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9710
9711                 xTicks
9712                     .selectAll('line, text');
9713             }
9714
9715             if (showYAxis) {
9716                 yAxis
9717                     .scale(y)
9718                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9719                     .tickSize( -availableHeight, 0);
9720
9721                 g.select('.nv-y.nv-axis')
9722                     .attr('transform', 'translate(0,' + availableHeight + ')');
9723                 g.select('.nv-y.nv-axis').call(yAxis);
9724             }
9725
9726             // Zero line
9727             g.select(".nv-zeroLine line")
9728                 .attr("x1", y(0))
9729                 .attr("x2", y(0))
9730                 .attr("y1", 0)
9731                 .attr("y2", -availableHeight)
9732             ;
9733
9734             //------------------------------------------------------------
9735
9736
9737
9738             //============================================================
9739             // Event Handling/Dispatching (in chart's scope)
9740             //------------------------------------------------------------
9741
9742             legend.dispatch.on('stateChange', function(newState) {
9743                 for (var key in newState)
9744                     state[key] = newState[key];
9745                 dispatch.stateChange(state);
9746                 chart.update();
9747             });
9748
9749             controls.dispatch.on('legendClick', function(d,i) {
9750                 if (!d.disabled) return;
9751                 controlsData = controlsData.map(function(s) {
9752                     s.disabled = true;
9753                     return s;
9754                 });
9755                 d.disabled = false;
9756
9757                 switch (d.key) {
9758                     case 'Grouped':
9759                         multibar.stacked(false);
9760                         break;
9761                     case 'Stacked':
9762                         multibar.stacked(true);
9763                         break;
9764                 }
9765
9766                 state.stacked = multibar.stacked();
9767                 dispatch.stateChange(state);
9768                 stacked = multibar.stacked();
9769
9770                 chart.update();
9771             });
9772
9773             dispatch.on('tooltipShow', function(e) {
9774                 if (tooltips) showTooltip(e, that.parentNode);
9775             });
9776
9777             // Update chart from a state object passed to event handler
9778             dispatch.on('changeState', function(e) {
9779
9780                 if (typeof e.disabled !== 'undefined') {
9781                     data.forEach(function(series,i) {
9782                         series.disabled = e.disabled[i];
9783                     });
9784
9785                     state.disabled = e.disabled;
9786                 }
9787
9788                 if (typeof e.stacked !== 'undefined') {
9789                     multibar.stacked(e.stacked);
9790                     state.stacked = e.stacked;
9791                     stacked = e.stacked;
9792                 }
9793
9794                 chart.update();
9795             });
9796             //============================================================
9797
9798         });
9799         renderWatch.renderEnd('multibar horizontal chart immediate');
9800         return chart;
9801     }
9802
9803
9804     //============================================================
9805     // Event Handling/Dispatching (out of chart's scope)
9806     //------------------------------------------------------------
9807
9808     multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9809         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9810         dispatch.tooltipShow(e);
9811     });
9812
9813     multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9814         dispatch.tooltipHide(e);
9815     });
9816     dispatch.on('tooltipHide', function() {
9817         if (tooltips) nv.tooltip.cleanup();
9818     });
9819
9820     //============================================================
9821
9822
9823     //============================================================
9824     // Expose Public Variables
9825     //------------------------------------------------------------
9826
9827     // expose chart's sub-components
9828     chart.dispatch = dispatch;
9829     chart.multibar = multibar;
9830     chart.legend = legend;
9831     chart.xAxis = xAxis;
9832     chart.yAxis = yAxis;
9833
9834     // DO NOT DELETE. This is currently overridden below
9835     // until deprecated portions are removed.
9836     chart.state = state;
9837
9838     d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY',
9839         'clipEdge', 'id', 'delay', 'showValues','showBarLabels', 'valueFormat', 'stacked', 'barColor');
9840
9841     chart.options = nv.utils.optionsFunc.bind(chart);
9842
9843     chart.margin = function(_) {
9844         if (!arguments.length) return margin;
9845         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9846         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9847         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9848         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9849         return chart;
9850     };
9851
9852     chart.width = function(_) {
9853         if (!arguments.length) return width;
9854         width = _;
9855         return chart;
9856     };
9857
9858     chart.height = function(_) {
9859         if (!arguments.length) return height;
9860         height = _;
9861         return chart;
9862     };
9863
9864     chart.color = function(_) {
9865         if (!arguments.length) return color;
9866         color = nv.utils.getColor(_);
9867         legend.color(color);
9868         return chart;
9869     };
9870
9871     chart.showControls = function(_) {
9872         if (!arguments.length) return showControls;
9873         showControls = _;
9874         return chart;
9875     };
9876
9877     chart.showLegend = function(_) {
9878         if (!arguments.length) return showLegend;
9879         showLegend = _;
9880         return chart;
9881     };
9882
9883     chart.showXAxis = function(_) {
9884         if (!arguments.length) return showXAxis;
9885         showXAxis = _;
9886         return chart;
9887     };
9888
9889     chart.showYAxis = function(_) {
9890         if (!arguments.length) return showYAxis;
9891         showYAxis = _;
9892         return chart;
9893     };
9894
9895     chart.tooltip = function(_) {
9896         if (!arguments.length) return tooltip;
9897         tooltip = _;
9898         return chart;
9899     };
9900
9901     chart.tooltips = function(_) {
9902         if (!arguments.length) return tooltips;
9903         tooltips = _;
9904         return chart;
9905     };
9906
9907     chart.tooltipContent = function(_) {
9908         if (!arguments.length) return tooltip;
9909         tooltip = _;
9910         return chart;
9911     };
9912
9913     // DEPRECATED
9914     chart.state = function(_) {
9915         nv.deprecated('multiBarHorizontalChart.state');
9916         if (!arguments.length) return state;
9917         state = _;
9918         return chart;
9919     };
9920     for (var key in state) {
9921         chart.state[key] = state[key];
9922     }
9923     // END DEPRECATED
9924
9925     chart.defaultState = function(_) {
9926         if (!arguments.length) return defaultState;
9927         defaultState = _;
9928         return chart;
9929     };
9930
9931     chart.noData = function(_) {
9932         if (!arguments.length) return noData;
9933         noData = _;
9934         return chart;
9935     };
9936
9937     chart.transitionDuration = function(_) {
9938         nv.deprecated('multiBarHorizontalChart.transitionDuration');
9939         return chart.duration(_);
9940     };
9941
9942     chart.duration = function(_) {
9943         if (!arguments.length) return duration;
9944         duration = _;
9945         renderWatch.reset(duration);
9946         multibar.duration(duration);
9947         xAxis.duration(duration);
9948         yAxis.duration(duration);
9949         return chart;
9950     };
9951     //============================================================
9952
9953     return chart;
9954 };
9955 nv.models.multiChart = function() {
9956     "use strict";
9957     //============================================================
9958     // Public Variables with Default Settings
9959     //------------------------------------------------------------
9960
9961     var margin = {top: 30, right: 20, bottom: 50, left: 60},
9962         color = nv.utils.defaultColor(),
9963         width = null,
9964         height = null,
9965         showLegend = true,
9966         tooltips = true,
9967         tooltip = function(key, x, y, e, graph) {
9968             return '<h3>' + key + '</h3>' +
9969                 '<p>' +  y + ' at ' + x + '</p>'
9970         },
9971         x,
9972         y,
9973         yDomain1,
9974         yDomain2,
9975         getX = function(d) { return d.x },
9976         getY = function(d) { return d.y},
9977         interpolate = 'monotone'
9978         ;
9979
9980     //============================================================
9981     // Private Variables
9982     //------------------------------------------------------------
9983
9984     var x = d3.scale.linear(),
9985         yScale1 = d3.scale.linear(),
9986         yScale2 = d3.scale.linear(),
9987
9988         lines1 = nv.models.line().yScale(yScale1),
9989         lines2 = nv.models.line().yScale(yScale2),
9990
9991         bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9992         bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9993
9994         stack1 = nv.models.stackedArea().yScale(yScale1),
9995         stack2 = nv.models.stackedArea().yScale(yScale2),
9996
9997         xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9998         yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9999         yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
10000
10001         legend = nv.models.legend().height(30),
10002         dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
10003
10004     var showTooltip = function(e, offsetElement) {
10005         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10006             top = e.pos[1] + ( offsetElement.offsetTop || 0),
10007             x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
10008             y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
10009             content = tooltip(e.series.key, x, y, e, chart);
10010
10011         nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
10012     };
10013
10014     function chart(selection) {
10015         selection.each(function(data) {
10016             var container = d3.select(this),
10017                 that = this;
10018             nv.utils.initSVG(container);
10019
10020             chart.update = function() { container.transition().call(chart); };
10021             chart.container = this;
10022
10023             var availableWidth = (width  || parseInt(container.style('width')) || 960)
10024                     - margin.left - margin.right,
10025                 availableHeight = (height || parseInt(container.style('height')) || 400)
10026                     - margin.top - margin.bottom;
10027
10028             var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
10029             var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
10030             var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
10031             var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
10032             var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
10033             var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
10034
10035             var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
10036                 .map(function(d) {
10037                     return d.values.map(function(d,i) {
10038                         return { x: d.x, y: d.y }
10039                     })
10040                 })
10041
10042             var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
10043                 .map(function(d) {
10044                     return d.values.map(function(d,i) {
10045                         return { x: d.x, y: d.y }
10046                     })
10047                 })
10048
10049             x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
10050                 .range([0, availableWidth]);
10051
10052             var wrap = container.selectAll('g.wrap.multiChart').data([data]);
10053             var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
10054
10055             gEnter.append('g').attr('class', 'x axis');
10056             gEnter.append('g').attr('class', 'y1 axis');
10057             gEnter.append('g').attr('class', 'y2 axis');
10058             gEnter.append('g').attr('class', 'lines1Wrap');
10059             gEnter.append('g').attr('class', 'lines2Wrap');
10060             gEnter.append('g').attr('class', 'bars1Wrap');
10061             gEnter.append('g').attr('class', 'bars2Wrap');
10062             gEnter.append('g').attr('class', 'stack1Wrap');
10063             gEnter.append('g').attr('class', 'stack2Wrap');
10064             gEnter.append('g').attr('class', 'legendWrap');
10065
10066             var g = wrap.select('g');
10067
10068             if (showLegend) {
10069                 legend.width( availableWidth / 2 );
10070
10071                 g.select('.legendWrap')
10072                     .datum(data.map(function(series) {
10073                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
10074                         series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
10075                         return series;
10076                     }))
10077                     .call(legend);
10078
10079                 if ( margin.top != legend.height()) {
10080                     margin.top = legend.height();
10081                     availableHeight = (height || parseInt(container.style('height')) || 400)
10082                         - margin.top - margin.bottom;
10083                 }
10084
10085                 g.select('.legendWrap')
10086                     .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
10087             }
10088
10089
10090             lines1
10091                 .width(availableWidth)
10092                 .height(availableHeight)
10093                 .interpolate(interpolate)
10094                 .color(data.map(function(d, i) {
10095                     return d.color || color[i % color.length];
10096                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
10097
10098             lines2
10099                 .width(availableWidth)
10100                 .height(availableHeight)
10101                 .interpolate(interpolate)
10102                 .color(data.map(function(d,i) {
10103                     return d.color || color[i % color.length];
10104                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
10105
10106             bars1
10107                 .width(availableWidth)
10108                 .height(availableHeight)
10109                 .color(data.map(function(d,i) {
10110                     return d.color || color[i % color.length];
10111                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
10112
10113             bars2
10114                 .width(availableWidth)
10115                 .height(availableHeight)
10116                 .color(data.map(function(d,i) {
10117                     return d.color || color[i % color.length];
10118                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
10119
10120             stack1
10121                 .width(availableWidth)
10122                 .height(availableHeight)
10123                 .color(data.map(function(d,i) {
10124                     return d.color || color[i % color.length];
10125                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
10126
10127             stack2
10128                 .width(availableWidth)
10129                 .height(availableHeight)
10130                 .color(data.map(function(d,i) {
10131                     return d.color || color[i % color.length];
10132                 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
10133
10134             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10135
10136
10137             var lines1Wrap = g.select('.lines1Wrap')
10138                 .datum(dataLines1)
10139             var bars1Wrap = g.select('.bars1Wrap')
10140                 .datum(dataBars1)
10141             var stack1Wrap = g.select('.stack1Wrap')
10142                 .datum(dataStack1)
10143
10144             var lines2Wrap = g.select('.lines2Wrap')
10145                 .datum(dataLines2)
10146             var bars2Wrap = g.select('.bars2Wrap')
10147                 .datum(dataBars2)
10148             var stack2Wrap = g.select('.stack2Wrap')
10149                 .datum(dataStack2)
10150
10151             var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
10152                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
10153             }).concat([{x:0, y:0}]) : []
10154             var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
10155                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
10156             }).concat([{x:0, y:0}]) : []
10157
10158             yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
10159                 .range([0, availableHeight])
10160
10161             yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
10162                 .range([0, availableHeight])
10163
10164             lines1.yDomain(yScale1.domain())
10165             bars1.yDomain(yScale1.domain())
10166             stack1.yDomain(yScale1.domain())
10167
10168             lines2.yDomain(yScale2.domain())
10169             bars2.yDomain(yScale2.domain())
10170             stack2.yDomain(yScale2.domain())
10171
10172             if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
10173             if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
10174
10175             if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
10176             if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
10177
10178             if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
10179             if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
10180
10181
10182
10183             xAxis
10184                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
10185                 .tickSize(-availableHeight, 0);
10186
10187             g.select('.x.axis')
10188                 .attr('transform', 'translate(0,' + availableHeight + ')');
10189             d3.transition(g.select('.x.axis'))
10190                 .call(xAxis);
10191
10192             yAxis1
10193                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
10194                 .tickSize( -availableWidth, 0);
10195
10196
10197             d3.transition(g.select('.y1.axis'))
10198                 .call(yAxis1);
10199
10200             yAxis2
10201                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
10202                 .tickSize( -availableWidth, 0);
10203
10204             d3.transition(g.select('.y2.axis'))
10205                 .call(yAxis2);
10206
10207             g.select('.y2.axis')
10208                 .style('opacity', series2.length ? 1 : 0)
10209                 .attr('transform', 'translate(' + x.range()[1] + ',0)');
10210
10211             legend.dispatch.on('stateChange', function(newState) {
10212                 chart.update();
10213             });
10214
10215             dispatch.on('tooltipShow', function(e) {
10216                 if (tooltips) showTooltip(e, that.parentNode);
10217             });
10218
10219         });
10220
10221         return chart;
10222     }
10223
10224
10225     //============================================================
10226     // Event Handling/Dispatching (out of chart's scope)
10227     //------------------------------------------------------------
10228
10229     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
10230         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10231         dispatch.tooltipShow(e);
10232     });
10233
10234     lines1.dispatch.on('elementMouseout.tooltip', function(e) {
10235         dispatch.tooltipHide(e);
10236     });
10237
10238     lines2.dispatch.on('elementMouseover.tooltip', function(e) {
10239         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10240         dispatch.tooltipShow(e);
10241     });
10242
10243     lines2.dispatch.on('elementMouseout.tooltip', function(e) {
10244         dispatch.tooltipHide(e);
10245     });
10246
10247     bars1.dispatch.on('elementMouseover.tooltip', function(e) {
10248         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10249         dispatch.tooltipShow(e);
10250     });
10251
10252     bars1.dispatch.on('elementMouseout.tooltip', function(e) {
10253         dispatch.tooltipHide(e);
10254     });
10255
10256     bars2.dispatch.on('elementMouseover.tooltip', function(e) {
10257         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10258         dispatch.tooltipShow(e);
10259     });
10260
10261     bars2.dispatch.on('elementMouseout.tooltip', function(e) {
10262         dispatch.tooltipHide(e);
10263     });
10264
10265     stack1.dispatch.on('tooltipShow', function(e) {
10266         //disable tooltips when value ~= 0
10267         //// TODO: consider removing points from voronoi that have 0 value instead of this hack
10268         if (!Math.round(stack1.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
10269             setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
10270             return false;
10271         }
10272
10273         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10274             dispatch.tooltipShow(e);
10275     });
10276
10277     stack1.dispatch.on('tooltipHide', function(e) {
10278         dispatch.tooltipHide(e);
10279     });
10280
10281     stack2.dispatch.on('tooltipShow', function(e) {
10282         //disable tooltips when value ~= 0
10283         //// TODO: consider removing points from voronoi that have 0 value instead of this hack
10284         if (!Math.round(stack2.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
10285             setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
10286             return false;
10287         }
10288
10289         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10290             dispatch.tooltipShow(e);
10291     });
10292
10293     stack2.dispatch.on('tooltipHide', function(e) {
10294         dispatch.tooltipHide(e);
10295     });
10296
10297     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
10298         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10299         dispatch.tooltipShow(e);
10300     });
10301
10302     lines1.dispatch.on('elementMouseout.tooltip', function(e) {
10303         dispatch.tooltipHide(e);
10304     });
10305
10306     lines2.dispatch.on('elementMouseover.tooltip', function(e) {
10307         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10308         dispatch.tooltipShow(e);
10309     });
10310
10311     lines2.dispatch.on('elementMouseout.tooltip', function(e) {
10312         dispatch.tooltipHide(e);
10313     });
10314
10315     dispatch.on('tooltipHide', function() {
10316         if (tooltips) nv.tooltip.cleanup();
10317     });
10318
10319
10320
10321     //============================================================
10322     // Global getters and setters
10323     //------------------------------------------------------------
10324
10325     chart.dispatch = dispatch;
10326     chart.lines1 = lines1;
10327     chart.lines2 = lines2;
10328     chart.bars1 = bars1;
10329     chart.bars2 = bars2;
10330     chart.stack1 = stack1;
10331     chart.stack2 = stack2;
10332     chart.xAxis = xAxis;
10333     chart.yAxis1 = yAxis1;
10334     chart.yAxis2 = yAxis2;
10335     chart.options = nv.utils.optionsFunc.bind(chart);
10336
10337     chart.x = function(_) {
10338         if (!arguments.length) return getX;
10339         getX = _;
10340         lines1.x(_);
10341         bars1.x(_);
10342         return chart;
10343     };
10344
10345     chart.y = function(_) {
10346         if (!arguments.length) return getY;
10347         getY = _;
10348         lines1.y(_);
10349         bars1.y(_);
10350         return chart;
10351     };
10352
10353     chart.yDomain1 = function(_) {
10354         if (!arguments.length) return yDomain1;
10355         yDomain1 = _;
10356         return chart;
10357     };
10358
10359     chart.yDomain2 = function(_) {
10360         if (!arguments.length) return yDomain2;
10361         yDomain2 = _;
10362         return chart;
10363     };
10364
10365     chart.margin = function(_) {
10366         if (!arguments.length) return margin;
10367         margin = _;
10368         return chart;
10369     };
10370
10371     chart.width = function(_) {
10372         if (!arguments.length) return width;
10373         width = _;
10374         return chart;
10375     };
10376
10377     chart.height = function(_) {
10378         if (!arguments.length) return height;
10379         height = _;
10380         return chart;
10381     };
10382
10383     chart.color = function(_) {
10384         if (!arguments.length) return color;
10385         color = _;
10386         legend.color(_);
10387         return chart;
10388     };
10389
10390     chart.showLegend = function(_) {
10391         if (!arguments.length) return showLegend;
10392         showLegend = _;
10393         return chart;
10394     };
10395
10396     chart.tooltips = function(_) {
10397         if (!arguments.length) return tooltips;
10398         tooltips = _;
10399         return chart;
10400     };
10401
10402     chart.tooltipContent = function(_) {
10403         if (!arguments.length) return tooltip;
10404         tooltip = _;
10405         return chart;
10406     };
10407
10408     chart.interpolate = function(_) {
10409         if(!arguments.length) {
10410             return interpolate;
10411         }
10412         interpolate = _;
10413         return chart;
10414     };
10415
10416     return chart;
10417 };
10418
10419
10420 nv.models.ohlcBar = function() {
10421     "use strict";
10422     //============================================================
10423     // Public Variables with Default Settings
10424     //------------------------------------------------------------
10425
10426     var margin = {top: 0, right: 0, bottom: 0, left: 0}
10427         , width = 960
10428         , height = 500
10429         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10430         , x = d3.scale.linear()
10431         , y = d3.scale.linear()
10432         , getX = function(d) { return d.x }
10433         , getY = function(d) { return d.y }
10434         , getOpen = function(d) { return d.open }
10435         , getClose = function(d) { return d.close }
10436         , getHigh = function(d) { return d.high }
10437         , getLow = function(d) { return d.low }
10438         , forceX = []
10439         , forceY = []
10440         , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10441         , clipEdge = true
10442         , color = nv.utils.defaultColor()
10443         , xDomain
10444         , yDomain
10445         , xRange
10446         , yRange
10447         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10448         ;
10449
10450     //============================================================
10451
10452     //============================================================
10453     // Private Variables
10454     //------------------------------------------------------------
10455
10456     //TODO: store old scales for transitions
10457
10458     //============================================================
10459
10460
10461     function chart(selection) {
10462         selection.each(function(data) {
10463             var availableWidth = width - margin.left - margin.right,
10464                 availableHeight = height - margin.top - margin.bottom,
10465                 container = d3.select(this);
10466             nv.utils.initSVG(container);
10467
10468             //------------------------------------------------------------
10469             // Setup Scales
10470
10471             x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
10472
10473             if (padData)
10474                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
10475             else
10476                 x.range(xRange || [0, availableWidth]);
10477
10478             y   .domain(yDomain || [
10479                 d3.min(data[0].values.map(getLow).concat(forceY)),
10480                 d3.max(data[0].values.map(getHigh).concat(forceY))
10481             ])
10482                 .range(yRange || [availableHeight, 0]);
10483
10484             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10485             if (x.domain()[0] === x.domain()[1])
10486                 x.domain()[0] ?
10487                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10488                     : x.domain([-1,1]);
10489
10490             if (y.domain()[0] === y.domain()[1])
10491                 y.domain()[0] ?
10492                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
10493                     : y.domain([-1,1]);
10494
10495             //------------------------------------------------------------
10496
10497
10498             //------------------------------------------------------------
10499             // Setup containers and skeleton of chart
10500
10501             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
10502             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
10503             var defsEnter = wrapEnter.append('defs');
10504             var gEnter = wrapEnter.append('g');
10505             var g = wrap.select('g');
10506
10507             gEnter.append('g').attr('class', 'nv-ticks');
10508
10509             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10510
10511             //------------------------------------------------------------
10512
10513
10514             container
10515                 .on('click', function(d,i) {
10516                     dispatch.chartClick({
10517                         data: d,
10518                         index: i,
10519                         pos: d3.event,
10520                         id: id
10521                     });
10522                 });
10523
10524
10525             defsEnter.append('clipPath')
10526                 .attr('id', 'nv-chart-clip-path-' + id)
10527                 .append('rect');
10528
10529             wrap.select('#nv-chart-clip-path-' + id + ' rect')
10530                 .attr('width', availableWidth)
10531                 .attr('height', availableHeight);
10532
10533             g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
10534
10535
10536
10537             var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
10538                 .data(function(d) { return d });
10539
10540             ticks.exit().remove();
10541
10542
10543             var ticksEnter = ticks.enter().append('path')
10544                 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
10545                 .attr('d', function(d,i) {
10546                     var w = (availableWidth / data[0].values.length) * .9;
10547                     return 'm0,0l0,'
10548                         + (y(getOpen(d,i))
10549                             - y(getHigh(d,i)))
10550                         + 'l'
10551                         + (-w/2)
10552                         + ',0l'
10553                         + (w/2)
10554                         + ',0l0,'
10555                         + (y(getLow(d,i)) - y(getOpen(d,i)))
10556                         + 'l0,'
10557                         + (y(getClose(d,i))
10558                             - y(getLow(d,i)))
10559                         + 'l'
10560                         + (w/2)
10561                         + ',0l'
10562                         + (-w/2)
10563                         + ',0z';
10564                 })
10565                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10566                 //.attr('fill', function(d,i) { return color[0]; })
10567                 //.attr('stroke', function(d,i) { return color[0]; })
10568                 //.attr('x', 0 )
10569                 //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
10570                 //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
10571                 .on('mouseover', function(d,i) {
10572                     d3.select(this).classed('hover', true);
10573                     dispatch.elementMouseover({
10574                         point: d,
10575                         series: data[0],
10576                         pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
10577                         pointIndex: i,
10578                         seriesIndex: 0,
10579                         e: d3.event
10580                     });
10581
10582                 })
10583                 .on('mouseout', function(d,i) {
10584                     d3.select(this).classed('hover', false);
10585                     dispatch.elementMouseout({
10586                         point: d,
10587                         series: data[0],
10588                         pointIndex: i,
10589                         seriesIndex: 0,
10590                         e: d3.event
10591                     });
10592                 })
10593                 .on('click', function(d,i) {
10594                     dispatch.elementClick({
10595                         //label: d[label],
10596                         value: getY(d,i),
10597                         data: d,
10598                         index: i,
10599                         pos: [x(getX(d,i)), y(getY(d,i))],
10600                         e: d3.event,
10601                         id: id
10602                     });
10603                     d3.event.stopPropagation();
10604                 })
10605                 .on('dblclick', function(d,i) {
10606                     dispatch.elementDblClick({
10607                         //label: d[label],
10608                         value: getY(d,i),
10609                         data: d,
10610                         index: i,
10611                         pos: [x(getX(d,i)), y(getY(d,i))],
10612                         e: d3.event,
10613                         id: id
10614                     });
10615                     d3.event.stopPropagation();
10616                 });
10617
10618             ticks
10619                 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
10620             d3.transition(ticks)
10621                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10622                 .attr('d', function(d,i) {
10623                     var w = (availableWidth / data[0].values.length) * .9;
10624                     return 'm0,0l0,'
10625                         + (y(getOpen(d,i))
10626                             - y(getHigh(d,i)))
10627                         + 'l'
10628                         + (-w/2)
10629                         + ',0l'
10630                         + (w/2)
10631                         + ',0l0,'
10632                         + (y(getLow(d,i))
10633                             - y(getOpen(d,i)))
10634                         + 'l0,'
10635                         + (y(getClose(d,i))
10636                             - y(getLow(d,i)))
10637                         + 'l'
10638                         + (w/2)
10639                         + ',0l'
10640                         + (-w/2)
10641                         + ',0z';
10642                 })
10643             //.attr('width', (availableWidth / data[0].values.length) * .9 )
10644
10645
10646             //d3.transition(ticks)
10647             //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
10648             //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
10649             //.order();  // not sure if this makes any sense for this model
10650
10651         });
10652
10653         return chart;
10654     }
10655
10656
10657     //============================================================
10658     // Expose Public Variables
10659     //------------------------------------------------------------
10660
10661     chart.dispatch = dispatch;
10662
10663     chart.options = nv.utils.optionsFunc.bind(chart);
10664
10665     chart.x = function(_) {
10666         if (!arguments.length) return getX;
10667         getX = _;
10668         return chart;
10669     };
10670
10671     chart.y = function(_) {
10672         if (!arguments.length) return getY;
10673         getY = _;
10674         return chart;
10675     };
10676
10677     chart.open = function(_) {
10678         if (!arguments.length) return getOpen;
10679         getOpen = _;
10680         return chart;
10681     };
10682
10683     chart.close = function(_) {
10684         if (!arguments.length) return getClose;
10685         getClose = _;
10686         return chart;
10687     };
10688
10689     chart.high = function(_) {
10690         if (!arguments.length) return getHigh;
10691         getHigh = _;
10692         return chart;
10693     };
10694
10695     chart.low = function(_) {
10696         if (!arguments.length) return getLow;
10697         getLow = _;
10698         return chart;
10699     };
10700
10701     chart.margin = function(_) {
10702         if (!arguments.length) return margin;
10703         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10704         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10705         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10706         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10707         return chart;
10708     };
10709
10710     chart.width = function(_) {
10711         if (!arguments.length) return width;
10712         width = _;
10713         return chart;
10714     };
10715
10716     chart.height = function(_) {
10717         if (!arguments.length) return height;
10718         height = _;
10719         return chart;
10720     };
10721
10722     chart.xScale = function(_) {
10723         if (!arguments.length) return x;
10724         x = _;
10725         return chart;
10726     };
10727
10728     chart.yScale = function(_) {
10729         if (!arguments.length) return y;
10730         y = _;
10731         return chart;
10732     };
10733
10734     chart.xDomain = function(_) {
10735         if (!arguments.length) return xDomain;
10736         xDomain = _;
10737         return chart;
10738     };
10739
10740     chart.yDomain = function(_) {
10741         if (!arguments.length) return yDomain;
10742         yDomain = _;
10743         return chart;
10744     };
10745
10746     chart.xRange = function(_) {
10747         if (!arguments.length) return xRange;
10748         xRange = _;
10749         return chart;
10750     };
10751
10752     chart.yRange = function(_) {
10753         if (!arguments.length) return yRange;
10754         yRange = _;
10755         return chart;
10756     };
10757
10758     chart.forceX = function(_) {
10759         if (!arguments.length) return forceX;
10760         forceX = _;
10761         return chart;
10762     };
10763
10764     chart.forceY = function(_) {
10765         if (!arguments.length) return forceY;
10766         forceY = _;
10767         return chart;
10768     };
10769
10770     chart.padData = function(_) {
10771         if (!arguments.length) return padData;
10772         padData = _;
10773         return chart;
10774     };
10775
10776     chart.clipEdge = function(_) {
10777         if (!arguments.length) return clipEdge;
10778         clipEdge = _;
10779         return chart;
10780     };
10781
10782     chart.color = function(_) {
10783         if (!arguments.length) return color;
10784         color = nv.utils.getColor(_);
10785         return chart;
10786     };
10787
10788     chart.id = function(_) {
10789         if (!arguments.length) return id;
10790         id = _;
10791         return chart;
10792     };
10793
10794     //============================================================
10795
10796
10797     return chart;
10798 }
10799 nv.models.pie = function() {
10800     "use strict";
10801
10802     //============================================================
10803     // Public Variables with Default Settings
10804     //------------------------------------------------------------
10805
10806     var margin = {top: 0, right: 0, bottom: 0, left: 0}
10807         , width = 500
10808         , height = 500
10809         , getX = function(d) { return d.x }
10810         , getY = function(d) { return d.y }
10811         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10812         , color = nv.utils.defaultColor()
10813         , valueFormat = d3.format(',.2f')
10814         , labelFormat = d3.format('%')
10815         , showLabels = true
10816         , pieLabelsOutside = true
10817         , donutLabelsOutside = false
10818         , labelType = "key"
10819         , labelThreshold = .02 //if slice percentage is under this, don't show label
10820         , donut = false
10821         , title = false
10822         , growOnHover = true
10823         , titleOffset = 0
10824         , labelSunbeamLayout = false
10825         , startAngle = false
10826         , endAngle = false
10827         , donutRatio = 0.5
10828         , duration = 250
10829         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
10830         ;
10831
10832
10833     //============================================================
10834     // chart function
10835     //------------------------------------------------------------
10836
10837     var renderWatch = nv.utils.renderWatch(dispatch);
10838
10839     function chart(selection) {
10840         renderWatch.reset();
10841         selection.each(function(data) {
10842             var availableWidth = width - margin.left - margin.right
10843                 ,availableHeight = height - margin.top - margin.bottom
10844                 ,radius = Math.min(availableWidth, availableHeight) / 2
10845                 ,arcRadius = radius-(radius / 5)
10846                 ,container = d3.select(this)
10847                 ;
10848             nv.utils.initSVG(container);
10849
10850             // Setup containers and skeleton of chart
10851             var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10852             var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10853             var gEnter = wrapEnter.append('g');
10854             var g = wrap.select('g');
10855             var g_pie = gEnter.append('g').attr('class', 'nv-pie');
10856             gEnter.append('g').attr('class', 'nv-pieLabels');
10857
10858             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10859             g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10860             g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10861
10862             //
10863             container.on('click', function(d,i) {
10864                 dispatch.chartClick({
10865                     data: d,
10866                     index: i,
10867                     pos: d3.event,
10868                     id: id
10869                 });
10870             });
10871
10872
10873             var arc = d3.svg.arc().outerRadius(arcRadius);
10874             var arcOver = d3.svg.arc().outerRadius(arcRadius + 5);
10875
10876             if (startAngle) {
10877                 arc.startAngle(startAngle);
10878                 arcOver.startAngle(startAngle);
10879             }
10880             if (endAngle) {
10881                 arc.endAngle(endAngle);
10882                 arcOver.endAngle(endAngle);
10883             }
10884             if (donut) {
10885                 arc.innerRadius(radius * donutRatio);
10886                 arcOver.innerRadius(radius * donutRatio);
10887             }
10888
10889             // Setup the Pie chart and choose the data element
10890             var pie = d3.layout.pie()
10891                 .sort(null)
10892                 .value(function(d) { return d.disabled ? 0 : getY(d) });
10893
10894             // if title is specified and donut, put it in the middle
10895             if (donut && title) {
10896                 var title_g = g_pie.append('g').attr('class', 'nv-pie');
10897
10898                 title_g.append("text")
10899                     .style("text-anchor", "middle")
10900                     .attr('class', 'nv-pie-title')
10901                     .text(function (d) {
10902                         return title;
10903                     })
10904                     .attr("dy", "0.35em") // trick to vertically center text
10905                     .attr('transform', function(d, i) {
10906                         return 'translate(0, '+ titleOffset + ')';
10907                     });
10908             }
10909
10910             var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
10911             var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
10912
10913             slices.exit().remove();
10914             pieLabels.exit().remove();
10915
10916             var ae = slices.enter().append('g')
10917             ae.attr('class', 'nv-slice')
10918             ae.on('mouseover', function(d,i){
10919                 d3.select(this).classed('hover', true);
10920                 if (growOnHover) {
10921                     d3.select(this).select("path").transition()
10922                         .duration(70)
10923                         .attr("d", arcOver);
10924                 }
10925                 dispatch.elementMouseover({
10926                     label: getX(d.data),
10927                     value: getY(d.data),
10928                     point: d.data,
10929                     pointIndex: i,
10930                     pos: [d3.event.pageX, d3.event.pageY],
10931                     id: id,
10932                     color: d3.select(this).style("fill")
10933                 });
10934             });
10935             ae.on('mouseout', function(d,i){
10936                 d3.select(this).classed('hover', false);
10937                 if (growOnHover) {
10938                     d3.select(this).select("path").transition()
10939                         .duration(50)
10940                         .attr("d", arc);
10941                 }
10942                 dispatch.elementMouseout({
10943                     label: getX(d.data),
10944                     value: getY(d.data),
10945                     point: d.data,
10946                     index: i,
10947                     id: id
10948                 });
10949             });
10950             ae.on('click', function(d,i) {
10951                 dispatch.elementClick({
10952                     label: getX(d.data),
10953                     value: getY(d.data),
10954                     point: d.data,
10955                     index: i,
10956                     pos: d3.event,
10957                     id: id
10958                 });
10959                 d3.event.stopPropagation();
10960             });
10961             ae.on('dblclick', function(d,i) {
10962                 dispatch.elementDblClick({
10963                     label: getX(d.data),
10964                     value: getY(d.data),
10965                     point: d.data,
10966                     index: i,
10967                     pos: d3.event,
10968                     id: id
10969                 });
10970                 d3.event.stopPropagation();
10971             });
10972
10973             slices.attr('fill', function(d,i) { return color(d, i); })
10974             slices.attr('stroke', function(d,i) { return color(d, i); });
10975
10976             var paths = ae.append('path').each(function(d) {
10977                 this._current = d;
10978             });
10979
10980             slices.select('path')
10981                 .transition()
10982                 .attr('d', arc)
10983                 .attrTween('d', arcTween);
10984
10985             if (showLabels) {
10986                 // This does the normal label
10987                 var labelsArc = d3.svg.arc().innerRadius(0);
10988
10989                 if (pieLabelsOutside){
10990                     var labelsArc = arc;
10991                 }
10992
10993                 if (donutLabelsOutside) {
10994                     labelsArc = d3.svg.arc().outerRadius(arc.outerRadius());
10995                 }
10996
10997                 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
10998                     var group = d3.select(this);
10999
11000                     group.attr('transform', function(d) {
11001                         if (labelSunbeamLayout) {
11002                             d.outerRadius = arcRadius + 10; // Set Outer Coordinate
11003                             d.innerRadius = arcRadius + 15; // Set Inner Coordinate
11004                             var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
11005                             if ((d.startAngle+d.endAngle)/2 < Math.PI) {
11006                                 rotateAngle -= 90;
11007                             } else {
11008                                 rotateAngle += 90;
11009                             }
11010                             return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
11011                         } else {
11012                             d.outerRadius = radius + 10; // Set Outer Coordinate
11013                             d.innerRadius = radius + 15; // Set Inner Coordinate
11014                             return 'translate(' + labelsArc.centroid(d) + ')'
11015                         }
11016                     });
11017
11018                     group.append('rect')
11019                         .style('stroke', '#fff')
11020                         .style('fill', '#fff')
11021                         .attr("rx", 3)
11022                         .attr("ry", 3);
11023
11024                     group.append('text')
11025                         .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
11026                         .style('fill', '#000')
11027
11028                 });
11029
11030                 var labelLocationHash = {};
11031                 var avgHeight = 14;
11032                 var avgWidth = 140;
11033                 var createHashKey = function(coordinates) {
11034                     return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
11035                 };
11036
11037                 pieLabels.watchTransition(renderWatch,'pie labels').attr('transform', function(d) {
11038                     if (labelSunbeamLayout) {
11039                         d.outerRadius = arcRadius + 10; // Set Outer Coordinate
11040                         d.innerRadius = arcRadius + 15; // Set Inner Coordinate
11041                         var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
11042                         if ((d.startAngle+d.endAngle)/2 < Math.PI) {
11043                             rotateAngle -= 90;
11044                         } else {
11045                             rotateAngle += 90;
11046                         }
11047                         return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
11048                     } else {
11049                         d.outerRadius = radius + 10; // Set Outer Coordinate
11050                         d.innerRadius = radius + 15; // Set Inner Coordinate
11051
11052                         /*
11053                          Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
11054                          Each label location is hashed, and if a hash collision occurs, we assume an overlap.
11055                          Adjust the label's y-position to remove the overlap.
11056                          */
11057                         var center = labelsArc.centroid(d);
11058                         if(d.value){
11059                             var hashKey = createHashKey(center);
11060                             if (labelLocationHash[hashKey]) {
11061                                 center[1] -= avgHeight;
11062                             }
11063                             labelLocationHash[createHashKey(center)] = true;
11064                         }
11065                         return 'translate(' + center + ')'
11066                     }
11067                 });
11068
11069                 pieLabels.select(".nv-label text")
11070                     .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
11071                     .text(function(d, i) {
11072                         var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
11073                         var labelTypes = {
11074                             "key" : getX(d.data),
11075                             "value": getY(d.data),
11076                             "percent": labelFormat(percent)
11077                         };
11078                         return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
11079                     })
11080                 ;
11081             }
11082
11083
11084             // Computes the angle of an arc, converting from radians to degrees.
11085             function angle(d) {
11086                 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
11087                 return a > 90 ? a - 180 : a;
11088             }
11089
11090             function arcTween(a) {
11091                 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
11092                 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
11093                 if (!donut) a.innerRadius = 0;
11094                 var i = d3.interpolate(this._current, a);
11095                 this._current = i(0);
11096                 return function(t) {
11097                     return arc(i(t));
11098                 };
11099             }
11100         });
11101
11102         renderWatch.renderEnd('pie immediate');
11103         return chart;
11104     }
11105
11106     //============================================================
11107     // Expose Public Variables
11108     //------------------------------------------------------------
11109
11110     chart.dispatch = dispatch;
11111     chart.options = nv.utils.optionsFunc.bind(chart);
11112
11113     chart._options = Object.create({}, {
11114         // simple options, just get/set the necessary values
11115         width:      {get: function(){return width;}, set: function(_){width=_;}},
11116         height:     {get: function(){return height;}, set: function(_){height=_;}},
11117         showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
11118         title:      {get: function(){return title;}, set: function(_){title=_;}},
11119         titleOffset:    {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
11120         labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
11121         labelFormat:    {get: function(){return labelFormat;}, set: function(_){labelFormat=_;}},
11122         valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
11123         x:          {get: function(){return getX;}, set: function(_){getX=_;}},
11124         id:         {get: function(){return id;}, set: function(_){id=_;}},
11125         endAngle:   {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
11126         startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
11127         donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
11128         pieLabelsOutside:   {get: function(){return pieLabelsOutside;}, set: function(_){pieLabelsOutside=_;}},
11129         donutLabelsOutside: {get: function(){return donutLabelsOutside;}, set: function(_){donutLabelsOutside=_;}},
11130         labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
11131         donut:              {get: function(){return donut;}, set: function(_){donut=_;}},
11132         growOnHover:        {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
11133
11134         // options that require extra logic in the setter
11135         margin: {get: function(){return margin;}, set: function(_){
11136             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11137             margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11138             margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11139             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11140         }},
11141         y: {get: function(){return getY;}, set: function(_){
11142             getY=d3.functor(_);
11143         }},
11144         color: {get: function(){return color;}, set: function(_){
11145             color=nv.utils.getColor(_);
11146         }},
11147         labelType:          {get: function(){return labelType;}, set: function(_){
11148             labelType= _ || 'key';
11149         }}
11150     });
11151
11152     nv.utils.initOptions(chart);
11153     return chart;
11154 };
11155 nv.models.pieChart = function() {
11156     "use strict";
11157
11158     //============================================================
11159     // Public Variables with Default Settings
11160     //------------------------------------------------------------
11161
11162     var pie = nv.models.pie();
11163     var legend = nv.models.legend();
11164
11165     var margin = {top: 30, right: 20, bottom: 20, left: 20}
11166         , width = null
11167         , height = null
11168         , showLegend = true
11169         , color = nv.utils.defaultColor()
11170         , tooltips = true
11171         , tooltip = function(key, y, e, graph) {
11172             return '<h3 style="background-color: '
11173                 + e.color + '">' + key + '</h3>'
11174                 + '<p>' +  y + '</p>';
11175         }
11176         , state = nv.utils.state()
11177         , defaultState = null
11178         , noData = "No Data Available."
11179         , duration = 250
11180         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
11181         ;
11182
11183     //============================================================
11184     // Private Variables
11185     //------------------------------------------------------------
11186
11187     var showTooltip = function(e, offsetElement) {
11188         var tooltipLabel = pie.x()(e.point);
11189         var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
11190             top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
11191             y = pie.valueFormat()(pie.y()(e.point)),
11192             content = tooltip(tooltipLabel, y, e, chart)
11193             ;
11194         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
11195     };
11196
11197     var renderWatch = nv.utils.renderWatch(dispatch);
11198
11199     var stateGetter = function(data) {
11200         return function(){
11201             return {
11202                 active: data.map(function(d) { return !d.disabled })
11203             };
11204         }
11205     };
11206
11207     var stateSetter = function(data) {
11208         return function(state) {
11209             if (state.active !== undefined) {
11210                 data.forEach(function (series, i) {
11211                     series.disabled = !state.active[i];
11212                 });
11213             }
11214         }
11215     };
11216
11217     //============================================================
11218     // Chart function
11219     //------------------------------------------------------------
11220
11221     function chart(selection) {
11222         renderWatch.reset();
11223         renderWatch.models(pie);
11224
11225         selection.each(function(data) {
11226             var container = d3.select(this);
11227             nv.utils.initSVG(container);
11228
11229             var that = this;
11230             var availableWidth = (width || parseInt(container.style('width'), 10) || 960)
11231                     - margin.left - margin.right,
11232                 availableHeight = (height || parseInt(container.style('height'), 10) || 400)
11233                     - margin.top - margin.bottom
11234                 ;
11235
11236             chart.update = function() { container.transition().call(chart); };
11237             chart.container = this;
11238
11239             state.setter(stateSetter(data), chart.update)
11240                 .getter(stateGetter(data))
11241                 .update();
11242
11243             //set state.disabled
11244             state.disabled = data.map(function(d) { return !!d.disabled });
11245
11246             if (!defaultState) {
11247                 var key;
11248                 defaultState = {};
11249                 for (key in state) {
11250                     if (state[key] instanceof Array)
11251                         defaultState[key] = state[key].slice(0);
11252                     else
11253                         defaultState[key] = state[key];
11254                 }
11255             }
11256
11257             // Display No Data message if there's nothing to show.
11258             if (!data || !data.length) {
11259                 var noDataText = container.selectAll('.nv-noData').data([noData]);
11260
11261                 noDataText.enter().append('text')
11262                     .attr('class', 'nvd3 nv-noData')
11263                     .attr('dy', '-.7em')
11264                     .style('text-anchor', 'middle');
11265
11266                 noDataText
11267                     .attr('x', margin.left + availableWidth / 2)
11268                     .attr('y', margin.top + availableHeight / 2)
11269                     .text(function(d) { return d });
11270
11271                 return chart;
11272             } else {
11273                 container.selectAll('.nv-noData').remove();
11274             }
11275
11276             // Setup containers and skeleton of chart
11277             var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
11278             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
11279             var g = wrap.select('g');
11280
11281             gEnter.append('g').attr('class', 'nv-pieWrap');
11282             gEnter.append('g').attr('class', 'nv-legendWrap');
11283
11284             // Legend
11285             if (showLegend) {
11286                 legend.width( availableWidth ).key(pie.x());
11287
11288                 wrap.select('.nv-legendWrap')
11289                     .datum(data)
11290                     .call(legend);
11291
11292                 if ( margin.top != legend.height()) {
11293                     margin.top = legend.height();
11294                     availableHeight = (height || parseInt(container.style('height')) || 400)
11295                         - margin.top - margin.bottom;
11296                 }
11297
11298                 wrap.select('.nv-legendWrap')
11299                     .attr('transform', 'translate(0,' + (-margin.top) +')');
11300             }
11301             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11302
11303             // Main Chart Component(s)
11304             pie.width(availableWidth).height(availableHeight);
11305             var pieWrap = g.select('.nv-pieWrap').datum([data]);
11306             d3.transition(pieWrap).call(pie);
11307
11308             // Event Handling/Dispatching (in chart's scope)
11309             legend.dispatch.on('stateChange', function(newState) {
11310                 for (var key in newState) {
11311                     state[key] = newState[key];
11312                 }
11313                 dispatch.stateChange(state);
11314                 chart.update();
11315             });
11316
11317             pie.dispatch.on('elementMouseout.tooltip', function(e) {
11318                 dispatch.tooltipHide(e);
11319             });
11320
11321             // Update chart from a state object passed to event handler
11322             dispatch.on('changeState', function(e) {
11323                 if (typeof e.disabled !== 'undefined') {
11324                     data.forEach(function(series,i) {
11325                         series.disabled = e.disabled[i];
11326                     });
11327                     state.disabled = e.disabled;
11328                 }
11329                 chart.update();
11330             });
11331
11332         });
11333
11334         renderWatch.renderEnd('pieChart immediate');
11335         return chart;
11336     }
11337
11338     //============================================================
11339     // Event Handling/Dispatching (out of chart's scope)
11340     //------------------------------------------------------------
11341
11342     pie.dispatch.on('elementMouseover.tooltip', function(e) {
11343         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
11344         dispatch.tooltipShow(e);
11345     });
11346
11347     dispatch.on('tooltipShow', function(e) {
11348         if (tooltips) showTooltip(e);
11349     });
11350
11351     dispatch.on('tooltipHide', function() {
11352         if (tooltips) nv.tooltip.cleanup();
11353     });
11354
11355     //============================================================
11356     // Expose Public Variables
11357     //------------------------------------------------------------
11358
11359     // expose chart's sub-components
11360     chart.legend = legend;
11361     chart.dispatch = dispatch;
11362     chart.pie = pie;
11363     chart.options = nv.utils.optionsFunc.bind(chart);
11364
11365     // use Object get/set functionality to map between vars and chart functions
11366     chart._options = Object.create({}, {
11367         // simple options, just get/set the necessary values
11368         noData:         {get: function(){return noData;},         set: function(_){noData=_;}},
11369         tooltipContent: {get: function(){return tooltip;},        set: function(_){tooltip=_;}},
11370         tooltips:       {get: function(){return tooltips;},       set: function(_){tooltips=_;}},
11371         showLegend:     {get: function(){return showLegend;},     set: function(_){showLegend=_;}},
11372         defaultState:   {get: function(){return defaultState;},   set: function(_){defaultState=_;}},
11373         // options that require extra logic in the setter
11374         color: {get: function(){return color;}, set: function(_){
11375             color = _;
11376             legend.color(color);
11377             pie.color(color);
11378         }},
11379         duration: {get: function(){return duration;}, set: function(_){
11380             duration = _;
11381             renderWatch.reset(duration);
11382         }}
11383     });
11384     nv.utils.inheritOptions(chart, pie);
11385     nv.utils.initOptions(chart);
11386     return chart;
11387 };
11388
11389 nv.models.scatter = function() {
11390     "use strict";
11391     //============================================================
11392     // Public Variables with Default Settings
11393     //------------------------------------------------------------
11394
11395     var margin       = {top: 0, right: 0, bottom: 0, left: 0}
11396         , width        = 960
11397         , height       = 500
11398         , color        = nv.utils.defaultColor() // chooses color
11399         , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
11400         , x            = d3.scale.linear()
11401         , y            = d3.scale.linear()
11402         , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
11403         , getX         = function(d) { return d.x } // accessor to get the x value
11404         , getY         = function(d) { return d.y } // accessor to get the y value
11405         , getSize      = function(d) { return d.size || 1} // accessor to get the point size
11406         , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
11407         , onlyCircles  = true // Set to false to use shapes
11408         , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
11409         , forceY       = [] // List of numbers to Force into the Y scale
11410         , forceSize    = [] // List of numbers to Force into the Size scale
11411         , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
11412         , pointKey     = null
11413         , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
11414         , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
11415         , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
11416         , clipEdge     = false // if true, masks points within x and y scale
11417         , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
11418         , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
11419         , xDomain      = null // Override x domain (skips the calculation from data)
11420         , yDomain      = null // Override y domain
11421         , xRange       = null // Override x range
11422         , yRange       = null // Override y range
11423         , sizeDomain   = null // Override point size domain
11424         , sizeRange    = null
11425         , singlePoint  = false
11426         , dispatch     = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
11427         , useVoronoi   = true
11428         , duration     = 250
11429         ;
11430
11431     //============================================================
11432
11433
11434     //============================================================
11435     // Private Variables
11436     //------------------------------------------------------------
11437
11438     var x0, y0, z0 // used to store previous scales
11439         , timeoutID
11440         , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
11441         , renderWatch = nv.utils.renderWatch(dispatch, duration)
11442         ;
11443
11444     //============================================================
11445
11446
11447     function chart(selection) {
11448         renderWatch.reset();
11449         selection.each(function(data) {
11450             var availableWidth = width - margin.left - margin.right,
11451                 availableHeight = height - margin.top - margin.bottom,
11452                 container = d3.select(this);
11453             nv.utils.initSVG(container);
11454
11455             //add series index to each data point for reference
11456             data.forEach(function(series, i) {
11457                 series.values.forEach(function(point) {
11458                     point.series = i;
11459                 });
11460             });
11461
11462             //------------------------------------------------------------
11463             // Setup Scales
11464
11465             // remap and flatten the data for use in calculating the scales' domains
11466             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
11467                 d3.merge(
11468                     data.map(function(d) {
11469                         return d.values.map(function(d,i) {
11470                             return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
11471                         })
11472                     })
11473                 );
11474
11475             x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
11476
11477             if (padData && data[0])
11478                 x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
11479             //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
11480             else
11481                 x.range(xRange || [0, availableWidth]);
11482
11483             y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
11484                 .range(yRange || [availableHeight, 0]);
11485
11486             z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
11487                 .range(sizeRange || [16, 256]);
11488
11489             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
11490             if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
11491             if (x.domain()[0] === x.domain()[1])
11492                 x.domain()[0] ?
11493                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
11494                     : x.domain([-1,1]);
11495
11496             if (y.domain()[0] === y.domain()[1])
11497                 y.domain()[0] ?
11498                     y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
11499                     : y.domain([-1,1]);
11500
11501             if ( isNaN(x.domain()[0])) {
11502                 x.domain([-1,1]);
11503             }
11504
11505             if ( isNaN(y.domain()[0])) {
11506                 y.domain([-1,1]);
11507             }
11508
11509
11510             x0 = x0 || x;
11511             y0 = y0 || y;
11512             z0 = z0 || z;
11513
11514             //------------------------------------------------------------
11515
11516
11517             //------------------------------------------------------------
11518             // Setup containers and skeleton of chart
11519
11520             var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
11521             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
11522             var defsEnter = wrapEnter.append('defs');
11523             var gEnter = wrapEnter.append('g');
11524             var g = wrap.select('g');
11525
11526             gEnter.append('g').attr('class', 'nv-groups');
11527             gEnter.append('g').attr('class', 'nv-point-paths');
11528
11529             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11530
11531             //------------------------------------------------------------
11532
11533
11534             defsEnter.append('clipPath')
11535                 .attr('id', 'nv-edge-clip-' + id)
11536                 .append('rect');
11537
11538             wrap.select('#nv-edge-clip-' + id + ' rect')
11539                 .attr('width', availableWidth)
11540                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
11541
11542             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
11543
11544
11545             function updateInteractiveLayer() {
11546
11547                 if (!interactive) return false;
11548
11549                 var eventElements;
11550
11551                 var vertices = d3.merge(data.map(function(group, groupIndex) {
11552                         return group.values
11553                             .map(function(point, pointIndex) {
11554                                 // *Adding noise to make duplicates very unlikely
11555                                 // *Injecting series and point index for reference
11556                                 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
11557                                  */
11558                                 var pX = getX(point,pointIndex);
11559                                 var pY = getY(point,pointIndex);
11560
11561                                 return [x(pX)+ Math.random() * 1e-7,
11562                                         y(pY)+ Math.random() * 1e-7,
11563                                     groupIndex,
11564                                     pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
11565                             })
11566                             .filter(function(pointArray, pointIndex) {
11567                                 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
11568                             })
11569                     })
11570                 );
11571
11572
11573
11574                 //inject series and point index for reference into voronoi
11575                 if (useVoronoi === true) {
11576
11577                     if (clipVoronoi) {
11578                         var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
11579                             .data([id])
11580                             .enter();
11581
11582                         pointClipsEnter.append('clipPath')
11583                             .attr('class', 'nv-point-clips')
11584                             .attr('id', 'nv-points-clip-' + id);
11585
11586                         var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
11587                             .data(vertices);
11588                         pointClips.enter().append('circle')
11589                             .attr('r', clipRadius);
11590                         pointClips.exit().remove();
11591                         pointClips
11592                             .attr('cx', function(d) { return d[0] })
11593                             .attr('cy', function(d) { return d[1] });
11594
11595                         wrap.select('.nv-point-paths')
11596                             .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
11597                     }
11598
11599
11600                     if(vertices.length) {
11601                         // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
11602                         vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
11603                         vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11604                         vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11605                         vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11606                     }
11607
11608                     var bounds = d3.geom.polygon([
11609                         [-10,-10],
11610                         [-10,height + 10],
11611                         [width + 10,height + 10],
11612                         [width + 10,-10]
11613                     ]);
11614
11615                     var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11616                         return {
11617                             'data': bounds.clip(d),
11618                             'series': vertices[i][2],
11619                             'point': vertices[i][3]
11620                         }
11621                     });
11622
11623
11624                     var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11625                         .data(voronoi);
11626                     pointPaths.enter().append('path')
11627                         .attr('class', function(d,i) { return 'nv-path-'+i; });
11628                     pointPaths.exit().remove();
11629                     pointPaths
11630                         .attr('d', function(d) {
11631                             if (!d || !d.data || d.data.length === 0)
11632                                 return 'M 0 0'
11633                             else
11634                                 return 'M' + d.data.join('L') + 'Z';
11635                         });
11636
11637                     var mouseEventCallback = function(d,mDispatch) {
11638                         if (needsUpdate) return 0;
11639                         var series = data[d.series];
11640                         if (typeof series === 'undefined') return;
11641
11642                         var point  = series.values[d.point];
11643
11644                         mDispatch({
11645                             point: point,
11646                             series: series,
11647                             pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11648                             seriesIndex: d.series,
11649                             pointIndex: d.point
11650                         });
11651                     };
11652
11653                     pointPaths
11654                         .on('click', function(d) {
11655                             mouseEventCallback(d, dispatch.elementClick);
11656                         })
11657                         .on('dblclick', function(d) {
11658                             mouseEventCallback(d, dispatch.elementDblClick);
11659                         })
11660                         .on('mouseover', function(d) {
11661                             mouseEventCallback(d, dispatch.elementMouseover);
11662                         })
11663                         .on('mouseout', function(d, i) {
11664                             mouseEventCallback(d, dispatch.elementMouseout);
11665                         });
11666
11667
11668                 } else {
11669                     /*
11670                      // bring data in form needed for click handlers
11671                      var dataWithPoints = vertices.map(function(d, i) {
11672                      return {
11673                      'data': d,
11674                      'series': vertices[i][2],
11675                      'point': vertices[i][3]
11676                      }
11677                      });
11678                      */
11679
11680                     // add event handlers to points instead voronoi paths
11681                     wrap.select('.nv-groups').selectAll('.nv-group')
11682                         .selectAll('.nv-point')
11683                         //.data(dataWithPoints)
11684                         //.style('pointer-events', 'auto') // recativate events, disabled by css
11685                         .on('click', function(d,i) {
11686                             //nv.log('test', d, i);
11687                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11688                             var series = data[d.series],
11689                                 point  = series.values[i];
11690
11691                             dispatch.elementClick({
11692                                 point: point,
11693                                 series: series,
11694                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11695                                 seriesIndex: d.series,
11696                                 pointIndex: i
11697                             });
11698                         })
11699                         .on('mouseover', function(d,i) {
11700                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11701                             var series = data[d.series],
11702                                 point  = series.values[i];
11703
11704                             dispatch.elementMouseover({
11705                                 point: point,
11706                                 series: series,
11707                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11708                                 seriesIndex: d.series,
11709                                 pointIndex: i
11710                             });
11711                         })
11712                         .on('mouseout', function(d,i) {
11713                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11714                             var series = data[d.series],
11715                                 point  = series.values[i];
11716
11717                             dispatch.elementMouseout({
11718                                 point: point,
11719                                 series: series,
11720                                 seriesIndex: d.series,
11721                                 pointIndex: i
11722                             });
11723                         });
11724                 }
11725
11726                 needsUpdate = false;
11727             }
11728
11729             needsUpdate = true;
11730             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11731                 .data(function(d) { return d }, function(d) { return d.key });
11732             groups.enter().append('g')
11733                 .style('stroke-opacity', 1e-6)
11734                 .style('fill-opacity', 1e-6);
11735             groups.exit()
11736                 .remove();
11737             groups
11738                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11739                 .classed('hover', function(d) { return d.hover });
11740             groups.watchTransition(renderWatch, 'scatter: groups')
11741                 .style('fill', function(d,i) { return color(d, i) })
11742                 .style('stroke', function(d,i) { return color(d, i) })
11743                 .style('stroke-opacity', 1)
11744                 .style('fill-opacity', .5);
11745
11746
11747             if (onlyCircles) {
11748                 var points = groups.selectAll('circle.nv-point')
11749                     .data(function(d) { return d.values }, pointKey);
11750                 points.enter().append('circle')
11751                     .style('fill', function (d,i) { return d.color })
11752                     .style('stroke', function (d,i) { return d.color })
11753                     .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11754                     .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11755                     .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11756                 points.exit().remove();
11757                 groups.exit().selectAll('path.nv-point')
11758                     .watchTransition(renderWatch, 'scatter exit')
11759                     .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11760                     .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11761                     .remove();
11762                 points.each(function(d,i) {
11763                     d3.select(this)
11764                         .classed('nv-point', true)
11765                         .classed('nv-point-' + i, true)
11766                         .classed('hover',false)
11767                     ;
11768                 });
11769                 points
11770                     .watchTransition(renderWatch, 'scatter points')
11771                     .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11772                     .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11773                     .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11774
11775             } else {
11776
11777                 var points = groups.selectAll('path.nv-point')
11778                     .data(function(d) { return d.values });
11779                 points.enter().append('path')
11780                     .style('fill', function (d,i) { return d.color })
11781                     .style('stroke', function (d,i) { return d.color })
11782                     .attr('transform', function(d,i) {
11783                         return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11784                     })
11785                     .attr('d',
11786                     d3.svg.symbol()
11787                         .type(getShape)
11788                         .size(function(d,i) { return z(getSize(d,i)) })
11789                 );
11790                 points.exit().remove();
11791                 groups.exit().selectAll('path.nv-point')
11792                     .watchTransition(renderWatch, 'scatter exit')
11793                     .attr('transform', function(d,i) {
11794                         return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11795                     })
11796                     .remove();
11797                 points.each(function(d,i) {
11798                     d3.select(this)
11799                         .classed('nv-point', true)
11800                         .classed('nv-point-' + i, true)
11801                         .classed('hover',false)
11802                     ;
11803                 });
11804                 points
11805                     .watchTransition(renderWatch, 'scatter points')
11806                     .attr('transform', function(d,i) {
11807                         //nv.log(d,i,getX(d,i), x(getX(d,i)));
11808                         return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11809                     })
11810                     .attr('d',
11811                     d3.svg.symbol()
11812                         .type(getShape)
11813                         .size(function(d,i) { return z(getSize(d,i)) })
11814                 );
11815             }
11816
11817
11818             // Delay updating the invisible interactive layer for smoother animation
11819             clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11820             timeoutID = setTimeout(updateInteractiveLayer, 300);
11821             //updateInteractiveLayer();
11822
11823             //store old scales for use in transitions on update
11824             x0 = x.copy();
11825             y0 = y.copy();
11826             z0 = z.copy();
11827
11828         });
11829         renderWatch.renderEnd('scatter immediate');
11830         return chart;
11831     }
11832
11833
11834     //============================================================
11835     // Event Handling/Dispatching (out of chart's scope)
11836     //------------------------------------------------------------
11837     chart.clearHighlights = function() {
11838         //Remove the 'hover' class from all highlighted points.
11839         d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11840     };
11841
11842     chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11843         d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11844             .classed("hover",isHoverOver);
11845     };
11846
11847
11848     dispatch.on('elementMouseover.point', function(d) {
11849         if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11850     });
11851
11852     dispatch.on('elementMouseout.point', function(d) {
11853         if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11854     });
11855
11856     //============================================================
11857
11858
11859     //============================================================
11860     // Expose Public Variables
11861     //------------------------------------------------------------
11862
11863     chart.dispatch = dispatch;
11864     chart.options = nv.utils.optionsFunc.bind(chart);
11865
11866     chart.x = function(_) {
11867         if (!arguments.length) return getX;
11868         getX = d3.functor(_);
11869         return chart;
11870     };
11871
11872     chart.y = function(_) {
11873         if (!arguments.length) return getY;
11874         getY = d3.functor(_);
11875         return chart;
11876     };
11877
11878     chart.size = function(_) {
11879         if (!arguments.length) return getSize;
11880         getSize = d3.functor(_);
11881         return chart;
11882     };
11883
11884     chart.margin = function(_) {
11885         if (!arguments.length) return margin;
11886         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11887         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11888         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11889         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11890         return chart;
11891     };
11892
11893     chart.width = function(_) {
11894         if (!arguments.length) return width;
11895         width = _;
11896         return chart;
11897     };
11898
11899     chart.height = function(_) {
11900         if (!arguments.length) return height;
11901         height = _;
11902         return chart;
11903     };
11904
11905     chart.xScale = function(_) {
11906         if (!arguments.length) return x;
11907         x = _;
11908         return chart;
11909     };
11910
11911     chart.yScale = function(_) {
11912         if (!arguments.length) return y;
11913         y = _;
11914         return chart;
11915     };
11916
11917     chart.zScale = function(_) {
11918         if (!arguments.length) return z;
11919         z = _;
11920         return chart;
11921     };
11922
11923     chart.xDomain = function(_) {
11924         if (!arguments.length) return xDomain;
11925         xDomain = _;
11926         return chart;
11927     };
11928
11929     chart.yDomain = function(_) {
11930         if (!arguments.length) return yDomain;
11931         yDomain = _;
11932         return chart;
11933     };
11934
11935     chart.sizeDomain = function(_) {
11936         if (!arguments.length) return sizeDomain;
11937         sizeDomain = _;
11938         return chart;
11939     };
11940
11941     chart.xRange = function(_) {
11942         if (!arguments.length) return xRange;
11943         xRange = _;
11944         return chart;
11945     };
11946
11947     chart.yRange = function(_) {
11948         if (!arguments.length) return yRange;
11949         yRange = _;
11950         return chart;
11951     };
11952
11953     chart.sizeRange = function(_) {
11954         if (!arguments.length) return sizeRange;
11955         sizeRange = _;
11956         return chart;
11957     };
11958
11959     chart.forceX = function(_) {
11960         if (!arguments.length) return forceX;
11961         forceX = _;
11962         return chart;
11963     };
11964
11965     chart.forceY = function(_) {
11966         if (!arguments.length) return forceY;
11967         forceY = _;
11968         return chart;
11969     };
11970
11971     chart.forceSize = function(_) {
11972         if (!arguments.length) return forceSize;
11973         forceSize = _;
11974         return chart;
11975     };
11976
11977     chart.interactive = function(_) {
11978         if (!arguments.length) return interactive;
11979         interactive = _;
11980         return chart;
11981     };
11982
11983     chart.pointKey = function(_) {
11984         if (!arguments.length) return pointKey;
11985         pointKey = _;
11986         return chart;
11987     };
11988
11989     chart.pointActive = function(_) {
11990         if (!arguments.length) return pointActive;
11991         pointActive = _;
11992         return chart;
11993     };
11994
11995     chart.padData = function(_) {
11996         if (!arguments.length) return padData;
11997         padData = _;
11998         return chart;
11999     };
12000
12001     chart.padDataOuter = function(_) {
12002         if (!arguments.length) return padDataOuter;
12003         padDataOuter = _;
12004         return chart;
12005     };
12006
12007     chart.clipEdge = function(_) {
12008         if (!arguments.length) return clipEdge;
12009         clipEdge = _;
12010         return chart;
12011     };
12012
12013     chart.clipVoronoi= function(_) {
12014         if (!arguments.length) return clipVoronoi;
12015         clipVoronoi = _;
12016         return chart;
12017     };
12018
12019     chart.useVoronoi= function(_) {
12020         if (!arguments.length) return useVoronoi;
12021         useVoronoi = _;
12022         if (useVoronoi === false) {
12023             clipVoronoi = false;
12024         }
12025         return chart;
12026     };
12027
12028     chart.clipRadius = function(_) {
12029         if (!arguments.length) return clipRadius;
12030         clipRadius = _;
12031         return chart;
12032     };
12033
12034     chart.color = function(_) {
12035         if (!arguments.length) return color;
12036         color = nv.utils.getColor(_);
12037         return chart;
12038     };
12039
12040     chart.shape = function(_) {
12041         if (!arguments.length) return getShape;
12042         getShape = _;
12043         return chart;
12044     };
12045
12046     chart.onlyCircles = function(_) {
12047         if (!arguments.length) return onlyCircles;
12048         onlyCircles = _;
12049         return chart;
12050     };
12051
12052     chart.id = function(_) {
12053         if (!arguments.length) return id;
12054         id = _;
12055         return chart;
12056     };
12057
12058     chart.singlePoint = function(_) {
12059         if (!arguments.length) return singlePoint;
12060         singlePoint = _;
12061         return chart;
12062     };
12063
12064     chart.duration = function(_) {
12065         if (!arguments.length) return duration;
12066         duration = _;
12067         renderWatch.reset(duration);
12068         return chart;
12069     };
12070
12071     //============================================================
12072
12073
12074     return chart;
12075 }
12076 nv.models.scatterChart = function() {
12077     "use strict";
12078     //============================================================
12079     // Public Variables with Default Settings
12080     //------------------------------------------------------------
12081
12082     var scatter      = nv.models.scatter()
12083         , xAxis        = nv.models.axis()
12084         , yAxis        = nv.models.axis()
12085         , legend       = nv.models.legend()
12086         , controls     = nv.models.legend()
12087         , distX        = nv.models.distribution()
12088         , distY        = nv.models.distribution()
12089         ;
12090
12091     var margin       = {top: 30, right: 20, bottom: 50, left: 75}
12092         , width        = null
12093         , height       = null
12094         , color        = nv.utils.defaultColor()
12095         , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12096         , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12097         , xPadding     = 0
12098         , yPadding     = 0
12099         , showDistX    = false
12100         , showDistY    = false
12101         , showLegend   = true
12102         , showXAxis    = true
12103         , showYAxis    = true
12104         , rightAlignYAxis = false
12105         , showControls = !!d3.fisheye
12106         , fisheye      = 0
12107         , pauseFisheye = false
12108         , tooltips     = true
12109         , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
12110         , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
12111         , tooltip      = null
12112         , state        = nv.utils.state()
12113         , defaultState = null
12114         , dispatch     = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
12115         , noData       = "No Data Available."
12116         , transitionDuration = 250
12117         , duration = 250
12118         ;
12119
12120     scatter
12121         .xScale(x)
12122         .yScale(y)
12123     ;
12124     xAxis
12125         .orient('bottom')
12126         .tickPadding(10)
12127     ;
12128     yAxis
12129         .orient((rightAlignYAxis) ? 'right' : 'left')
12130         .tickPadding(10)
12131     ;
12132     distX
12133         .axis('x')
12134     ;
12135     distY
12136         .axis('y')
12137     ;
12138
12139     controls.updateState(false);
12140
12141     //============================================================
12142
12143
12144     //============================================================
12145     // Private Variables
12146     //------------------------------------------------------------
12147
12148     var x0, y0;
12149     var renderWatch = nv.utils.renderWatch(dispatch, duration);
12150
12151     var showTooltip = function(e, offsetElement) {
12152         //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12153
12154         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12155             top = e.pos[1] + ( offsetElement.offsetTop || 0),
12156             leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12157             topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12158             leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12159             topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12160             xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12161             yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12162
12163         if( tooltipX != null )
12164             nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12165         if( tooltipY != null )
12166             nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12167         if( tooltip != null )
12168             nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12169     };
12170
12171     var controlsData = [
12172         { key: 'Magnify', disabled: true }
12173     ];
12174
12175     var stateGetter = function(data) {
12176         return function(){
12177             return {
12178                 active: data.map(function(d) { return !d.disabled })
12179             };
12180         }
12181     };
12182
12183     var stateSetter = function(data) {
12184         return function(state) {
12185             if (state.active !== undefined)
12186                 data.forEach(function(series,i) {
12187                     series.disabled = !state.active[i];
12188                 });
12189         }
12190     };
12191
12192     //============================================================
12193
12194
12195     function chart(selection) {
12196         renderWatch.reset();
12197         renderWatch.models(scatter);
12198         if (showXAxis) renderWatch.models(xAxis);
12199         if (showYAxis) renderWatch.models(yAxis);
12200         if (showDistX) renderWatch.models(distX);
12201         if (showDistY) renderWatch.models(distY);
12202
12203         selection.each(function(data) {
12204             var container = d3.select(this),
12205                 that = this;
12206             nv.utils.initSVG(container);
12207
12208             var availableWidth = (width  || parseInt(container.style('width')) || 960)
12209                     - margin.left - margin.right,
12210                 availableHeight = (height || parseInt(container.style('height')) || 400)
12211                     - margin.top - margin.bottom;
12212
12213             chart.update = function() {
12214                 if (duration === 0)
12215                     container.call(chart);
12216                 else
12217                     container.transition().duration(duration).call(chart);
12218             };
12219             chart.container = this;
12220
12221
12222             state
12223                 .setter(stateSetter(data), chart.update)
12224                 .getter(stateGetter(data))
12225                 .update();
12226
12227             // DEPRECATED set state.disabled
12228             state.disabled = data.map(function(d) { return !!d.disabled });
12229
12230             if (!defaultState) {
12231                 var key;
12232                 defaultState = {};
12233                 for (key in state) {
12234                     if (state[key] instanceof Array)
12235                         defaultState[key] = state[key].slice(0);
12236                     else
12237                         defaultState[key] = state[key];
12238                 }
12239             }
12240
12241             //------------------------------------------------------------
12242             // Display noData message if there's nothing to show.
12243
12244             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12245                 var noDataText = container.selectAll('.nv-noData').data([noData]);
12246
12247                 noDataText.enter().append('text')
12248                     .attr('class', 'nvd3 nv-noData')
12249                     .attr('dy', '-.7em')
12250                     .style('text-anchor', 'middle');
12251
12252                 noDataText
12253                     .attr('x', margin.left + availableWidth / 2)
12254                     .attr('y', margin.top + availableHeight / 2)
12255                     .text(function(d) { return d });
12256
12257                 return chart;
12258             } else {
12259                 container.selectAll('.nv-noData').remove();
12260             }
12261
12262             //------------------------------------------------------------
12263
12264
12265             //------------------------------------------------------------
12266             // Setup Scales
12267
12268             x0 = x0 || x;
12269             y0 = y0 || y;
12270
12271             //------------------------------------------------------------
12272
12273
12274             //------------------------------------------------------------
12275             // Setup containers and skeleton of chart
12276
12277             var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12278             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12279             var gEnter = wrapEnter.append('g');
12280             var g = wrap.select('g');
12281
12282             // background for pointer events
12283             gEnter.append('rect').attr('class', 'nvd3 nv-background');
12284
12285             gEnter.append('g').attr('class', 'nv-x nv-axis');
12286             gEnter.append('g').attr('class', 'nv-y nv-axis');
12287             gEnter.append('g').attr('class', 'nv-scatterWrap');
12288             gEnter.append('g').attr('class', 'nv-distWrap');
12289             gEnter.append('g').attr('class', 'nv-legendWrap');
12290             gEnter.append('g').attr('class', 'nv-controlsWrap');
12291
12292             //------------------------------------------------------------
12293
12294
12295             //------------------------------------------------------------
12296             // Legend
12297
12298             if (showLegend) {
12299                 var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
12300                 legend.width(legendWidth);
12301
12302                 wrap.select('.nv-legendWrap')
12303                     .datum(data)
12304                     .call(legend);
12305
12306                 if ( margin.top != legend.height()) {
12307                     margin.top = legend.height();
12308                     availableHeight = (height || parseInt(container.style('height')) || 400)
12309                         - margin.top - margin.bottom;
12310                 }
12311
12312                 wrap.select('.nv-legendWrap')
12313                     .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
12314             }
12315
12316             //------------------------------------------------------------
12317
12318
12319             //------------------------------------------------------------
12320             // Controls
12321
12322             if (showControls) {
12323                 controls.width(180).color(['#444']);
12324                 g.select('.nv-controlsWrap')
12325                     .datum(controlsData)
12326                     .attr('transform', 'translate(0,' + (-margin.top) +')')
12327                     .call(controls);
12328             }
12329
12330             //------------------------------------------------------------
12331
12332
12333             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12334
12335             if (rightAlignYAxis) {
12336                 g.select(".nv-y.nv-axis")
12337                     .attr("transform", "translate(" + availableWidth + ",0)");
12338             }
12339
12340             //------------------------------------------------------------
12341             // Main Chart Component(s)
12342
12343             scatter
12344                 .width(availableWidth)
12345                 .height(availableHeight)
12346                 .color(data.map(function(d,i) {
12347                     return d.color || color(d, i);
12348                 }).filter(function(d,i) { return !data[i].disabled }));
12349
12350             if (xPadding !== 0)
12351                 scatter.xDomain(null);
12352
12353             if (yPadding !== 0)
12354                 scatter.yDomain(null);
12355             wrap.select('.nv-scatterWrap')
12356                 .datum(data.filter(function(d) { return !d.disabled }))
12357                 .call(scatter);
12358
12359             //Adjust for x and y padding
12360             if (xPadding !== 0) {
12361                 var xRange = x.domain()[1] - x.domain()[0];
12362                 scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
12363             }
12364
12365             if (yPadding !== 0) {
12366                 var yRange = y.domain()[1] - y.domain()[0];
12367                 scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
12368             }
12369
12370             //Only need to update the scatter again if x/yPadding changed the domain.
12371             if (yPadding !== 0 || xPadding !== 0) {
12372                 wrap.select('.nv-scatterWrap')
12373                     .datum(data.filter(function(d) { return !d.disabled }))
12374                     .call(scatter);
12375             }
12376
12377             //------------------------------------------------------------
12378
12379
12380             //------------------------------------------------------------
12381             // Setup Axes
12382             if (showXAxis) {
12383                 xAxis
12384                     .scale(x)
12385                     .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
12386                     .tickSize( -availableHeight , 0);
12387
12388                 g.select('.nv-x.nv-axis')
12389                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
12390                     .call(xAxis);
12391
12392             }
12393
12394             if (showYAxis) {
12395                 yAxis
12396                     .scale(y)
12397                     .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
12398                     .tickSize( -availableWidth, 0);
12399
12400                 g.select('.nv-y.nv-axis')
12401                     .call(yAxis);
12402             }
12403
12404
12405             if (showDistX) {
12406                 distX
12407                     .getData(scatter.x())
12408                     .scale(x)
12409                     .width(availableWidth)
12410                     .color(data.map(function(d,i) {
12411                         return d.color || color(d, i);
12412                     }).filter(function(d,i) { return !data[i].disabled }));
12413                 gEnter.select('.nv-distWrap').append('g')
12414                     .attr('class', 'nv-distributionX');
12415                 g.select('.nv-distributionX')
12416                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
12417                     .datum(data.filter(function(d) { return !d.disabled }))
12418                     .call(distX);
12419             }
12420
12421             if (showDistY) {
12422                 distY
12423                     .getData(scatter.y())
12424                     .scale(y)
12425                     .width(availableHeight)
12426                     .color(data.map(function(d,i) {
12427                         return d.color || color(d, i);
12428                     }).filter(function(d,i) { return !data[i].disabled }));
12429                 gEnter.select('.nv-distWrap').append('g')
12430                     .attr('class', 'nv-distributionY');
12431                 g.select('.nv-distributionY')
12432                     .attr('transform',
12433                         'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12434                     .datum(data.filter(function(d) { return !d.disabled }))
12435                     .call(distY);
12436             }
12437
12438             //------------------------------------------------------------
12439
12440
12441
12442
12443             if (d3.fisheye) {
12444                 g.select('.nv-background')
12445                     .attr('width', availableWidth)
12446                     .attr('height', availableHeight);
12447
12448                 g.select('.nv-background').on('mousemove', updateFisheye);
12449                 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12450                 scatter.dispatch.on('elementClick.freezeFisheye', function() {
12451                     pauseFisheye = !pauseFisheye;
12452                 });
12453             }
12454
12455
12456             function updateFisheye() {
12457                 if (pauseFisheye) {
12458                     g.select('.nv-point-paths').style('pointer-events', 'all');
12459                     return false;
12460                 }
12461
12462                 g.select('.nv-point-paths').style('pointer-events', 'none' );
12463
12464                 var mouse = d3.mouse(this);
12465                 x.distortion(fisheye).focus(mouse[0]);
12466                 y.distortion(fisheye).focus(mouse[1]);
12467
12468                 g.select('.nv-scatterWrap')
12469                     .call(scatter);
12470
12471                 if (showXAxis)
12472                     g.select('.nv-x.nv-axis').call(xAxis);
12473
12474                 if (showYAxis)
12475                     g.select('.nv-y.nv-axis').call(yAxis);
12476
12477                 g.select('.nv-distributionX')
12478                     .datum(data.filter(function(d) { return !d.disabled }))
12479                     .call(distX);
12480                 g.select('.nv-distributionY')
12481                     .datum(data.filter(function(d) { return !d.disabled }))
12482                     .call(distY);
12483             }
12484
12485
12486
12487             //============================================================
12488             // Event Handling/Dispatching (in chart's scope)
12489             //------------------------------------------------------------
12490
12491             controls.dispatch.on('legendClick', function(d,i) {
12492                 d.disabled = !d.disabled;
12493
12494                 fisheye = d.disabled ? 0 : 2.5;
12495                 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12496                 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12497
12498                 if (d.disabled) {
12499                     x.distortion(fisheye).focus(0);
12500                     y.distortion(fisheye).focus(0);
12501
12502                     g.select('.nv-scatterWrap').call(scatter);
12503                     g.select('.nv-x.nv-axis').call(xAxis);
12504                     g.select('.nv-y.nv-axis').call(yAxis);
12505                 } else {
12506                     pauseFisheye = false;
12507                 }
12508
12509                 dispatch.stateChange(state);
12510
12511                 chart.update();
12512             });
12513
12514             legend.dispatch.on('stateChange', function(newState) {
12515                 for (var key in newState)
12516                     state[key] = newState[key];
12517                 dispatch.stateChange(state);
12518                 chart.update();
12519             });
12520
12521             scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12522                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12523                     .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
12524                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12525                     .attr('x2', e.pos[0] + distX.size());
12526
12527                 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12528                 dispatch.tooltipShow(e);
12529             });
12530
12531             dispatch.on('tooltipShow', function(e) {
12532                 if (tooltips) showTooltip(e, that.parentNode);
12533             });
12534
12535             // Update chart from a state object passed to event handler
12536             dispatch.on('changeState', function(e) {
12537
12538                 if (typeof e.disabled !== 'undefined') {
12539                     data.forEach(function(series,i) {
12540                         series.disabled = e.disabled[i];
12541                     });
12542
12543                     state.disabled = e.disabled;
12544                 }
12545
12546                 chart.update();
12547             });
12548
12549             //============================================================
12550
12551
12552             //store old scales for use in transitions on update
12553             x0 = x.copy();
12554             y0 = y.copy();
12555
12556
12557         });
12558         renderWatch.renderEnd('scatterChart immediate');
12559         return chart;
12560     }
12561
12562
12563     //============================================================
12564     // Event Handling/Dispatching (out of chart's scope)
12565     //------------------------------------------------------------
12566
12567     scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12568         dispatch.tooltipHide(e);
12569
12570         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12571             .attr('y1', 0);
12572         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12573             .attr('x2', distY.size());
12574     });
12575     dispatch.on('tooltipHide', function() {
12576         if (tooltips) nv.tooltip.cleanup();
12577     });
12578
12579     //============================================================
12580
12581
12582     //============================================================
12583     // Expose Public Variables
12584     //------------------------------------------------------------
12585
12586     // expose chart's sub-components
12587     chart.dispatch = dispatch;
12588     chart.scatter = scatter;
12589     chart.legend = legend;
12590     chart.controls = controls;
12591     chart.xAxis = xAxis;
12592     chart.yAxis = yAxis;
12593     chart.distX = distX;
12594     chart.distY = distY;
12595     // DO NOT DELETE. This is currently overridden below
12596     // until deprecated portions are removed.
12597     chart.state = state;
12598
12599     d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
12600     chart.options = nv.utils.optionsFunc.bind(chart);
12601
12602     chart.margin = function(_) {
12603         if (!arguments.length) return margin;
12604         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
12605         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
12606         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12607         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
12608         return chart;
12609     };
12610
12611     chart.width = function(_) {
12612         if (!arguments.length) return width;
12613         width = _;
12614         return chart;
12615     };
12616
12617     chart.height = function(_) {
12618         if (!arguments.length) return height;
12619         height = _;
12620         return chart;
12621     };
12622
12623     chart.color = function(_) {
12624         if (!arguments.length) return color;
12625         color = nv.utils.getColor(_);
12626         legend.color(color);
12627         distX.color(color);
12628         distY.color(color);
12629         return chart;
12630     };
12631
12632     chart.showDistX = function(_) {
12633         if (!arguments.length) return showDistX;
12634         showDistX = _;
12635         return chart;
12636     };
12637
12638     chart.showDistY = function(_) {
12639         if (!arguments.length) return showDistY;
12640         showDistY = _;
12641         return chart;
12642     };
12643
12644     chart.showControls = function(_) {
12645         if (!arguments.length) return showControls;
12646         showControls = _;
12647         return chart;
12648     };
12649
12650     chart.showLegend = function(_) {
12651         if (!arguments.length) return showLegend;
12652         showLegend = _;
12653         return chart;
12654     };
12655
12656     chart.showXAxis = function(_) {
12657         if (!arguments.length) return showXAxis;
12658         showXAxis = _;
12659         return chart;
12660     };
12661
12662     chart.showYAxis = function(_) {
12663         if (!arguments.length) return showYAxis;
12664         showYAxis = _;
12665         return chart;
12666     };
12667
12668     chart.rightAlignYAxis = function(_) {
12669         if(!arguments.length) return rightAlignYAxis;
12670         rightAlignYAxis = _;
12671         yAxis.orient( (_) ? 'right' : 'left');
12672         return chart;
12673     };
12674
12675
12676     chart.fisheye = function(_) {
12677         if (!arguments.length) return fisheye;
12678         fisheye = _;
12679         return chart;
12680     };
12681
12682     chart.xPadding = function(_) {
12683         if (!arguments.length) return xPadding;
12684         xPadding = _;
12685         return chart;
12686     };
12687
12688     chart.yPadding = function(_) {
12689         if (!arguments.length) return yPadding;
12690         yPadding = _;
12691         return chart;
12692     };
12693
12694     chart.tooltips = function(_) {
12695         if (!arguments.length) return tooltips;
12696         tooltips = _;
12697         return chart;
12698     };
12699
12700     chart.tooltipContent = function(_) {
12701         if (!arguments.length) return tooltip;
12702         tooltip = _;
12703         return chart;
12704     };
12705
12706     chart.tooltipXContent = function(_) {
12707         if (!arguments.length) return tooltipX;
12708         tooltipX = _;
12709         return chart;
12710     };
12711
12712     chart.tooltipYContent = function(_) {
12713         if (!arguments.length) return tooltipY;
12714         tooltipY = _;
12715         return chart;
12716     };
12717
12718     // DEPRECATED
12719     chart.state = function(_) {
12720         nv.deprecated('scatterChart.state');
12721         if (!arguments.length) return state;
12722         state = _;
12723         return chart;
12724     };
12725     for (var key in state) {
12726         chart.state[key] = state[key];
12727     }
12728     // END DEPRECATED
12729
12730     chart.defaultState = function(_) {
12731         if (!arguments.length) return defaultState;
12732         defaultState = _;
12733         return chart;
12734     };
12735
12736     chart.noData = function(_) {
12737         if (!arguments.length) return noData;
12738         noData = _;
12739         return chart;
12740     };
12741     chart.duration = function(_) {
12742         if (!arguments.length) return duration;
12743         duration = _;
12744         renderWatch.reset(duration);
12745         scatter.duration(duration);
12746         xAxis.duration(duration);
12747         yAxis.duration(duration);
12748         distX.duration(duration);
12749         distY.duration(duration);
12750         return chart;
12751     };
12752     chart.transitionDuration = function(_) {
12753         nv.deprecated('scatterChart.transitionDuration');
12754         return chart.duration(_);
12755     };
12756
12757     //============================================================
12758
12759
12760     return chart;
12761 };
12762
12763 nv.models.scatterPlusLineChart = function() {
12764     "use strict";
12765     //============================================================
12766     // Public Variables with Default Settings
12767     //------------------------------------------------------------
12768
12769     var scatter      = nv.models.scatter()
12770         , xAxis        = nv.models.axis()
12771         , yAxis        = nv.models.axis()
12772         , legend       = nv.models.legend()
12773         , controls     = nv.models.legend()
12774         , distX        = nv.models.distribution()
12775         , distY        = nv.models.distribution()
12776         ;
12777
12778     var margin       = {top: 30, right: 20, bottom: 50, left: 75}
12779         , width        = null
12780         , height       = null
12781         , color        = nv.utils.defaultColor()
12782         , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12783         , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12784         , showDistX    = false
12785         , showDistY    = false
12786         , showLegend   = true
12787         , showXAxis    = true
12788         , showYAxis    = true
12789         , rightAlignYAxis = false
12790         , showControls = !!d3.fisheye
12791         , fisheye      = 0
12792         , pauseFisheye = false
12793         , tooltips     = true
12794         , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
12795         , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
12796         , tooltip      = function(key, x, y, date) { return '<h3>' + key + '</h3>'
12797             + '<p>' + date + '</p>' }
12798         , state = nv.utils.state()
12799         , defaultState = null
12800         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
12801         , noData       = "No Data Available."
12802         , duration = 250
12803         ;
12804
12805     state.fisheye = 0; // DEPRECATED Maintained for backward compatibility
12806
12807     scatter
12808         .xScale(x)
12809         .yScale(y)
12810     ;
12811     xAxis
12812         .orient('bottom')
12813         .tickPadding(10)
12814     ;
12815     yAxis
12816         .orient((rightAlignYAxis) ? 'right' : 'left')
12817         .tickPadding(10)
12818     ;
12819     distX
12820         .axis('x')
12821     ;
12822     distY
12823         .axis('y')
12824     ;
12825
12826     controls.updateState(false);
12827     //============================================================
12828
12829
12830     //============================================================
12831     // Private Variables
12832     //------------------------------------------------------------
12833
12834     var x0, y0
12835         , renderWatch = nv.utils.renderWatch(dispatch, duration);
12836
12837     var showTooltip = function(e, offsetElement) {
12838         //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12839
12840         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12841             top = e.pos[1] + ( offsetElement.offsetTop || 0),
12842             leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12843             topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12844             leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12845             topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12846             xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12847             yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12848
12849         if( tooltipX != null )
12850             nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12851         if( tooltipY != null )
12852             nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12853         if( tooltip != null )
12854             nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12855     };
12856
12857     var controlsData = [
12858         { key: 'Magnify', disabled: true }
12859     ];
12860
12861     var stateGetter = function(data) {
12862         return function(){
12863             return {
12864                 active: data.map(function(d) { return !d.disabled }),
12865                 fisheye: fisheye
12866             };
12867         }
12868     };
12869
12870     var stateSetter = function(data) {
12871         return function(state) {
12872             if (state.fisheye !== undefined)
12873                 fisheye = state.fisheye;
12874             if (state.active !== undefined)
12875                 data.forEach(function(series,i) {
12876                     series.disabled = !state.active[i];
12877                 });
12878         }
12879     };
12880
12881     //============================================================
12882
12883
12884     function chart(selection) {
12885         renderWatch.reset();
12886         renderWatch.models(scatter);
12887         if (showXAxis) renderWatch.models(xAxis);
12888         if (showYAxis) renderWatch.models(yAxis);
12889         if (showDistX) renderWatch.models(distX);
12890         if (showDistY) renderWatch.models(distY);
12891
12892         selection.each(function(data) {
12893             var container = d3.select(this),
12894                 that = this;
12895             nv.utils.initSVG(container);
12896
12897             var availableWidth = (width  || parseInt(container.style('width')) || 960)
12898                     - margin.left - margin.right,
12899                 availableHeight = (height || parseInt(container.style('height')) || 400)
12900                     - margin.top - margin.bottom;
12901
12902             chart.update = function() {
12903                 if (duration === 0)
12904                     container.call(chart);
12905                 else
12906                     container.transition().duration(duration).call(chart);
12907             };
12908             chart.container = this;
12909
12910             state
12911                 .setter(stateSetter(data), chart.update)
12912                 .getter(stateGetter(data))
12913                 .update();
12914
12915             // DEPRECATED set state.disableddisabled
12916             state.disabled = data.map(function(d) { return !!d.disabled });
12917
12918             if (!defaultState) {
12919                 var key;
12920                 defaultState = {};
12921                 for (key in state) {
12922                     if (state[key] instanceof Array)
12923                         defaultState[key] = state[key].slice(0);
12924                     else
12925                         defaultState[key] = state[key];
12926                 }
12927             }
12928
12929             //------------------------------------------------------------
12930             // Display noData message if there's nothing to show.
12931
12932             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12933                 var noDataText = container.selectAll('.nv-noData').data([noData]);
12934
12935                 noDataText.enter().append('text')
12936                     .attr('class', 'nvd3 nv-noData')
12937                     .attr('dy', '-.7em')
12938                     .style('text-anchor', 'middle');
12939
12940                 noDataText
12941                     .attr('x', margin.left + availableWidth / 2)
12942                     .attr('y', margin.top + availableHeight / 2)
12943                     .text(function(d) { return d });
12944
12945                 renderWatch.renderEnd('scatter immediate');
12946
12947                 return chart;
12948             } else {
12949                 container.selectAll('.nv-noData').remove();
12950             }
12951
12952             //------------------------------------------------------------
12953
12954
12955             //------------------------------------------------------------
12956             // Setup Scales
12957
12958             x = scatter.xScale();
12959             y = scatter.yScale();
12960
12961             x0 = x0 || x;
12962             y0 = y0 || y;
12963
12964             //------------------------------------------------------------
12965
12966
12967             //------------------------------------------------------------
12968             // Setup containers and skeleton of chart
12969
12970             var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12971             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12972             var gEnter = wrapEnter.append('g');
12973             var g = wrap.select('g');
12974
12975             // background for pointer events
12976             gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12977
12978             gEnter.append('g').attr('class', 'nv-x nv-axis');
12979             gEnter.append('g').attr('class', 'nv-y nv-axis');
12980             gEnter.append('g').attr('class', 'nv-scatterWrap');
12981             gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12982             gEnter.append('g').attr('class', 'nv-distWrap');
12983             gEnter.append('g').attr('class', 'nv-legendWrap');
12984             gEnter.append('g').attr('class', 'nv-controlsWrap');
12985
12986             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12987
12988             if (rightAlignYAxis) {
12989                 g.select(".nv-y.nv-axis")
12990                     .attr("transform", "translate(" + availableWidth + ",0)");
12991             }
12992
12993             //------------------------------------------------------------
12994
12995
12996             //------------------------------------------------------------
12997             // Legend
12998
12999             if (showLegend) {
13000                 legend.width( availableWidth / 2 );
13001
13002                 wrap.select('.nv-legendWrap')
13003                     .datum(data)
13004                     .call(legend);
13005
13006                 if ( margin.top != legend.height()) {
13007                     margin.top = legend.height();
13008                     availableHeight = (height || parseInt(container.style('height')) || 400)
13009                         - margin.top - margin.bottom;
13010                 }
13011
13012                 wrap.select('.nv-legendWrap')
13013                     .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
13014             }
13015
13016             //------------------------------------------------------------
13017
13018
13019             //------------------------------------------------------------
13020             // Controls
13021
13022             if (showControls) {
13023                 controls.width(180).color(['#444']);
13024                 g.select('.nv-controlsWrap')
13025                     .datum(controlsData)
13026                     .attr('transform', 'translate(0,' + (-margin.top) +')')
13027                     .call(controls);
13028             }
13029
13030             //------------------------------------------------------------
13031
13032
13033             //------------------------------------------------------------
13034             // Main Chart Component(s)
13035
13036             scatter
13037                 .width(availableWidth)
13038                 .height(availableHeight)
13039                 .color(data.map(function(d,i) {
13040                     return d.color || color(d, i);
13041                 }).filter(function(d,i) { return !data[i].disabled }));
13042
13043             wrap.select('.nv-scatterWrap')
13044                 .datum(data.filter(function(d) { return !d.disabled }))
13045                 .call(scatter);
13046
13047             wrap.select('.nv-regressionLinesWrap')
13048                 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
13049
13050             var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
13051                 .data(function(d) {return d });
13052
13053             regWrap.enter().append('g').attr('class', 'nv-regLines');
13054
13055             var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
13056             var regLineEnter = regLine.enter()
13057                 .append('line').attr('class', 'nv-regLine')
13058                 .style('stroke-opacity', 0);
13059
13060             regLine
13061                 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
13062                 .attr('x1', x.range()[0])
13063                 .attr('x2', x.range()[1])
13064                 .attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
13065                 .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
13066                 .style('stroke', function(d,i,j) { return color(d,j) })
13067                 .style('stroke-opacity', function(d,i) {
13068                     return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
13069                 });
13070
13071             //------------------------------------------------------------
13072
13073
13074             //------------------------------------------------------------
13075             // Setup Axes
13076
13077             if (showXAxis) {
13078                 xAxis
13079                     .scale(x)
13080                     .ticks( xAxis.ticks() ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
13081                     .tickSize( -availableHeight , 0);
13082
13083                 g.select('.nv-x.nv-axis')
13084                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
13085                     .call(xAxis);
13086             }
13087
13088             if (showYAxis) {
13089                 yAxis
13090                     .scale(y)
13091                     .ticks( yAxis.ticks() ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
13092                     .tickSize( -availableWidth, 0);
13093
13094                 g.select('.nv-y.nv-axis')
13095                     .call(yAxis);
13096             }
13097
13098
13099             if (showDistX) {
13100                 distX
13101                     .getData(scatter.x())
13102                     .scale(x)
13103                     .width(availableWidth)
13104                     .color(data.map(function(d,i) {
13105                         return d.color || color(d, i);
13106                     }).filter(function(d,i) { return !data[i].disabled }));
13107                 gEnter.select('.nv-distWrap').append('g')
13108                     .attr('class', 'nv-distributionX');
13109                 g.select('.nv-distributionX')
13110                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
13111                     .datum(data.filter(function(d) { return !d.disabled }))
13112                     .call(distX);
13113             }
13114
13115             if (showDistY) {
13116                 distY
13117                     .getData(scatter.y())
13118                     .scale(y)
13119                     .width(availableHeight)
13120                     .color(data.map(function(d,i) {
13121                         return d.color || color(d, i);
13122                     }).filter(function(d,i) { return !data[i].disabled }));
13123                 gEnter.select('.nv-distWrap').append('g')
13124                     .attr('class', 'nv-distributionY');
13125                 g.select('.nv-distributionY')
13126                     .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
13127                     .datum(data.filter(function(d) { return !d.disabled }))
13128                     .call(distY);
13129             }
13130
13131             //------------------------------------------------------------
13132
13133
13134
13135
13136             if (d3.fisheye) {
13137                 g.select('.nv-background')
13138                     .attr('width', availableWidth)
13139                     .attr('height', availableHeight)
13140                 ;
13141
13142                 g.select('.nv-background').on('mousemove', updateFisheye);
13143                 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
13144                 scatter.dispatch.on('elementClick.freezeFisheye', function() {
13145                     pauseFisheye = !pauseFisheye;
13146                 });
13147             }
13148
13149             // At this point, everything has been selected and bound... I think
13150
13151
13152             function updateFisheye() {
13153                 if (pauseFisheye) {
13154                     g.select('.nv-point-paths').style('pointer-events', 'all');
13155                     return false;
13156                 }
13157
13158                 g.select('.nv-point-paths').style('pointer-events', 'none' );
13159
13160                 var mouse = d3.mouse(this);
13161                 x.distortion(fisheye).focus(mouse[0]);
13162                 y.distortion(fisheye).focus(mouse[1]);
13163
13164                 g.select('.nv-scatterWrap')
13165                     .datum(data.filter(function(d) { return !d.disabled }))
13166                     .call(scatter);
13167
13168                 if (showXAxis)
13169                     g.select('.nv-x.nv-axis').call(xAxis);
13170
13171                 if (showYAxis)
13172                     g.select('.nv-y.nv-axis').call(yAxis);
13173
13174                 g.select('.nv-distributionX')
13175                     .datum(data.filter(function(d) { return !d.disabled }))
13176                     .call(distX);
13177                 g.select('.nv-distributionY')
13178                     .datum(data.filter(function(d) { return !d.disabled }))
13179                     .call(distY);
13180             }
13181
13182
13183
13184             //============================================================
13185             // Event Handling/Dispatching (in chart's scope)
13186             //------------------------------------------------------------
13187
13188             controls.dispatch.on('legendClick', function(d,i) {
13189                 d.disabled = !d.disabled;
13190
13191                 fisheye = d.disabled ? 0 : 2.5;
13192                 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
13193                 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
13194
13195                 if (d.disabled) {
13196                     x.distortion(fisheye).focus(0);
13197                     y.distortion(fisheye).focus(0);
13198
13199                     g.select('.nv-scatterWrap').call(scatter);
13200                     g.select('.nv-x.nv-axis').call(xAxis);
13201                     g.select('.nv-y.nv-axis').call(yAxis);
13202                 } else {
13203                     pauseFisheye = false;
13204                 }
13205
13206                 state.fisheye = fisheye;
13207                 dispatch.stateChange(state);
13208
13209                 chart.update();
13210             });
13211
13212             legend.dispatch.on('stateChange', function(newState) {
13213                 for (var key in newState)
13214                     state[key] = newState[key];
13215                 dispatch.stateChange(state);
13216                 chart.update();
13217             });
13218
13219
13220             scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13221                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
13222                     .attr('y1', e.pos[1] - availableHeight);
13223                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
13224                     .attr('x2', e.pos[0] + distX.size());
13225
13226                 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
13227                 dispatch.tooltipShow(e);
13228             });
13229
13230             dispatch.on('tooltipShow', function(e) {
13231                 if (tooltips) showTooltip(e, that.parentNode);
13232             });
13233
13234             // Update chart from a state object passed to event handler
13235             dispatch.on('changeState', function(e) {
13236
13237                 if (typeof e.disabled !== 'undefined') {
13238                     data.forEach(function(series,i) {
13239                         series.disabled = e.disabled[i];
13240                     });
13241
13242                     state.disabled = e.disabled;
13243                 }
13244
13245                 if (typeof e.fisheye !== 'undefined') {
13246                     state.fisheye = e.fisheye;
13247                     fisheye = e.fisheye;
13248                 }
13249
13250                 chart.update();
13251             });
13252
13253             //============================================================
13254
13255
13256             //store old scales for use in transitions on update
13257             x0 = x.copy();
13258             y0 = y.copy();
13259
13260
13261         });
13262
13263         renderWatch.renderEnd('scatter with line immediate');
13264         return chart;
13265     }
13266
13267     //============================================================
13268     // Event Handling/Dispatching (out of chart's scope)
13269     //------------------------------------------------------------
13270
13271     scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13272         dispatch.tooltipHide(e);
13273
13274         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
13275             .attr('y1', 0);
13276         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
13277             .attr('x2', distY.size());
13278     });
13279     dispatch.on('tooltipHide', function() {
13280         if (tooltips) nv.tooltip.cleanup();
13281     });
13282
13283     //============================================================
13284
13285
13286     //============================================================
13287     // Expose Public Variables
13288     //------------------------------------------------------------
13289
13290     // expose chart's sub-components
13291     chart.dispatch = dispatch;
13292     chart.scatter = scatter;
13293     chart.legend = legend;
13294     chart.controls = controls;
13295     chart.xAxis = xAxis;
13296     chart.yAxis = yAxis;
13297     chart.distX = distX;
13298     chart.distY = distY;
13299
13300     // DO NOT DELETE. This is currently overridden below
13301     // until deprecated portions are removed.
13302     chart.state = state;
13303
13304     d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
13305
13306     chart.options = nv.utils.optionsFunc.bind(chart);
13307
13308     chart.margin = function(_) {
13309         if (!arguments.length) return margin;
13310         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13311         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13312         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13313         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13314         return chart;
13315     };
13316
13317     chart.width = function(_) {
13318         if (!arguments.length) return width;
13319         width = _;
13320         return chart;
13321     };
13322
13323     chart.height = function(_) {
13324         if (!arguments.length) return height;
13325         height = _;
13326         return chart;
13327     };
13328
13329     chart.color = function(_) {
13330         if (!arguments.length) return color;
13331         color = nv.utils.getColor(_);
13332         legend.color(color);
13333         distX.color(color);
13334         distY.color(color);
13335         return chart;
13336     };
13337
13338     chart.showDistX = function(_) {
13339         if (!arguments.length) return showDistX;
13340         showDistX = _;
13341         return chart;
13342     };
13343
13344     chart.showDistY = function(_) {
13345         if (!arguments.length) return showDistY;
13346         showDistY = _;
13347         return chart;
13348     };
13349
13350     chart.showControls = function(_) {
13351         if (!arguments.length) return showControls;
13352         showControls = _;
13353         return chart;
13354     };
13355
13356     chart.showLegend = function(_) {
13357         if (!arguments.length) return showLegend;
13358         showLegend = _;
13359         return chart;
13360     };
13361
13362     chart.showXAxis = function(_) {
13363         if (!arguments.length) return showXAxis;
13364         showXAxis = _;
13365         return chart;
13366     };
13367
13368     chart.showYAxis = function(_) {
13369         if (!arguments.length) return showYAxis;
13370         showYAxis = _;
13371         return chart;
13372     };
13373
13374     chart.rightAlignYAxis = function(_) {
13375         if(!arguments.length) return rightAlignYAxis;
13376         rightAlignYAxis = _;
13377         yAxis.orient( (_) ? 'right' : 'left');
13378         return chart;
13379     };
13380
13381     chart.fisheye = function(_) {
13382         if (!arguments.length) return fisheye;
13383         fisheye = _;
13384         return chart;
13385     };
13386
13387     chart.tooltips = function(_) {
13388         if (!arguments.length) return tooltips;
13389         tooltips = _;
13390         return chart;
13391     };
13392
13393     chart.tooltipContent = function(_) {
13394         if (!arguments.length) return tooltip;
13395         tooltip = _;
13396         return chart;
13397     };
13398
13399     chart.tooltipXContent = function(_) {
13400         if (!arguments.length) return tooltipX;
13401         tooltipX = _;
13402         return chart;
13403     };
13404
13405     chart.tooltipYContent = function(_) {
13406         if (!arguments.length) return tooltipY;
13407         tooltipY = _;
13408         return chart;
13409     };
13410
13411     // DEPRECATED
13412     chart.state = function(_) {
13413         nv.deprecated('scatterPlusLineChart.state');
13414         if (!arguments.length) return state;
13415         state = _;
13416         return chart;
13417     };
13418     for (var key in state) {
13419         chart.state[key] = state[key];
13420     }
13421     // END DEPRECATED
13422
13423     chart.defaultState = function(_) {
13424         if (!arguments.length) return defaultState;
13425         defaultState = _;
13426         return chart;
13427     };
13428
13429     chart.noData = function(_) {
13430         if (!arguments.length) return noData;
13431         noData = _;
13432         return chart;
13433     };
13434
13435     chart.transitionDuration = function(_) {
13436         nv.deprecated('scatterPlusLineChart.transitionDuration');
13437         return chart.duration(_);
13438     };
13439
13440     chart.duration = function(_) {
13441         if (!arguments.length) return duration;
13442         duration = _;
13443         return chart;
13444     };
13445
13446     //============================================================
13447
13448
13449     return chart;
13450 };
13451
13452 nv.models.sparkline = function() {
13453     "use strict";
13454     //============================================================
13455     // Public Variables with Default Settings
13456     //------------------------------------------------------------
13457
13458     var margin = {top: 2, right: 0, bottom: 2, left: 0}
13459         , width = 400
13460         , height = 32
13461         , animate = true
13462         , x = d3.scale.linear()
13463         , y = d3.scale.linear()
13464         , getX = function(d) { return d.x }
13465         , getY = function(d) { return d.y }
13466         , color = nv.utils.getColor(['#000'])
13467         , xDomain
13468         , yDomain
13469         , xRange
13470         , yRange
13471         ;
13472
13473     //============================================================
13474
13475
13476     function chart(selection) {
13477         selection.each(function(data) {
13478             var availableWidth = width - margin.left - margin.right,
13479                 availableHeight = height - margin.top - margin.bottom,
13480                 container = d3.select(this);
13481             nv.utils.initSVG(container);
13482
13483             //------------------------------------------------------------
13484             // Setup Scales
13485
13486             x   .domain(xDomain || d3.extent(data, getX ))
13487                 .range(xRange || [0, availableWidth]);
13488
13489             y   .domain(yDomain || d3.extent(data, getY ))
13490                 .range(yRange || [availableHeight, 0]);
13491
13492             //------------------------------------------------------------
13493
13494
13495             //------------------------------------------------------------
13496             // Setup containers and skeleton of chart
13497
13498             var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
13499             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
13500             var gEnter = wrapEnter.append('g');
13501             var g = wrap.select('g');
13502
13503             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
13504
13505             //------------------------------------------------------------
13506
13507
13508             var paths = wrap.selectAll('path')
13509                 .data(function(d) { return [d] });
13510             paths.enter().append('path');
13511             paths.exit().remove();
13512             paths
13513                 .style('stroke', function(d,i) { return d.color || color(d, i) })
13514                 .attr('d', d3.svg.line()
13515                     .x(function(d,i) { return x(getX(d,i)) })
13516                     .y(function(d,i) { return y(getY(d,i)) })
13517             );
13518
13519
13520             // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
13521             var points = wrap.selectAll('circle.nv-point')
13522                 .data(function(data) {
13523                     var yValues = data.map(function(d, i) { return getY(d,i); });
13524                     function pointIndex(index) {
13525                         if (index != -1) {
13526                             var result = data[index];
13527                             result.pointIndex = index;
13528                             return result;
13529                         } else {
13530                             return null;
13531                         }
13532                     }
13533                     var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
13534                         minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
13535                         currentPoint = pointIndex(yValues.length - 1);
13536                     return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
13537                 });
13538             points.enter().append('circle');
13539             points.exit().remove();
13540             points
13541                 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
13542                 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
13543                 .attr('r', 2)
13544                 .attr('class', function(d,i) {
13545                     return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
13546                             getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
13547                 });
13548         });
13549
13550         return chart;
13551     }
13552
13553
13554     //============================================================
13555     // Expose Public Variables
13556     //------------------------------------------------------------
13557     chart.options = nv.utils.optionsFunc.bind(chart);
13558
13559     chart.margin = function(_) {
13560         if (!arguments.length) return margin;
13561         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13562         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13563         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13564         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13565         return chart;
13566     };
13567
13568     chart.width = function(_) {
13569         if (!arguments.length) return width;
13570         width = _;
13571         return chart;
13572     };
13573
13574     chart.height = function(_) {
13575         if (!arguments.length) return height;
13576         height = _;
13577         return chart;
13578     };
13579
13580     chart.x = function(_) {
13581         if (!arguments.length) return getX;
13582         getX = d3.functor(_);
13583         return chart;
13584     };
13585
13586     chart.y = function(_) {
13587         if (!arguments.length) return getY;
13588         getY = d3.functor(_);
13589         return chart;
13590     };
13591
13592     chart.xScale = function(_) {
13593         if (!arguments.length) return x;
13594         x = _;
13595         return chart;
13596     };
13597
13598     chart.yScale = function(_) {
13599         if (!arguments.length) return y;
13600         y = _;
13601         return chart;
13602     };
13603
13604     chart.xDomain = function(_) {
13605         if (!arguments.length) return xDomain;
13606         xDomain = _;
13607         return chart;
13608     };
13609
13610     chart.yDomain = function(_) {
13611         if (!arguments.length) return yDomain;
13612         yDomain = _;
13613         return chart;
13614     };
13615
13616     chart.xRange = function(_) {
13617         if (!arguments.length) return xRange;
13618         xRange = _;
13619         return chart;
13620     };
13621
13622     chart.yRange = function(_) {
13623         if (!arguments.length) return yRange;
13624         yRange = _;
13625         return chart;
13626     };
13627
13628     chart.animate = function(_) {
13629         if (!arguments.length) return animate;
13630         animate = _;
13631         return chart;
13632     };
13633
13634     chart.color = function(_) {
13635         if (!arguments.length) return color;
13636         color = nv.utils.getColor(_);
13637         return chart;
13638     };
13639
13640     //============================================================
13641
13642
13643     return chart;
13644 }
13645
13646 nv.models.sparklinePlus = function() {
13647     "use strict";
13648     //============================================================
13649     // Public Variables with Default Settings
13650     //------------------------------------------------------------
13651
13652     var sparkline = nv.models.sparkline();
13653
13654     var margin = {top: 15, right: 100, bottom: 10, left: 50}
13655         , width = null
13656         , height = null
13657         , x
13658         , y
13659         , index = []
13660         , paused = false
13661         , xTickFormat = d3.format(',r')
13662         , yTickFormat = d3.format(',.2f')
13663         , showValue = true
13664         , alignValue = true
13665         , rightAlignValue = false
13666         , noData = "No Data Available."
13667         ;
13668
13669     //============================================================
13670
13671
13672     function chart(selection) {
13673         selection.each(function(data) {
13674             var container = d3.select(this);
13675             nv.utils.initSVG(container);
13676
13677             var availableWidth = (width  || parseInt(container.style('width')) || 960)
13678                     - margin.left - margin.right,
13679                 availableHeight = (height || parseInt(container.style('height')) || 400)
13680                     - margin.top - margin.bottom;
13681
13682
13683
13684             chart.update = function() { chart(selection) };
13685             chart.container = this;
13686
13687
13688             //------------------------------------------------------------
13689             // Display No Data message if there's nothing to show.
13690
13691             if (!data || !data.length) {
13692                 var noDataText = container.selectAll('.nv-noData').data([noData]);
13693
13694                 noDataText.enter().append('text')
13695                     .attr('class', 'nvd3 nv-noData')
13696                     .attr('dy', '-.7em')
13697                     .style('text-anchor', 'middle');
13698
13699                 noDataText
13700                     .attr('x', margin.left + availableWidth / 2)
13701                     .attr('y', margin.top + availableHeight / 2)
13702                     .text(function(d) { return d });
13703
13704                 return chart;
13705             } else {
13706                 container.selectAll('.nv-noData').remove();
13707             }
13708
13709             var currentValue = sparkline.y()(data[data.length-1], data.length-1);
13710
13711             //------------------------------------------------------------
13712
13713
13714
13715             //------------------------------------------------------------
13716             // Setup Scales
13717
13718             x = sparkline.xScale();
13719             y = sparkline.yScale();
13720
13721             //------------------------------------------------------------
13722
13723
13724             //------------------------------------------------------------
13725             // Setup containers and skeleton of chart
13726
13727             var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
13728             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
13729             var gEnter = wrapEnter.append('g');
13730             var g = wrap.select('g');
13731
13732             gEnter.append('g').attr('class', 'nv-sparklineWrap');
13733             gEnter.append('g').attr('class', 'nv-valueWrap');
13734             gEnter.append('g').attr('class', 'nv-hoverArea');
13735
13736             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13737
13738             //------------------------------------------------------------
13739
13740
13741             //------------------------------------------------------------
13742             // Main Chart Component(s)
13743
13744             var sparklineWrap = g.select('.nv-sparklineWrap');
13745
13746             sparkline
13747                 .width(availableWidth)
13748                 .height(availableHeight);
13749
13750             sparklineWrap
13751                 .call(sparkline);
13752
13753             //------------------------------------------------------------
13754
13755
13756             var valueWrap = g.select('.nv-valueWrap');
13757
13758             var value = valueWrap.selectAll('.nv-currentValue')
13759                 .data([currentValue]);
13760
13761             value.enter().append('text').attr('class', 'nv-currentValue')
13762                 .attr('dx', rightAlignValue ? -8 : 8)
13763                 .attr('dy', '.9em')
13764                 .style('text-anchor', rightAlignValue ? 'end' : 'start');
13765
13766             value
13767                 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13768                 .attr('y', alignValue ? function(d) { return y(d) } : 0)
13769                 .style('fill', sparkline.color()(data[data.length-1], data.length-1))
13770                 .text(yTickFormat(currentValue));
13771
13772
13773
13774             gEnter.select('.nv-hoverArea').append('rect')
13775                 .on('mousemove', sparklineHover)
13776                 .on('click', function() { paused = !paused })
13777                 .on('mouseout', function() { index = []; updateValueLine(); });
13778             //.on('mouseout', function() { index = null; updateValueLine(); });
13779
13780             g.select('.nv-hoverArea rect')
13781                 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13782                 .attr('width', availableWidth + margin.left + margin.right)
13783                 .attr('height', availableHeight + margin.top);
13784
13785
13786
13787             function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13788                 if (paused) return;
13789
13790                 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13791
13792                 var hoverEnter = hoverValue.enter()
13793                     .append('g').attr('class', 'nv-hoverValue')
13794                     .style('stroke-opacity', 0)
13795                     .style('fill-opacity', 0);
13796
13797                 hoverValue.exit()
13798                     .transition().duration(250)
13799                     .style('stroke-opacity', 0)
13800                     .style('fill-opacity', 0)
13801                     .remove();
13802
13803                 hoverValue
13804                     .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13805                     .transition().duration(250)
13806                     .style('stroke-opacity', 1)
13807                     .style('fill-opacity', 1);
13808
13809                 if (!index.length) return;
13810
13811                 hoverEnter.append('line')
13812                     .attr('x1', 0)
13813                     .attr('y1', -margin.top)
13814                     .attr('x2', 0)
13815                     .attr('y2', availableHeight);
13816
13817
13818                 hoverEnter.append('text').attr('class', 'nv-xValue')
13819                     .attr('x', -6)
13820                     .attr('y', -margin.top)
13821                     .attr('text-anchor', 'end')
13822                     .attr('dy', '.9em')
13823
13824
13825                 g.select('.nv-hoverValue .nv-xValue')
13826                     .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13827
13828                 hoverEnter.append('text').attr('class', 'nv-yValue')
13829                     .attr('x', 6)
13830                     .attr('y', -margin.top)
13831                     .attr('text-anchor', 'start')
13832                     .attr('dy', '.9em')
13833
13834                 g.select('.nv-hoverValue .nv-yValue')
13835                     .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13836
13837             }
13838
13839
13840             function sparklineHover() {
13841                 if (paused) return;
13842
13843                 var pos = d3.mouse(this)[0] - margin.left;
13844
13845                 function getClosestIndex(data, x) {
13846                     var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13847                     var closestIndex = 0;
13848                     for (var i = 0; i < data.length; i++){
13849                         if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13850                             distance = Math.abs(sparkline.x()(data[i], i) - x);
13851                             closestIndex = i;
13852                         }
13853                     }
13854                     return closestIndex;
13855                 }
13856
13857                 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13858
13859                 updateValueLine();
13860             }
13861
13862         });
13863
13864         return chart;
13865     }
13866
13867
13868     //============================================================
13869     // Expose Public Variables
13870     //------------------------------------------------------------
13871
13872     // expose chart's sub-components
13873     chart.sparkline = sparkline;
13874
13875     d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13876
13877     chart.options = nv.utils.optionsFunc.bind(chart);
13878
13879     chart.margin = function(_) {
13880         if (!arguments.length) return margin;
13881         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13882         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13883         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13884         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13885         return chart;
13886     };
13887
13888     chart.width = function(_) {
13889         if (!arguments.length) return width;
13890         width = _;
13891         return chart;
13892     };
13893
13894     chart.height = function(_) {
13895         if (!arguments.length) return height;
13896         height = _;
13897         return chart;
13898     };
13899
13900     chart.xTickFormat = function(_) {
13901         if (!arguments.length) return xTickFormat;
13902         xTickFormat = _;
13903         return chart;
13904     };
13905
13906     chart.yTickFormat = function(_) {
13907         if (!arguments.length) return yTickFormat;
13908         yTickFormat = _;
13909         return chart;
13910     };
13911
13912     chart.showValue = function(_) {
13913         if (!arguments.length) return showValue;
13914         showValue = _;
13915         return chart;
13916     };
13917
13918     chart.alignValue = function(_) {
13919         if (!arguments.length) return alignValue;
13920         alignValue = _;
13921         return chart;
13922     };
13923
13924     chart.rightAlignValue = function(_) {
13925         if (!arguments.length) return rightAlignValue;
13926         rightAlignValue = _;
13927         return chart;
13928     };
13929
13930     chart.noData = function(_) {
13931         if (!arguments.length) return noData;
13932         noData = _;
13933         return chart;
13934     };
13935
13936     //============================================================
13937
13938
13939     return chart;
13940 }
13941
13942 nv.models.stackedArea = function() {
13943     "use strict";
13944     //============================================================
13945     // Public Variables with Default Settings
13946     //------------------------------------------------------------
13947
13948     var margin = {top: 0, right: 0, bottom: 0, left: 0}
13949         , width = 960
13950         , height = 500
13951         , color = nv.utils.defaultColor() // a function that computes the color
13952         , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13953         , getX = function(d) { return d.x } // accessor to get the x value from a data point
13954         , getY = function(d) { return d.y } // accessor to get the y value from a data point
13955         , style = 'stack'
13956         , offset = 'zero'
13957         , order = 'default'
13958         , interpolate = 'linear'  // controls the line interpolation
13959         , clipEdge = false // if true, masks lines within x and y scale
13960         , x //can be accessed via chart.xScale()
13961         , y //can be accessed via chart.yScale()
13962         , scatter = nv.models.scatter()
13963         , duration = 250
13964         , dispatch =  d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout','renderEnd')
13965         ;
13966
13967     // scatter is interactive by default, but this chart isn't so must disable
13968     scatter.interactive(false);
13969
13970     scatter
13971         .size(2.2) // default size
13972         .sizeDomain([2.2,2.2]) // all the same size by default
13973     ;
13974
13975     /************************************
13976      * offset:
13977      *   'wiggle' (stream)
13978      *   'zero' (stacked)
13979      *   'expand' (normalize to 100%)
13980      *   'silhouette' (simple centered)
13981      *
13982      * order:
13983      *   'inside-out' (stream)
13984      *   'default' (input order)
13985      ************************************/
13986
13987     //============================================================
13988     var renderWatch = nv.utils.renderWatch(dispatch, duration);
13989
13990
13991     function chart(selection) {
13992         renderWatch.reset();
13993         renderWatch.models(scatter);
13994         selection.each(function(data) {
13995             var availableWidth = width - margin.left - margin.right,
13996                 availableHeight = height - margin.top - margin.bottom,
13997                 container = d3.select(this);
13998             nv.utils.initSVG(container);
13999
14000             //------------------------------------------------------------
14001             // Setup Scales
14002
14003             x = scatter.xScale();
14004             y = scatter.yScale();
14005
14006             //------------------------------------------------------------
14007
14008             var dataRaw = data;
14009             // Injecting point index into each point because d3.layout.stack().out does not give index
14010             data.forEach(function(aseries, i) {
14011                 aseries.seriesIndex = i;
14012                 aseries.values = aseries.values.map(function(d, j) {
14013                     d.index = j;
14014                     d.seriesIndex = i;
14015                     return d;
14016                 });
14017             });
14018
14019             var dataFiltered = data.filter(function(series) {
14020                 return !series.disabled;
14021             });
14022
14023             data = d3.layout.stack()
14024                 .order(order)
14025                 .offset(offset)
14026                 .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
14027                 .x(getX)
14028                 .y(getY)
14029                 .out(function(d, y0, y) {
14030                     var yHeight = (getY(d) === 0) ? 0 : y;
14031                     d.display = {
14032                         y: yHeight,
14033                         y0: y0
14034                     };
14035                 })
14036             (dataFiltered);
14037
14038
14039             //------------------------------------------------------------
14040             // Setup containers and skeleton of chart
14041
14042             var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
14043             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
14044             var defsEnter = wrapEnter.append('defs');
14045             var gEnter = wrapEnter.append('g');
14046             var g = wrap.select('g');
14047
14048             gEnter.append('g').attr('class', 'nv-areaWrap');
14049             gEnter.append('g').attr('class', 'nv-scatterWrap');
14050
14051             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
14052
14053             //------------------------------------------------------------
14054
14055
14056             scatter
14057                 .width(availableWidth)
14058                 .height(availableHeight)
14059                 .x(getX)
14060                 .y(function(d) { return d.display.y + d.display.y0 })
14061                 .forceY([0])
14062                 .color(data.map(function(d,i) {
14063                     return d.color || color(d, d.seriesIndex);
14064                 }));
14065
14066
14067             var scatterWrap = g.select('.nv-scatterWrap')
14068                 .datum(data);
14069
14070             scatterWrap.call(scatter);
14071
14072             defsEnter.append('clipPath')
14073                 .attr('id', 'nv-edge-clip-' + id)
14074                 .append('rect');
14075
14076             wrap.select('#nv-edge-clip-' + id + ' rect')
14077                 .attr('width', availableWidth)
14078                 .attr('height', availableHeight);
14079
14080             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
14081
14082             var area = d3.svg.area()
14083                 .x(function(d,i)  { return x(getX(d,i)) })
14084                 .y0(function(d) {
14085                     return y(d.display.y0)
14086                 })
14087                 .y1(function(d) {
14088                     return y(d.display.y + d.display.y0)
14089                 })
14090                 .interpolate(interpolate);
14091
14092             var zeroArea = d3.svg.area()
14093                 .x(function(d,i)  { return x(getX(d,i)) })
14094                 .y0(function(d) { return y(d.display.y0) })
14095                 .y1(function(d) { return y(d.display.y0) });
14096
14097
14098             var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
14099                 .data(function(d) { return d });
14100
14101             path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
14102                 .attr('d', function(d,i){
14103                     return zeroArea(d.values, d.seriesIndex);
14104                 })
14105                 .on('mouseover', function(d,i) {
14106                     d3.select(this).classed('hover', true);
14107                     dispatch.areaMouseover({
14108                         point: d,
14109                         series: d.key,
14110                         pos: [d3.event.pageX, d3.event.pageY],
14111                         seriesIndex: d.seriesIndex
14112                     });
14113                 })
14114                 .on('mouseout', function(d,i) {
14115                     d3.select(this).classed('hover', false);
14116                     dispatch.areaMouseout({
14117                         point: d,
14118                         series: d.key,
14119                         pos: [d3.event.pageX, d3.event.pageY],
14120                         seriesIndex: d.seriesIndex
14121                     });
14122                 })
14123                 .on('click', function(d,i) {
14124                     d3.select(this).classed('hover', false);
14125                     dispatch.areaClick({
14126                         point: d,
14127                         series: d.key,
14128                         pos: [d3.event.pageX, d3.event.pageY],
14129                         seriesIndex: d.seriesIndex
14130                     });
14131                 })
14132
14133             path.exit().remove();
14134
14135             path
14136                 .style('fill', function(d,i){
14137                     return d.color || color(d, d.seriesIndex)
14138                 })
14139                 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
14140             path.watchTransition(renderWatch,'stackedArea path')
14141                 .attr('d', function(d,i) {
14142                     return area(d.values,i)
14143                 });
14144
14145
14146
14147             //============================================================
14148             // Event Handling/Dispatching (in chart's scope)
14149             //------------------------------------------------------------
14150
14151             scatter.dispatch.on('elementMouseover.area', function(e) {
14152                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
14153             });
14154             scatter.dispatch.on('elementMouseout.area', function(e) {
14155                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
14156             });
14157
14158             //============================================================
14159             //Special offset functions
14160             chart.d3_stackedOffset_stackPercent = function(stackData) {
14161                 var n = stackData.length,    //How many series
14162                     m = stackData[0].length,     //how many points per series
14163                     k = 1 / n,
14164                     i,
14165                     j,
14166                     o,
14167                     y0 = [];
14168
14169                 for (j = 0; j < m; ++j) { //Looping through all points
14170                     for (i = 0, o = 0; i < dataRaw.length; i++)  //looping through series'
14171                         o += getY(dataRaw[i].values[j])   //total value of all points at a certian point in time.
14172
14173                     if (o) for (i = 0; i < n; i++)
14174                         stackData[i][j][1] /= o;
14175                     else
14176                         for (i = 0; i < n; i++)
14177                             stackData[i][j][1] = k;
14178                 }
14179                 for (j = 0; j < m; ++j) y0[j] = 0;
14180                 return y0;
14181             };
14182
14183         });
14184
14185         renderWatch.renderEnd('stackedArea immediate');
14186         return chart;
14187     }
14188
14189
14190     //============================================================
14191     // Event Handling/Dispatching (out of chart's scope)
14192     //------------------------------------------------------------
14193
14194     scatter.dispatch.on('elementClick.area', function(e) {
14195         dispatch.areaClick(e);
14196     })
14197     scatter.dispatch.on('elementMouseover.tooltip', function(e) {
14198         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
14199             dispatch.tooltipShow(e);
14200     });
14201     scatter.dispatch.on('elementMouseout.tooltip', function(e) {
14202         dispatch.tooltipHide(e);
14203     });
14204
14205     //============================================================
14206
14207     //============================================================
14208     // Global getters and setters
14209     //------------------------------------------------------------
14210
14211     chart.dispatch = dispatch;
14212     chart.scatter = scatter;
14213
14214     d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
14215         'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
14216
14217     chart.options = nv.utils.optionsFunc.bind(chart);
14218
14219     chart.x = function(_) {
14220         if (!arguments.length) return getX;
14221         getX = d3.functor(_);
14222         return chart;
14223     };
14224
14225     chart.y = function(_) {
14226         if (!arguments.length) return getY;
14227         getY = d3.functor(_);
14228         return chart;
14229     }
14230
14231     chart.margin = function(_) {
14232         if (!arguments.length) return margin;
14233         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
14234         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
14235         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14236         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
14237         return chart;
14238     };
14239
14240     chart.width = function(_) {
14241         if (!arguments.length) return width;
14242         width = _;
14243         return chart;
14244     };
14245
14246     chart.height = function(_) {
14247         if (!arguments.length) return height;
14248         height = _;
14249         return chart;
14250     };
14251
14252     chart.clipEdge = function(_) {
14253         if (!arguments.length) return clipEdge;
14254         clipEdge = _;
14255         return chart;
14256     };
14257
14258     chart.color = function(_) {
14259         if (!arguments.length) return color;
14260         color = nv.utils.getColor(_);
14261         return chart;
14262     };
14263
14264     chart.offset = function(_) {
14265         if (!arguments.length) return offset;
14266         offset = _;
14267         return chart;
14268     };
14269
14270     chart.order = function(_) {
14271         if (!arguments.length) return order;
14272         order = _;
14273         return chart;
14274     };
14275
14276     //shortcut for offset + order
14277     chart.style = function(_) {
14278         if (!arguments.length) return style;
14279         style = _;
14280
14281         switch (style) {
14282             case 'stack':
14283                 chart.offset('zero');
14284                 chart.order('default');
14285                 break;
14286             case 'stream':
14287                 chart.offset('wiggle');
14288                 chart.order('inside-out');
14289                 break;
14290             case 'stream-center':
14291                 chart.offset('silhouette');
14292                 chart.order('inside-out');
14293                 break;
14294             case 'expand':
14295                 chart.offset('expand');
14296                 chart.order('default');
14297                 break;
14298             case 'stack_percent':
14299                 chart.offset(chart.d3_stackedOffset_stackPercent);
14300                 chart.order('default');
14301                 break;
14302         }
14303
14304         return chart;
14305     };
14306
14307     chart.interpolate = function(_) {
14308         if (!arguments.length) return interpolate;
14309         interpolate = _;
14310         return chart;
14311     };
14312
14313     chart.duration = function(_) {
14314         if (!arguments.length) return duration;
14315         duration = _;
14316         renderWatch.reset(duration);
14317         scatter.duration(duration);
14318         return chart;
14319     };
14320
14321     //============================================================
14322
14323
14324     return chart;
14325 }
14326
14327 nv.models.stackedAreaChart = function() {
14328     "use strict";
14329     //============================================================
14330     // Public Variables with Default Settings
14331     //------------------------------------------------------------
14332
14333     var stacked = nv.models.stackedArea()
14334         , xAxis = nv.models.axis()
14335         , yAxis = nv.models.axis()
14336         , legend = nv.models.legend()
14337         , controls = nv.models.legend()
14338         , interactiveLayer = nv.interactiveGuideline()
14339         ;
14340
14341     var margin = {top: 30, right: 25, bottom: 50, left: 60}
14342         , width = null
14343         , height = null
14344         , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
14345         , showControls = true
14346         , showLegend = true
14347         , showXAxis = true
14348         , showYAxis = true
14349         , rightAlignYAxis = false
14350         , useInteractiveGuideline = false
14351         , tooltips = true
14352         , tooltip = function(key, x, y, e, graph) {
14353             return '<h3>' + key + '</h3>' +
14354                 '<p>' +  y + ' on ' + x + '</p>'
14355         }
14356         , x //can be accessed via chart.xScale()
14357         , y //can be accessed via chart.yScale()
14358         , yAxisTickFormat = d3.format(',.2f')
14359         , state = nv.utils.state()
14360         , defaultState = null
14361         , noData = 'No Data Available.'
14362         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
14363         , controlWidth = 250
14364         , cData = ['Stacked','Stream','Expanded']
14365         , controlLabels = {}
14366         , duration = 250
14367         ;
14368
14369     state.style = stacked.style();
14370
14371     xAxis
14372         .orient('bottom')
14373         .tickPadding(7)
14374     ;
14375     yAxis
14376         .orient((rightAlignYAxis) ? 'right' : 'left')
14377     ;
14378
14379     controls.updateState(false);
14380     //============================================================
14381
14382
14383     //============================================================
14384     // Private Variables
14385     //------------------------------------------------------------
14386     var renderWatch = nv.utils.renderWatch(dispatch);
14387     var style = stacked.style();
14388
14389     var showTooltip = function(e, offsetElement) {
14390         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
14391             top = e.pos[1] + ( offsetElement.offsetTop || 0),
14392             x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
14393             y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
14394             content = tooltip(e.series.key, x, y, e, chart);
14395
14396         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
14397     };
14398
14399     var stateGetter = function(data) {
14400         return function(){
14401             return {
14402                 active: data.map(function(d) { return !d.disabled }),
14403                 style: stacked.style()
14404             };
14405         }
14406     };
14407
14408     var stateSetter = function(data) {
14409         return function(state) {
14410             if (state.style !== undefined)
14411                 style = state.style;
14412             if (state.active !== undefined)
14413                 data.forEach(function(series,i) {
14414                     series.disabled = !state.active[i];
14415                 });
14416         }
14417     };
14418
14419     //============================================================
14420
14421
14422     function chart(selection) {
14423         renderWatch.reset();
14424         renderWatch.models(stacked);
14425         if (showXAxis) renderWatch.models(xAxis);
14426         if (showYAxis) renderWatch.models(yAxis);
14427
14428         selection.each(function(data) {
14429             var container = d3.select(this),
14430                 that = this;
14431             nv.utils.initSVG(container);
14432
14433             var availableWidth = (width  || parseInt(container.style('width')) || 960)
14434                     - margin.left - margin.right,
14435                 availableHeight = (height || parseInt(container.style('height')) || 400)
14436                     - margin.top - margin.bottom;
14437
14438             chart.update = function() { container.transition().duration(duration).call(chart); };
14439             chart.container = this;
14440
14441             state
14442                 .setter(stateSetter(data), chart.update)
14443                 .getter(stateGetter(data))
14444                 .update();
14445
14446             // DEPRECATED set state.disabled
14447             state.disabled = data.map(function(d) { return !!d.disabled });
14448
14449             if (!defaultState) {
14450                 var key;
14451                 defaultState = {};
14452                 for (key in state) {
14453                     if (state[key] instanceof Array)
14454                         defaultState[key] = state[key].slice(0);
14455                     else
14456                         defaultState[key] = state[key];
14457                 }
14458             }
14459
14460             //------------------------------------------------------------
14461             // Display No Data message if there's nothing to show.
14462
14463             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
14464                 var noDataText = container.selectAll('.nv-noData').data([noData]);
14465
14466                 noDataText.enter().append('text')
14467                     .attr('class', 'nvd3 nv-noData')
14468                     .attr('dy', '-.7em')
14469                     .style('text-anchor', 'middle');
14470
14471                 noDataText
14472                     .attr('x', margin.left + availableWidth / 2)
14473                     .attr('y', margin.top + availableHeight / 2)
14474                     .text(function(d) { return d });
14475
14476                 return chart;
14477             } else {
14478                 container.selectAll('.nv-noData').remove();
14479             }
14480
14481             //------------------------------------------------------------
14482
14483
14484             //------------------------------------------------------------
14485             // Setup Scales
14486
14487             x = stacked.xScale();
14488             y = stacked.yScale();
14489
14490             //------------------------------------------------------------
14491
14492
14493             //------------------------------------------------------------
14494             // Setup containers and skeleton of chart
14495
14496             var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
14497             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
14498             var g = wrap.select('g');
14499
14500             gEnter.append("rect").style("opacity",0);
14501             gEnter.append('g').attr('class', 'nv-x nv-axis');
14502             gEnter.append('g').attr('class', 'nv-y nv-axis');
14503             gEnter.append('g').attr('class', 'nv-stackedWrap');
14504             gEnter.append('g').attr('class', 'nv-legendWrap');
14505             gEnter.append('g').attr('class', 'nv-controlsWrap');
14506             gEnter.append('g').attr('class', 'nv-interactive');
14507
14508             g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
14509             //------------------------------------------------------------
14510             // Legend
14511
14512             if (showLegend) {
14513                 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
14514                 legend
14515                     .width(legendWidth);
14516
14517                 g.select('.nv-legendWrap')
14518                     .datum(data)
14519                     .call(legend);
14520
14521                 if ( margin.top != legend.height()) {
14522                     margin.top = legend.height();
14523                     availableHeight = (height || parseInt(container.style('height')) || 400)
14524                         - margin.top - margin.bottom;
14525                 }
14526
14527                 g.select('.nv-legendWrap')
14528                     .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
14529             }
14530
14531             //------------------------------------------------------------
14532
14533
14534             //------------------------------------------------------------
14535             // Controls
14536
14537             if (showControls) {
14538                 var controlsData = [
14539                     {
14540                         key: controlLabels.stacked || 'Stacked',
14541                         metaKey: 'Stacked',
14542                         disabled: stacked.style() != 'stack',
14543                         style: 'stack'
14544                     },
14545                     {
14546                         key: controlLabels.stream || 'Stream',
14547                         metaKey: 'Stream',
14548                         disabled: stacked.style() != 'stream',
14549                         style: 'stream'
14550                     },
14551                     {
14552                         key: controlLabels.expanded || 'Expanded',
14553                         metaKey: 'Expanded',
14554                         disabled: stacked.style() != 'expand',
14555                         style: 'expand'
14556                     },
14557                     {
14558                         key: controlLabels.stack_percent || 'Stack %',
14559                         metaKey: 'Stack_Percent',
14560                         disabled: stacked.style() != 'stack_percent',
14561                         style: 'stack_percent'
14562                     }
14563                 ];
14564
14565                 controlWidth = (cData.length/3) * 260;
14566
14567                 controlsData = controlsData.filter(function(d) {
14568                     return cData.indexOf(d.metaKey) !== -1;
14569                 });
14570
14571                 controls
14572                     .width( controlWidth )
14573                     .color(['#444', '#444', '#444']);
14574
14575                 g.select('.nv-controlsWrap')
14576                     .datum(controlsData)
14577                     .call(controls);
14578
14579
14580                 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
14581                     margin.top = Math.max(controls.height(), legend.height());
14582                     availableHeight = (height || parseInt(container.style('height')) || 400)
14583                         - margin.top - margin.bottom;
14584                 }
14585
14586
14587                 g.select('.nv-controlsWrap')
14588                     .attr('transform', 'translate(0,' + (-margin.top) +')');
14589             }
14590
14591             //------------------------------------------------------------
14592
14593
14594             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
14595
14596             if (rightAlignYAxis) {
14597                 g.select(".nv-y.nv-axis")
14598                     .attr("transform", "translate(" + availableWidth + ",0)");
14599             }
14600
14601             //------------------------------------------------------------
14602             // Main Chart Component(s)
14603
14604             //------------------------------------------------------------
14605             //Set up interactive layer
14606             if (useInteractiveGuideline) {
14607                 interactiveLayer
14608                     .width(availableWidth)
14609                     .height(availableHeight)
14610                     .margin({left: margin.left, top: margin.top})
14611                     .svgContainer(container)
14612                     .xScale(x);
14613                 wrap.select(".nv-interactive").call(interactiveLayer);
14614             }
14615
14616             stacked
14617                 .width(availableWidth)
14618                 .height(availableHeight);
14619
14620             var stackedWrap = g.select('.nv-stackedWrap')
14621                 .datum(data);
14622
14623             stackedWrap.transition().call(stacked);
14624
14625             //------------------------------------------------------------
14626
14627
14628             //------------------------------------------------------------
14629             // Setup Axes
14630
14631             if (showXAxis) {
14632                 xAxis
14633                     .scale(x)
14634                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
14635                     .tickSize( -availableHeight, 0);
14636
14637                 g.select('.nv-x.nv-axis')
14638                     .attr('transform', 'translate(0,' + availableHeight + ')');
14639
14640                 g.select('.nv-x.nv-axis')
14641                     .transition().duration(0)
14642                     .call(xAxis);
14643             }
14644
14645             if (showYAxis) {
14646                 yAxis
14647                     .scale(y)
14648                     .ticks(stacked.offset() == 'wiggle' ? 0 : nv.utils.calcTicksY(availableHeight/36, data) )
14649                     .tickSize(-availableWidth, 0)
14650                     .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
14651                         ? d3.format('%') : yAxisTickFormat);
14652
14653                 g.select('.nv-y.nv-axis')
14654                     .transition().duration(0)
14655                     .call(yAxis);
14656             }
14657
14658             //------------------------------------------------------------
14659
14660
14661             //============================================================
14662             // Event Handling/Dispatching (in chart's scope)
14663             //------------------------------------------------------------
14664
14665             stacked.dispatch.on('areaClick.toggle', function(e) {
14666                 if (data.filter(function(d) { return !d.disabled }).length === 1)
14667                     data.forEach(function(d) {
14668                         d.disabled = false;
14669                     });
14670                 else
14671                     data.forEach(function(d,i) {
14672                         d.disabled = (i != e.seriesIndex);
14673                     });
14674
14675                 state.disabled = data.map(function(d) { return !!d.disabled });
14676                 dispatch.stateChange(state);
14677
14678                 chart.update();
14679             });
14680
14681             legend.dispatch.on('stateChange', function(newState) {
14682                 for (var key in newState)
14683                     state[key] = newState[key];
14684                 dispatch.stateChange(state);
14685                 chart.update();
14686             });
14687
14688             controls.dispatch.on('legendClick', function(d,i) {
14689                 if (!d.disabled) return;
14690
14691                 controlsData = controlsData.map(function(s) {
14692                     s.disabled = true;
14693                     return s;
14694                 });
14695                 d.disabled = false;
14696
14697                 stacked.style(d.style);
14698
14699
14700                 state.style = stacked.style();
14701                 dispatch.stateChange(state);
14702
14703                 chart.update();
14704             });
14705
14706
14707             interactiveLayer.dispatch.on('elementMousemove', function(e) {
14708                 stacked.clearHighlights();
14709                 var singlePoint, pointIndex, pointXLocation, allData = [];
14710                 data
14711                     .filter(function(series, i) {
14712                         series.seriesIndex = i;
14713                         return !series.disabled;
14714                     })
14715                     .forEach(function(series,i) {
14716                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
14717                         stacked.highlightPoint(i, pointIndex, true);
14718                         var point = series.values[pointIndex];
14719                         if (typeof point === 'undefined') return;
14720                         if (typeof singlePoint === 'undefined') singlePoint = point;
14721                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
14722
14723                         //If we are in 'expand' mode, use the stacked percent value instead of raw value.
14724                         var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
14725                         allData.push({
14726                             key: series.key,
14727                             value: tooltipValue,
14728                             color: color(series,series.seriesIndex),
14729                             stackedValue: point.display
14730                         });
14731                     });
14732
14733                 allData.reverse();
14734
14735                 //Highlight the tooltip entry based on which stack the mouse is closest to.
14736                 if (allData.length > 2) {
14737                     var yValue = chart.yScale().invert(e.mouseY);
14738                     var yDistMax = Infinity, indexToHighlight = null;
14739                     allData.forEach(function(series,i) {
14740
14741                         //To handle situation where the stacked area chart is negative, we need to use absolute values
14742                         //when checking if the mouse Y value is within the stack area.
14743                         yValue = Math.abs(yValue);
14744                         var stackedY0 = Math.abs(series.stackedValue.y0);
14745                         var stackedY = Math.abs(series.stackedValue.y);
14746                         if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
14747                         {
14748                             indexToHighlight = i;
14749                             return;
14750                         }
14751                     });
14752                     if (indexToHighlight != null)
14753                         allData[indexToHighlight].highlight = true;
14754                 }
14755
14756                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
14757
14758                 //If we are in 'expand' mode, force the format to be a percentage.
14759                 var valueFormatter = (stacked.style() == 'expand') ?
14760                     function(d,i) {return d3.format(".1%")(d);} :
14761                     function(d,i) {return yAxis.tickFormat()(d); };
14762                 interactiveLayer.tooltip
14763                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
14764                     .chartContainer(that.parentNode)
14765                     .enabled(tooltips)
14766                     .valueFormatter(valueFormatter)
14767                     .data(
14768                     {
14769                         value: xValue,
14770                         series: allData
14771                     }
14772                 )();
14773
14774                 interactiveLayer.renderGuideLine(pointXLocation);
14775
14776             });
14777
14778             interactiveLayer.dispatch.on("elementMouseout",function(e) {
14779                 dispatch.tooltipHide();
14780                 stacked.clearHighlights();
14781             });
14782
14783
14784             dispatch.on('tooltipShow', function(e) {
14785                 if (tooltips) showTooltip(e, that.parentNode);
14786             });
14787
14788             // Update chart from a state object passed to event handler
14789             dispatch.on('changeState', function(e) {
14790
14791                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
14792                     data.forEach(function(series,i) {
14793                         series.disabled = e.disabled[i];
14794                     });
14795
14796                     state.disabled = e.disabled;
14797                 }
14798
14799                 if (typeof e.style !== 'undefined') {
14800                     stacked.style(e.style);
14801                     style = e.style;
14802                 }
14803
14804                 chart.update();
14805             });
14806
14807         });
14808
14809         renderWatch.renderEnd('stacked Area chart immediate');
14810         return chart;
14811     }
14812
14813
14814     //============================================================
14815     // Event Handling/Dispatching (out of chart's scope)
14816     //------------------------------------------------------------
14817
14818     stacked.dispatch.on('tooltipShow', function(e) {
14819         //disable tooltips when value ~= 0
14820         //// TODO: consider removing points from voronoi that have 0 value instead of this hack
14821         /*
14822          if (!Math.round(stacked.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
14823          setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
14824          return false;
14825          }
14826          */
14827
14828         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
14829         dispatch.tooltipShow(e);
14830     });
14831
14832     stacked.dispatch.on('tooltipHide', function(e) {
14833         dispatch.tooltipHide(e);
14834     });
14835
14836     dispatch.on('tooltipHide', function() {
14837         if (tooltips) nv.tooltip.cleanup();
14838     });
14839
14840     //============================================================
14841
14842
14843     //============================================================
14844     // Expose Public Variables
14845     //------------------------------------------------------------
14846
14847     // expose chart's sub-components
14848     chart.dispatch = dispatch;
14849     chart.stacked = stacked;
14850     chart.legend = legend;
14851     chart.controls = controls;
14852     chart.xAxis = xAxis;
14853     chart.yAxis = yAxis;
14854     chart.interactiveLayer = interactiveLayer;
14855
14856     // DO NOT DELETE. This is currently overridden below
14857     // until deprecated portions are removed.
14858     chart.state = state;
14859
14860     d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
14861
14862     chart.options = nv.utils.optionsFunc.bind(chart);
14863
14864     chart.margin = function(_) {
14865         if (!arguments.length) return margin;
14866         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
14867         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
14868         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14869         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
14870         return chart;
14871     };
14872
14873     chart.width = function(_) {
14874         if (!arguments.length) return width;
14875         width = _;
14876         return chart;
14877     };
14878
14879     chart.height = function(_) {
14880         if (!arguments.length) return height;
14881         height = _;
14882         return chart;
14883     };
14884
14885     chart.color = function(_) {
14886         if (!arguments.length) return color;
14887         color = nv.utils.getColor(_);
14888         legend.color(color);
14889         stacked.color(color);
14890         return chart;
14891     };
14892
14893     chart.showControls = function(_) {
14894         if (!arguments.length) return showControls;
14895         showControls = _;
14896         return chart;
14897     };
14898
14899     chart.showLegend = function(_) {
14900         if (!arguments.length) return showLegend;
14901         showLegend = _;
14902         return chart;
14903     };
14904
14905     chart.showXAxis = function(_) {
14906         if (!arguments.length) return showXAxis;
14907         showXAxis = _;
14908         return chart;
14909     };
14910
14911     chart.showYAxis = function(_) {
14912         if (!arguments.length) return showYAxis;
14913         showYAxis = _;
14914         return chart;
14915     };
14916
14917     chart.rightAlignYAxis = function(_) {
14918         if(!arguments.length) return rightAlignYAxis;
14919         rightAlignYAxis = _;
14920         yAxis.orient( (_) ? 'right' : 'left');
14921         return chart;
14922     };
14923
14924     chart.useInteractiveGuideline = function(_) {
14925         if(!arguments.length) return useInteractiveGuideline;
14926         useInteractiveGuideline = _;
14927         if (_ === true) {
14928             chart.interactive(false);
14929             chart.useVoronoi(false);
14930         }
14931         return chart;
14932     };
14933
14934     chart.tooltip = function(_) {
14935         if (!arguments.length) return tooltip;
14936         tooltip = _;
14937         return chart;
14938     };
14939
14940     chart.tooltips = function(_) {
14941         if (!arguments.length) return tooltips;
14942         tooltips = _;
14943         return chart;
14944     };
14945
14946     chart.tooltipContent = function(_) {
14947         if (!arguments.length) return tooltip;
14948         tooltip = _;
14949         return chart;
14950     };
14951
14952     // DEPRECATED
14953     chart.state = function(_) {
14954         nv.deprecated('stackedAreaChart.state');
14955         if (!arguments.length) return state;
14956         state = _;
14957         return chart;
14958     };
14959     for (var key in state) {
14960         chart.state[key] = state[key];
14961     }
14962     // END DEPRECATED
14963
14964     chart.defaultState = function(_) {
14965         if (!arguments.length) return defaultState;
14966         defaultState = _;
14967         return chart;
14968     };
14969
14970     chart.noData = function(_) {
14971         if (!arguments.length) return noData;
14972         noData = _;
14973         return chart;
14974     };
14975
14976     chart.transitionDuration = function(_) {
14977         nv.deprecated('lineChart.transitionDuration');
14978         return chart.duration(_);
14979     };
14980
14981     chart.controlsData = function(_) {
14982         if (!arguments.length) return cData;
14983         cData = _;
14984         return chart;
14985     };
14986
14987     chart.controlLabels = function(_) {
14988         if (!arguments.length) return controlLabels;
14989         if (typeof _ !== 'object') return controlLabels;
14990         controlLabels = _;
14991         return chart;
14992     };
14993
14994     yAxis.setTickFormat = yAxis.tickFormat;
14995
14996     yAxis.tickFormat = function(_) {
14997         if (!arguments.length) return yAxisTickFormat;
14998         yAxisTickFormat = _;
14999         return yAxis;
15000     };
15001
15002     chart.duration = function(_) {
15003         if (!arguments.length) return duration;
15004         duration = _;
15005         renderWatch.reset(duration);
15006         stacked.duration(duration);
15007         xAxis.duration(duration);
15008         yAxis.duration(duration);
15009         return chart;
15010     };
15011
15012
15013     //============================================================
15014
15015     return chart;
15016 };
15017
15018 nv.version = "1.6.0";
15019 })();