70844369a332ce4c70f682ee20074ada84584665
[mkws-moved-to-github.git] / examples / htdocs / widgetsrising / nv.d3.js
1 /* nvd3 version 1.7.0(https://github.com/liquidpele/nvd3) 2014-12-13 */
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 = false; //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 Else return what was passed in
949 */
950 nv.utils.getColor = function(color) {
951     //if you pass in nothing, get default colors back
952     if (!arguments.length) {
953         return nv.utils.defaultColor();
954
955     //if passed an array, wrap it in a function
956     } else if(color instanceof Array) {
957         return function(d, i) { return d.color || color[i % color.length]; };
958
959     //if passed a function, return the function, or whatever it may be
960     //external libs, such as angularjs-nvd3-directives use this
961     } else {
962         //can't really help it if someone passes rubbish as color
963         return color;
964     }
965 };
966
967
968 /*
969 Default color chooser uses the index of an object as before.
970  */
971 nv.utils.defaultColor = function() {
972     var colors = d3.scale.category20().range();
973     return function(d, i) {
974         return d.color || colors[i % colors.length]
975     };
976 };
977
978
979 /*
980 Returns a color function that takes the result of 'getKey' for each series and
981 looks for a corresponding color from the dictionary
982 */
983 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
984     // use default series.key if getKey is undefined
985     getKey = getKey || function(series) { return series.key };
986     defaultColors = defaultColors || d3.scale.category20().range();
987
988     // start at end of default color list and walk back to index 0
989     var defIndex = defaultColors.length;
990
991     return function(series, index) {
992         var key = getKey(series);
993         if (typeof dictionary[key] === 'function') {
994             return dictionary[key]();
995         } else if (dictionary[key] !== undefined) {
996             return dictionary[key];
997         } else {
998             // no match in dictionary, use a default color
999             if (!defIndex) {
1000                 // used all the default colors, start over
1001                 defIndex = defaultColors.length;
1002             }
1003             defIndex = defIndex - 1;
1004             return defaultColors[defIndex];
1005         }
1006     };
1007 };
1008
1009
1010 /*
1011 From the PJAX example on d3js.org, while this is not really directly needed
1012 it's a very cool method for doing pjax, I may expand upon it a little bit,
1013 open to suggestions on anything that may be useful
1014 */
1015 nv.utils.pjax = function(links, content) {
1016
1017     var load = function(href) {
1018         d3.html(href, function(fragment) {
1019             var target = d3.select(content).node();
1020             target.parentNode.replaceChild(
1021                 d3.select(fragment).select(content).node(),
1022                 target);
1023             nv.utils.pjax(links, content);
1024         });
1025     };
1026
1027     d3.selectAll(links).on("click", function() {
1028         history.pushState(this.href, this.textContent, this.href);
1029         load(this.href);
1030         d3.event.preventDefault();
1031     });
1032
1033     d3.select(window).on("popstate", function() {
1034         if (d3.event.state) {
1035             load(d3.event.state);
1036         }
1037     });
1038 };
1039
1040
1041 /*
1042 For when we want to approximate the width in pixels for an SVG:text element.
1043 Most common instance is when the element is in a display:none; container.
1044 Forumla is : text.length * font-size * constant_factor
1045 */
1046 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1047     if (typeof svgTextElem.style === 'function'
1048         && typeof svgTextElem.text === 'function') {
1049
1050         var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
1051         var textLength = svgTextElem.text().length;
1052         return textLength * fontSize * 0.5;
1053     }
1054     return 0;
1055 };
1056
1057
1058 /*
1059 Numbers that are undefined, null or NaN, convert them to zeros.
1060 */
1061 nv.utils.NaNtoZero = function(n) {
1062     if (typeof n !== 'number'
1063         || isNaN(n)
1064         || n === null
1065         || n === Infinity
1066         || n === -Infinity) {
1067
1068         return 0;
1069     }
1070     return n;
1071 };
1072
1073 /*
1074 Add a way to watch for d3 transition ends to d3
1075 */
1076 d3.selection.prototype.watchTransition = function(renderWatch){
1077     var args = [this].concat([].slice.call(arguments, 1));
1078     return renderWatch.transition.apply(renderWatch, args);
1079 };
1080
1081
1082 /*
1083 Helper object to watch when d3 has rendered something
1084 */
1085 nv.utils.renderWatch = function(dispatch, duration) {
1086     if (!(this instanceof nv.utils.renderWatch)) {
1087         return new nv.utils.renderWatch(dispatch, duration);
1088     }
1089
1090     var _duration = duration !== undefined ? duration : 250;
1091     var renderStack = [];
1092     var self = this;
1093
1094     this.models = function(models) {
1095         models = [].slice.call(arguments, 0);
1096         models.forEach(function(model){
1097             model.__rendered = false;
1098             (function(m){
1099                 m.dispatch.on('renderEnd', function(arg){
1100                     m.__rendered = true;
1101                     self.renderEnd('model');
1102                 });
1103             })(model);
1104
1105             if (renderStack.indexOf(model) < 0) {
1106                 renderStack.push(model);
1107             }
1108         });
1109     return this;
1110     };
1111
1112     this.reset = function(duration) {
1113         if (duration !== undefined) {
1114             _duration = duration;
1115         }
1116         renderStack = [];
1117     };
1118
1119     this.transition = function(selection, args, duration) {
1120         args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1121
1122         if (args.length > 1) {
1123             duration = args.pop();
1124         } else {
1125             duration = _duration !== undefined ? _duration : 250;
1126         }
1127         selection.__rendered = false;
1128
1129         if (renderStack.indexOf(selection) < 0) {
1130             renderStack.push(selection);
1131         }
1132
1133         if (duration === 0) {
1134             selection.__rendered = true;
1135             selection.delay = function() { return this; };
1136             selection.duration = function() { return this; };
1137             return selection;
1138         } else {
1139             if (selection.length === 0) {
1140                 selection.__rendered = true;
1141             } else if (selection.every( function(d){ return !d.length; } )) {
1142                 selection.__rendered = true;
1143             } else {
1144                 selection.__rendered = false;
1145             }
1146
1147             var n = 0;
1148             return selection
1149                 .transition()
1150                 .duration(duration)
1151                 .each(function(){ ++n; })
1152                 .each('end', function(d, i) {
1153                     if (--n === 0) {
1154                         selection.__rendered = true;
1155                         self.renderEnd.apply(this, args);
1156                     }
1157                 });
1158         }
1159     };
1160
1161     this.renderEnd = function() {
1162         if (renderStack.every( function(d){ return d.__rendered; } )) {
1163             renderStack.forEach( function(d){ d.__rendered = false; });
1164             dispatch.renderEnd.apply(this, arguments);
1165         }
1166     }
1167
1168 };
1169
1170
1171 /*
1172 Takes multiple objects and combines them into the first one (dst)
1173 example:  nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1174 gives:  {a: 2, b: 3, c: 4}
1175 */
1176 nv.utils.deepExtend = function(dst){
1177     var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1178     sources.forEach(function(source) {
1179         for (key in source) {
1180             var isArray = dst[key] instanceof Array;
1181             var isObject = typeof dst[key] === 'object';
1182             var srcObj = typeof source[key] === 'object';
1183
1184             if (isObject && !isArray && srcObj) {
1185                 nv.utils.deepExtend(dst[key], source[key]);
1186             } else {
1187                 dst[key] = source[key];
1188             }
1189         }
1190     });
1191 };
1192
1193
1194 /*
1195 state utility object, used to track d3 states in the models
1196 */
1197 nv.utils.state = function(){
1198     if (!(this instanceof nv.utils.state)) {
1199         return new nv.utils.state();
1200     }
1201     var state = {};
1202     var _self = this;
1203     var _setState = function(){};
1204     var _getState = function(){ return {}; };
1205     var init = null;
1206     var changed = null;
1207
1208     this.dispatch = d3.dispatch('change', 'set');
1209
1210     this.dispatch.on('set', function(state){
1211         _setState(state, true);
1212     });
1213
1214     this.getter = function(fn){
1215         _getState = fn;
1216         return this;
1217     };
1218
1219     this.setter = function(fn, callback) {
1220         if (!callback) {
1221             callback = function(){};
1222         }
1223         _setState = function(state, update){
1224             fn(state);
1225             if (update) {
1226                 callback();
1227             }
1228         };
1229         return this;
1230     };
1231
1232     this.init = function(state){
1233         init = init || {};
1234         nv.utils.deepExtend(init, state);
1235     };
1236
1237     var _set = function(){
1238         var settings = _getState();
1239
1240         if (JSON.stringify(settings) === JSON.stringify(state)) {
1241             return false;
1242         }
1243
1244         for (var key in settings) {
1245             if (state[key] === undefined) {
1246                 state[key] = {};
1247             }
1248             state[key] = settings[key];
1249             changed = true;
1250         }
1251         return true;
1252     };
1253
1254     this.update = function(){
1255         if (init) {
1256             _setState(init, false);
1257             init = null;
1258         }
1259         if (_set.call(this)) {
1260             this.dispatch.change(state);
1261         }
1262     };
1263
1264 };
1265
1266
1267 /*
1268 Snippet of code you can insert into each nv.models.* to give you the ability to
1269 do things like:
1270 chart.options({
1271   showXAxis: true,
1272   tooltips: true
1273 });
1274
1275 To enable in the chart:
1276 chart.options = nv.utils.optionsFunc.bind(chart);
1277 */
1278 nv.utils.optionsFunc = function(args) {
1279     nv.deprecated('nv.utils.optionsFunc');
1280     if (args) {
1281         d3.map(args).forEach((function(key,value) {
1282             if (typeof this[key] === "function") {
1283                 this[key](value);
1284             }
1285         }).bind(this));
1286     }
1287     return this;
1288 };
1289
1290
1291 /*
1292 numTicks:  requested number of ticks
1293 data:  the chart data
1294
1295 returns the number of ticks to actually use on X axis, based on chart data
1296 to avoid duplicate ticks with the same value
1297 */
1298 nv.utils.calcTicksX = function(numTicks, data) {
1299     // find max number of values from all data streams
1300     var numValues = 1;
1301     var i = 0;
1302     for (i; i < data.length; i += 1) {
1303         var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1304         numValues = stream_len > numValues ? stream_len : numValues;
1305     }
1306     nv.log("Requested number of ticks: ", numTicks);
1307     nv.log("Calculated max values to be: ", numValues);
1308     // make sure we don't have more ticks than values to avoid duplicates
1309     numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1310     // make sure we have at least one tick
1311     numTicks = numTicks < 1 ? 1 : numTicks;
1312     // make sure it's an integer
1313     numTicks = Math.floor(numTicks);
1314     nv.log("Calculating tick count as: ", numTicks);
1315     return numTicks;
1316 };
1317
1318
1319 /*
1320 returns number of ticks to actually use on Y axis, based on chart data
1321 */
1322 nv.utils.calcTicksY = function(numTicks, data) {
1323     // currently uses the same logic but we can adjust here if needed later
1324     return nv.utils.calcTicksX(numTicks, data);
1325 };
1326
1327
1328 /*
1329 Add a particular option from an options object onto chart
1330 Options exposed on a chart are a getter/setter function that returns chart
1331 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1332
1333 option objects should be generated via Object.create() to provide
1334 the option of manipulating data via get/set functions.
1335 */
1336 nv.utils.initOption = function(chart, name) {
1337     // if it's a call option, just call it directly, otherwise do get/set
1338     if (chart._calls && chart._calls[name]) {
1339         chart[name] = chart._calls[name];
1340     } else {
1341         chart[name] = function (_) {
1342             if (!arguments.length) return chart._options[name];
1343             chart._options[name] = _;
1344             return chart;
1345         };
1346     }
1347 };
1348
1349
1350 /*
1351 Add all options in an options object to the chart
1352 */
1353 nv.utils.initOptions = function(chart) {
1354     var ops = Object.getOwnPropertyNames(chart._options || {});
1355     var calls = Object.getOwnPropertyNames(chart._calls || {});
1356     ops = ops.concat(calls);
1357     for (var i in ops) {
1358         nv.utils.initOption(chart, ops[i]);
1359     }
1360 };
1361
1362
1363 /*
1364 Inherit options from a D3 object
1365 d3.rebind makes calling the function on target actually call it on source
1366 Also use _d3options so we can track what we inherit for documentation and chained inheritance
1367 */
1368 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
1369     target._d3options = oplist.concat(target._d3options || []);
1370     oplist.unshift(d3_source);
1371     oplist.unshift(target);
1372     d3.rebind.apply(this, oplist);
1373 };
1374
1375
1376 /*
1377 Remove duplicates from an array
1378 */
1379 nv.utils.arrayUnique = function(a) {
1380     return a.sort().filter(function(item, pos) {
1381         return !pos || item != a[pos - 1];
1382     });
1383 };
1384
1385
1386 /*
1387 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
1388 Necessary since d3 doesn't let you extend its list -_-
1389 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
1390 */
1391 nv.utils.symbolMap = d3.map();
1392
1393
1394 /*
1395 Replaces d3.svg.symbol so that we can look both there and our own map
1396  */
1397 nv.utils.symbol = function() {
1398     var type,
1399         size = 64;
1400     function symbol(d,i) {
1401         var t = type.call(this,d,i);
1402         var s = size.call(this,d,i);
1403         if (d3.svg.symbolTypes.indexOf(t) !== -1) {
1404             return d3.svg.symbol().type(t).size(s)();
1405         } else {
1406             return nv.utils.symbolMap.get(t)(s);
1407         }
1408     }
1409     symbol.type = function(_) {
1410         if (!arguments.length) return type;
1411         type = d3.functor(_);
1412         return symbol;
1413     };
1414     symbol.size = function(_) {
1415         if (!arguments.length) return size;
1416         size = d3.functor(_);
1417         return symbol;
1418     };
1419     return symbol;
1420 };
1421
1422
1423 /*
1424 Inherit option getter/setter functions from source to target
1425 d3.rebind makes calling the function on target actually call it on source
1426 Also track via _inherited and _d3options so we can track what we inherit
1427 for documentation generation purposes and chained inheritance
1428 */
1429 nv.utils.inheritOptions = function(target, source) {
1430     // inherit all the things
1431     var ops = Object.getOwnPropertyNames(source._options || {});
1432     var calls = Object.getOwnPropertyNames(source._calls || {});
1433     var inherited = source._inherited || [];
1434     var d3ops = source._d3options || [];
1435     var args = ops.concat(calls).concat(inherited).concat(d3ops);
1436     args.unshift(source);
1437     args.unshift(target);
1438     d3.rebind.apply(this, args);
1439     // pass along the lists to keep track of them, don't allow duplicates
1440     target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
1441     target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
1442 };
1443
1444
1445 /*
1446 Runs common initialize code on the svg before the chart builds
1447 */
1448 nv.utils.initSVG = function(svg) {
1449     svg.classed({'nvd3-svg':true});
1450 };nv.models.axis = function() {
1451     "use strict";
1452
1453     //============================================================
1454     // Public Variables with Default Settings
1455     //------------------------------------------------------------
1456
1457     var axis = d3.svg.axis();
1458     var scale = d3.scale.linear();
1459
1460     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1461         , width = 75 //only used for tickLabel currently
1462         , height = 60 //only used for tickLabel currently
1463         , axisLabelText = null
1464         , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1465         , highlightZero = true
1466         , rotateLabels = 0
1467         , rotateYLabel = true
1468         , staggerLabels = false
1469         , isOrdinal = false
1470         , ticks = null
1471         , axisLabelDistance = 0
1472         , duration = 250
1473         , dispatch = d3.dispatch('renderEnd')
1474         , axisRendered = false
1475         , maxMinRendered = false
1476         ;
1477     axis
1478         .scale(scale)
1479         .orient('bottom')
1480         .tickFormat(function(d) { return d })
1481     ;
1482
1483     //============================================================
1484     // Private Variables
1485     //------------------------------------------------------------
1486
1487     var scale0;
1488     var renderWatch = nv.utils.renderWatch(dispatch, duration);
1489
1490     function chart(selection) {
1491         renderWatch.reset();
1492         selection.each(function(data) {
1493             var container = d3.select(this);
1494             nv.utils.initSVG(container);
1495
1496             // Setup containers and skeleton of chart
1497             var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1498             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1499             var gEnter = wrapEnter.append('g');
1500             var g = wrap.select('g')
1501
1502             if (ticks !== null)
1503                 axis.ticks(ticks);
1504             else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1505                 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1506
1507             //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1508             g.watchTransition(renderWatch, 'axis').call(axis);
1509
1510             scale0 = scale0 || axis.scale();
1511
1512             var fmt = axis.tickFormat();
1513             if (fmt == null) {
1514                 fmt = scale0.tickFormat();
1515             }
1516
1517             var axisLabel = g.selectAll('text.nv-axislabel')
1518                 .data([axisLabelText || null]);
1519             axisLabel.exit().remove();
1520
1521             switch (axis.orient()) {
1522                 case 'top':
1523                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1524                     var w;
1525                     if (scale.range().length < 2) {
1526                         w = 0;
1527                     } else if (scale.range().length === 2) {
1528                         w = scale.range()[1];
1529                     } else {
1530                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1531                     }
1532                     axisLabel
1533                         .attr('text-anchor', 'middle')
1534                         .attr('y', 0)
1535                         .attr('x', w/2);
1536                     if (showMaxMin) {
1537                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1538                             .data(scale.domain());
1539                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1540                         axisMaxMin.exit().remove();
1541                         axisMaxMin
1542                             .attr('transform', function(d,i) {
1543                                 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
1544                             })
1545                             .select('text')
1546                             .attr('dy', '-0.5em')
1547                             .attr('y', -axis.tickPadding())
1548                             .attr('text-anchor', 'middle')
1549                             .text(function(d,i) {
1550                                 var v = fmt(d);
1551                                 return ('' + v).match('NaN') ? '' : v;
1552                             });
1553                         axisMaxMin.watchTransition(renderWatch, 'min-max top')
1554                             .attr('transform', function(d,i) {
1555                                 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
1556                             });
1557                     }
1558                     break;
1559                 case 'bottom':
1560                     var xLabelMargin = axisLabelDistance + 36;
1561                     var maxTextWidth = 30;
1562                     var xTicks = g.selectAll('g').select("text");
1563                     if (rotateLabels%360) {
1564                         //Calculate the longest xTick width
1565                         xTicks.each(function(d,i){
1566                             var width = this.getBoundingClientRect().width;
1567                             if(width > maxTextWidth) maxTextWidth = width;
1568                         });
1569                         //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1570                         var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1571                         var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1572                         //Rotate all xTicks
1573                         xTicks
1574                             .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1575                             .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1576                     }
1577                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1578                     var w;
1579                     if (scale.range().length < 2) {
1580                         w = 0;
1581                     } else if (scale.range().length === 2) {
1582                         w = scale.range()[1];
1583                     } else {
1584                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1585                     }
1586                     axisLabel
1587                         .attr('text-anchor', 'middle')
1588                         .attr('y', xLabelMargin)
1589                         .attr('x', w/2);
1590                     if (showMaxMin) {
1591                         //if (showMaxMin && !isOrdinal) {
1592                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1593                             //.data(scale.domain())
1594                             .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1595                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1596                         axisMaxMin.exit().remove();
1597                         axisMaxMin
1598                             .attr('transform', function(d,i) {
1599                                 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1600                             })
1601                             .select('text')
1602                             .attr('dy', '.71em')
1603                             .attr('y', axis.tickPadding())
1604                             .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1605                             .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1606                             .text(function(d,i) {
1607                                 var v = fmt(d);
1608                                 return ('' + v).match('NaN') ? '' : v;
1609                             });
1610                         axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1611                             .attr('transform', function(d,i) {
1612                                 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1613                             });
1614                     }
1615                     if (staggerLabels)
1616                         xTicks
1617                             .attr('transform', function(d,i) { return 'translate(0,' + nv.utils.NaNtoZero((i % 2 == 0 ? '0' : '12')) + ')' });
1618
1619                     break;
1620                 case 'right':
1621                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1622                     axisLabel
1623                         .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1624                         .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1625                         .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
1626                         .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1627                     if (showMaxMin) {
1628                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1629                             .data(scale.domain());
1630                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1631                             .style('opacity', 0);
1632                         axisMaxMin.exit().remove();
1633                         axisMaxMin
1634                             .attr('transform', function(d,i) {
1635                                 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
1636                             })
1637                             .select('text')
1638                             .attr('dy', '.32em')
1639                             .attr('y', 0)
1640                             .attr('x', axis.tickPadding())
1641                             .style('text-anchor', 'start')
1642                             .text(function(d,i) {
1643                                 var v = fmt(d);
1644                                 return ('' + v).match('NaN') ? '' : v;
1645                             });
1646                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1647                             .attr('transform', function(d,i) {
1648                                 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1649                             })
1650                             .select('text')
1651                             .style('opacity', 1);
1652                     }
1653                     break;
1654                 case 'left':
1655                     /*
1656                      //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1657                      var yTicks = g.selectAll('g').select("text");
1658                      yTicks.each(function(d,i){
1659                      var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1660                      if(labelPadding > width) width = labelPadding;
1661                      });
1662                      */
1663                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1664                     axisLabel
1665                         .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1666                         .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1667                         .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 25 - (axisLabelDistance || 0)) : -10)
1668                         .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1669                     if (showMaxMin) {
1670                         var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1671                             .data(scale.domain());
1672                         axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1673                             .style('opacity', 0);
1674                         axisMaxMin.exit().remove();
1675                         axisMaxMin
1676                             .attr('transform', function(d,i) {
1677                                 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
1678                             })
1679                             .select('text')
1680                             .attr('dy', '.32em')
1681                             .attr('y', 0)
1682                             .attr('x', -axis.tickPadding())
1683                             .attr('text-anchor', 'end')
1684                             .text(function(d,i) {
1685                                 var v = fmt(d);
1686                                 return ('' + v).match('NaN') ? '' : v;
1687                             });
1688                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1689                             .attr('transform', function(d,i) {
1690                                 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1691                             })
1692                             .select('text')
1693                             .style('opacity', 1);
1694                     }
1695                     break;
1696             }
1697             axisLabel.text(function(d) { return d });
1698
1699             if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1700                 //check if max and min overlap other values, if so, hide the values that overlap
1701                 g.selectAll('g') // the g's wrapping each tick
1702                     .each(function(d,i) {
1703                         d3.select(this).select('text').attr('opacity', 1);
1704                         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!
1705                             if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1706                                 d3.select(this).attr('opacity', 0);
1707
1708                             d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1709                         }
1710                     });
1711
1712                 //if Max and Min = 0 only show min, Issue #281
1713                 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
1714                     wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
1715                         return !i ? 1 : 0
1716                     });
1717                 }
1718             }
1719
1720             if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1721                 var maxMinRange = [];
1722                 wrap.selectAll('g.nv-axisMaxMin')
1723                     .each(function(d,i) {
1724                         try {
1725                             if (i) // i== 1, max position
1726                                 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)
1727                             else // i==0, min position
1728                                 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1729                         }catch (err) {
1730                             if (i) // i== 1, max position
1731                                 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)
1732                             else // i==0, min position
1733                                 maxMinRange.push(scale(d) + 4);
1734                         }
1735                     });
1736                 // the g's wrapping each tick
1737                 g.selectAll('g').each(function(d,i) {
1738                     if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1739                         if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1740                             d3.select(this).remove();
1741                         else
1742                             d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1743                     }
1744                 });
1745             }
1746
1747             //highlight zero line ... Maybe should not be an option and should just be in CSS?
1748             if (highlightZero) {
1749                 g.selectAll('.tick')
1750                     .filter(function (d) {
1751                         return !parseFloat(Math.round(this.__data__ * 100000) / 1000000) && (this.__data__ !== undefined)
1752                     }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1753                     .classed('zero', true);
1754             }
1755             //store old scales for use in transitions on update
1756             scale0 = scale.copy();
1757
1758         });
1759
1760         renderWatch.renderEnd('axis immediate');
1761         return chart;
1762     }
1763
1764     //============================================================
1765     // Expose Public Variables
1766     //------------------------------------------------------------
1767
1768     // expose chart's sub-components
1769     chart.axis = axis;
1770     chart.dispatch = dispatch;
1771
1772     chart.options = nv.utils.optionsFunc.bind(chart);
1773     chart._options = Object.create({}, {
1774         // simple options, just get/set the necessary values
1775         axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
1776         staggerLabels:     {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
1777         rotateLabels:      {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
1778         rotateYLabel:      {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
1779         highlightZero:     {get: function(){return highlightZero;}, set: function(_){highlightZero=_;}},
1780         showMaxMin:        {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
1781         axisLabel:         {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
1782         height:            {get: function(){return height;}, set: function(_){height=_;}},
1783         ticks:             {get: function(){return ticks;}, set: function(_){ticks=_;}},
1784         width:             {get: function(){return width;}, set: function(_){width=_;}},
1785
1786         // options that require extra logic in the setter
1787         margin: {get: function(){return margin;}, set: function(_){
1788             margin.top    = _.top !== undefined    ? _.top    : margin.top;
1789             margin.right  = _.right !== undefined  ? _.right  : margin.right;
1790             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
1791             margin.left   = _.left !== undefined   ? _.left   : margin.left;
1792         }},
1793         duration: {get: function(){return duration;}, set: function(_){
1794             duration=_;
1795             renderWatch.reset(duration);
1796         }},
1797         scale: {get: function(){return scale;}, set: function(_){
1798             scale = _;
1799             axis.scale(scale);
1800             isOrdinal = typeof scale.rangeBands === 'function';
1801             nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1802         }}
1803     });
1804
1805     nv.utils.initOptions(chart);
1806     nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
1807     nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1808
1809     return chart;
1810 };
1811
1812 // Chart design based on the recommendations of Stephen Few. Implementation
1813 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1814 // http://projects.instantcognition.com/protovis/bulletchart/
1815
1816 nv.models.bullet = function() {
1817     "use strict";
1818
1819     //============================================================
1820     // Public Variables with Default Settings
1821     //------------------------------------------------------------
1822
1823     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1824         , orient = 'left' // TODO top & bottom
1825         , reverse = false
1826         , ranges = function(d) { return d.ranges }
1827         , markers = function(d) { return d.markers ? d.markers : [0] }
1828         , measures = function(d) { return d.measures }
1829         , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1830         , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
1831         , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
1832         , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1833         , width = 380
1834         , height = 30
1835         , tickFormat = null
1836         , color = nv.utils.getColor(['#1f77b4'])
1837         , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1838         ;
1839
1840     function chart(selection) {
1841         selection.each(function(d, i) {
1842             var availableWidth = width - margin.left - margin.right,
1843                 availableHeight = height - margin.top - margin.bottom,
1844                 container = d3.select(this);
1845             nv.utils.initSVG(container);
1846
1847             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1848                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1849                 measurez = measures.call(this, d, i).slice().sort(d3.descending),
1850                 rangeLabelz = rangeLabels.call(this, d, i).slice(),
1851                 markerLabelz = markerLabels.call(this, d, i).slice(),
1852                 measureLabelz = measureLabels.call(this, d, i).slice();
1853
1854             // Setup Scales
1855             // Compute the new x-scale.
1856             var x1 = d3.scale.linear()
1857                 .domain( d3.extent(d3.merge([forceX, rangez])) )
1858                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1859
1860             // Retrieve the old x-scale, if this is an update.
1861             var x0 = this.__chart__ || d3.scale.linear()
1862                 .domain([0, Infinity])
1863                 .range(x1.range());
1864
1865             // Stash the new scale.
1866             this.__chart__ = x1;
1867
1868             var rangeMin = d3.min(rangez), //rangez[2]
1869                 rangeMax = d3.max(rangez), //rangez[0]
1870                 rangeAvg = rangez[1];
1871
1872             // Setup containers and skeleton of chart
1873             var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1874             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1875             var gEnter = wrapEnter.append('g');
1876             var g = wrap.select('g');
1877
1878             gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1879             gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1880             gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1881             gEnter.append('rect').attr('class', 'nv-measure');
1882             gEnter.append('path').attr('class', 'nv-markerTriangle');
1883
1884             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1885
1886             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1887                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1888             var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1889                 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
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             g.select('rect.nv-rangeAvg')
1898                 .attr('height', availableHeight)
1899                 .attr('width', w1(rangeAvg))
1900                 .attr('x', xp1(rangeAvg))
1901                 .datum(rangeAvg)
1902
1903             g.select('rect.nv-rangeMin')
1904                 .attr('height', availableHeight)
1905                 .attr('width', w1(rangeMax))
1906                 .attr('x', xp1(rangeMax))
1907                 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1908                 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1909                 .datum(rangeMax > 0 ? rangeMin : rangeMax)
1910
1911             g.select('rect.nv-measure')
1912                 .style('fill', color)
1913                 .attr('height', availableHeight / 3)
1914                 .attr('y', availableHeight / 3)
1915                 .attr('width', measurez < 0 ?
1916                     x1(0) - x1(measurez[0])
1917                     : x1(measurez[0]) - x1(0))
1918                 .attr('x', xp1(measurez))
1919                 .on('mouseover', function() {
1920                     dispatch.elementMouseover({
1921                         value: measurez[0],
1922                         label: measureLabelz[0] || 'Current',
1923                         pos: [x1(measurez[0]), availableHeight/2]
1924                     })
1925                 })
1926                 .on('mouseout', function() {
1927                     dispatch.elementMouseout({
1928                         value: measurez[0],
1929                         label: measureLabelz[0] || 'Current'
1930                     })
1931                 });
1932
1933             var h3 =  availableHeight / 6;
1934             if (markerz[0]) {
1935                 g.selectAll('path.nv-markerTriangle')
1936                     .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1937                     .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1938                     .on('mouseover', function() {
1939                         dispatch.elementMouseover({
1940                             value: markerz[0],
1941                             label: markerLabelz[0] || 'Previous',
1942                             pos: [x1(markerz[0]), availableHeight/2]
1943                         })
1944                     })
1945                     .on('mouseout', function() {
1946                         dispatch.elementMouseout({
1947                             value: markerz[0],
1948                             label: markerLabelz[0] || 'Previous'
1949                         })
1950                     });
1951             } else {
1952                 g.selectAll('path.nv-markerTriangle').remove();
1953             }
1954
1955             wrap.selectAll('.nv-range')
1956                 .on('mouseover', function(d,i) {
1957                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1958
1959                     dispatch.elementMouseover({
1960                         value: d,
1961                         label: label,
1962                         pos: [x1(d), availableHeight/2]
1963                     })
1964                 })
1965                 .on('mouseout', function(d,i) {
1966                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1967
1968                     dispatch.elementMouseout({
1969                         value: d,
1970                         label: label
1971                     })
1972                 });
1973         });
1974
1975         return chart;
1976     }
1977
1978     //============================================================
1979     // Expose Public Variables
1980     //------------------------------------------------------------
1981
1982     chart.dispatch = dispatch;
1983     chart.options = nv.utils.optionsFunc.bind(chart);
1984
1985     chart._options = Object.create({}, {
1986         // simple options, just get/set the necessary values
1987         ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
1988         markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
1989         measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
1990         forceX:      {get: function(){return forceX;}, set: function(_){forceX=_;}},
1991         width:    {get: function(){return width;}, set: function(_){width=_;}},
1992         height:    {get: function(){return height;}, set: function(_){height=_;}},
1993         tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
1994
1995         // options that require extra logic in the setter
1996         margin: {get: function(){return margin;}, set: function(_){
1997             margin.top    = _.top    !== undefined ? _.top    : margin.top;
1998             margin.right  = _.right  !== undefined ? _.right  : margin.right;
1999             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2000             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2001         }},
2002         orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2003             orient = _;
2004             reverse = orient == 'right' || orient == 'bottom';
2005         }},
2006         color:  {get: function(){return color;}, set: function(_){
2007             color = nv.utils.getColor(_);
2008         }}
2009     });
2010
2011     nv.utils.initOptions(chart);
2012     return chart;
2013 };
2014
2015
2016
2017 // Chart design based on the recommendations of Stephen Few. Implementation
2018 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2019 // http://projects.instantcognition.com/protovis/bulletchart/
2020 nv.models.bulletChart = function() {
2021     "use strict";
2022
2023     //============================================================
2024     // Public Variables with Default Settings
2025     //------------------------------------------------------------
2026
2027     var bullet = nv.models.bullet()
2028         ;
2029
2030     var orient = 'left' // TODO top & bottom
2031         , reverse = false
2032         , margin = {top: 5, right: 40, bottom: 20, left: 120}
2033         , ranges = function(d) { return d.ranges }
2034         , markers = function(d) { return d.markers ? d.markers : [0] }
2035         , measures = function(d) { return d.measures }
2036         , width = null
2037         , height = 55
2038         , tickFormat = null
2039         , tooltips = true
2040         , tooltip = function(key, x, y, e, graph) {
2041             return '<h3>' + x + '</h3>' +
2042                 '<p>' + y + '</p>'
2043         }
2044         , noData = 'No Data Available.'
2045         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2046         ;
2047
2048     //============================================================
2049     // Private Variables
2050     //------------------------------------------------------------
2051
2052     var showTooltip = function(e, offsetElement) {
2053         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
2054             top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
2055             content = tooltip(e.key, e.label, e.value, e, chart);
2056
2057         nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
2058     };
2059
2060     function chart(selection) {
2061         selection.each(function(d, i) {
2062             var container = d3.select(this);
2063             nv.utils.initSVG(container);
2064
2065             var availableWidth = (width  || parseInt(container.style('width')) || 960)
2066                     - margin.left - margin.right,
2067                 availableHeight = height - margin.top - margin.bottom,
2068                 that = this;
2069
2070             chart.update = function() { chart(selection) };
2071             chart.container = this;
2072
2073             // Display No Data message if there's nothing to show.
2074             if (!d || !ranges.call(this, d, i)) {
2075                 var noDataText = container.selectAll('.nv-noData').data([noData]);
2076
2077                 noDataText.enter().append('text')
2078                     .attr('class', 'nvd3 nv-noData')
2079                     .attr('dy', '-.7em')
2080                     .style('text-anchor', 'middle');
2081
2082                 noDataText
2083                     .attr('x', margin.left + availableWidth / 2)
2084                     .attr('y', 18 + margin.top + availableHeight / 2)
2085                     .text(function(d) { return d });
2086
2087                 return chart;
2088             } else {
2089                 container.selectAll('.nv-noData').remove();
2090             }
2091
2092             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2093                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2094                 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2095
2096             // Setup containers and skeleton of chart
2097             var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2098             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2099             var gEnter = wrapEnter.append('g');
2100             var g = wrap.select('g');
2101
2102             gEnter.append('g').attr('class', 'nv-bulletWrap');
2103             gEnter.append('g').attr('class', 'nv-titles');
2104
2105             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2106
2107             // Compute the new x-scale.
2108             var x1 = d3.scale.linear()
2109                 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
2110                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2111
2112             // Retrieve the old x-scale, if this is an update.
2113             var x0 = this.__chart__ || d3.scale.linear()
2114                 .domain([0, Infinity])
2115                 .range(x1.range());
2116
2117             // Stash the new scale.
2118             this.__chart__ = x1;
2119
2120             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2121                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2122
2123             var title = gEnter.select('.nv-titles').append('g')
2124                 .attr('text-anchor', 'end')
2125                 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2126             title.append('text')
2127                 .attr('class', 'nv-title')
2128                 .text(function(d) { return d.title; });
2129
2130             title.append('text')
2131                 .attr('class', 'nv-subtitle')
2132                 .attr('dy', '1em')
2133                 .text(function(d) { return d.subtitle; });
2134
2135             bullet
2136                 .width(availableWidth)
2137                 .height(availableHeight)
2138
2139             var bulletWrap = g.select('.nv-bulletWrap');
2140             d3.transition(bulletWrap).call(bullet);
2141
2142             // Compute the tick format.
2143             var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2144
2145             // Update the tick groups.
2146             var tick = g.selectAll('g.nv-tick')
2147                 .data(x1.ticks( availableWidth / 50 ), function(d) {
2148                     return this.textContent || format(d);
2149                 });
2150
2151             // Initialize the ticks with the old scale, x0.
2152             var tickEnter = tick.enter().append('g')
2153                 .attr('class', 'nv-tick')
2154                 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2155                 .style('opacity', 1e-6);
2156
2157             tickEnter.append('line')
2158                 .attr('y1', availableHeight)
2159                 .attr('y2', availableHeight * 7 / 6);
2160
2161             tickEnter.append('text')
2162                 .attr('text-anchor', 'middle')
2163                 .attr('dy', '1em')
2164                 .attr('y', availableHeight * 7 / 6)
2165                 .text(format);
2166
2167             // Transition the updating ticks to the new scale, x1.
2168             var tickUpdate = d3.transition(tick)
2169                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2170                 .style('opacity', 1);
2171
2172             tickUpdate.select('line')
2173                 .attr('y1', availableHeight)
2174                 .attr('y2', availableHeight * 7 / 6);
2175
2176             tickUpdate.select('text')
2177                 .attr('y', availableHeight * 7 / 6);
2178
2179             // Transition the exiting ticks to the new scale, x1.
2180             d3.transition(tick.exit())
2181                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2182                 .style('opacity', 1e-6)
2183                 .remove();
2184
2185             //============================================================
2186             // Event Handling/Dispatching (in chart's scope)
2187             //------------------------------------------------------------
2188
2189             dispatch.on('tooltipShow', function(e) {
2190                 e.key = d.title;
2191                 if (tooltips) showTooltip(e, that.parentNode);
2192             });
2193
2194         });
2195
2196         d3.timer.flush();
2197         return chart;
2198     }
2199
2200     //============================================================
2201     // Event Handling/Dispatching (out of chart's scope)
2202     //------------------------------------------------------------
2203
2204     bullet.dispatch.on('elementMouseover.tooltip', function(e) {
2205         dispatch.tooltipShow(e);
2206     });
2207
2208     bullet.dispatch.on('elementMouseout.tooltip', function(e) {
2209         dispatch.tooltipHide(e);
2210     });
2211
2212     dispatch.on('tooltipHide', function() {
2213         if (tooltips) nv.tooltip.cleanup();
2214     });
2215
2216     //============================================================
2217     // Expose Public Variables
2218     //------------------------------------------------------------
2219
2220     chart.bullet = bullet;
2221     chart.dispatch = dispatch;
2222     chart.options = nv.utils.optionsFunc.bind(chart);
2223
2224     chart._options = Object.create({}, {
2225         // simple options, just get/set the necessary values
2226         ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2227         markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2228         measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2229         width:    {get: function(){return width;}, set: function(_){width=_;}},
2230         height:    {get: function(){return height;}, set: function(_){height=_;}},
2231         tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2232         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2233         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2234         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
2235
2236         // options that require extra logic in the setter
2237         margin: {get: function(){return margin;}, set: function(_){
2238             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2239             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2240             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2241             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2242         }},
2243         orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2244             orient = _;
2245             reverse = orient == 'right' || orient == 'bottom';
2246         }}
2247     });
2248
2249     nv.utils.inheritOptions(chart, bullet);
2250     nv.utils.initOptions(chart);
2251
2252     return chart;
2253 };
2254
2255
2256
2257 nv.models.cumulativeLineChart = function() {
2258     "use strict";
2259
2260     //============================================================
2261     // Public Variables with Default Settings
2262     //------------------------------------------------------------
2263
2264     var lines = nv.models.line()
2265         , xAxis = nv.models.axis()
2266         , yAxis = nv.models.axis()
2267         , legend = nv.models.legend()
2268         , controls = nv.models.legend()
2269         , interactiveLayer = nv.interactiveGuideline()
2270         ;
2271
2272     var margin = {top: 30, right: 30, bottom: 50, left: 60}
2273         , color = nv.utils.defaultColor()
2274         , width = null
2275         , height = null
2276         , showLegend = true
2277         , showXAxis = true
2278         , showYAxis = true
2279         , rightAlignYAxis = false
2280         , tooltips = true
2281         , showControls = true
2282         , useInteractiveGuideline = false
2283         , rescaleY = true
2284         , tooltip = function(key, x, y, e, graph) {
2285             return '<h3>' + key + '</h3>' +
2286                 '<p>' +  y + ' at ' + x + '</p>'
2287         }
2288         , x //can be accessed via chart.xScale()
2289         , y //can be accessed via chart.yScale()
2290         , id = lines.id()
2291         , state = nv.utils.state()
2292         , defaultState = null
2293         , noData = 'No Data Available.'
2294         , average = function(d) { return d.average }
2295         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
2296         , transitionDuration = 250
2297         , duration = 250
2298         , noErrorCheck = false  //if set to TRUE, will bypass an error check in the indexify function.
2299         ;
2300
2301     state.index = 0;
2302     state.rescaleY = rescaleY;
2303
2304     xAxis
2305         .orient('bottom')
2306         .tickPadding(7)
2307     ;
2308     yAxis
2309         .orient((rightAlignYAxis) ? 'right' : 'left')
2310     ;
2311
2312     controls.updateState(false);
2313
2314     //============================================================
2315     // Private Variables
2316     //------------------------------------------------------------
2317
2318     var dx = d3.scale.linear()
2319         , index = {i: 0, x: 0}
2320         , renderWatch = nv.utils.renderWatch(dispatch, duration)
2321         ;
2322
2323     var showTooltip = function(e, offsetElement) {
2324         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2325             top = e.pos[1] + ( offsetElement.offsetTop || 0),
2326             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2327             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2328             content = tooltip(e.series.key, x, y, e, chart);
2329
2330         nv.tooltip.show([left, top], content, null, null, offsetElement);
2331     };
2332
2333     var stateGetter = function(data) {
2334         return function(){
2335             return {
2336                 active: data.map(function(d) { return !d.disabled }),
2337                 index: index.i,
2338                 rescaleY: rescaleY
2339             };
2340         }
2341     };
2342
2343     var stateSetter = function(data) {
2344         return function(state) {
2345             if (state.index !== undefined)
2346                 index.i = state.index;
2347             if (state.rescaleY !== undefined)
2348                 rescaleY = state.rescaleY;
2349             if (state.active !== undefined)
2350                 data.forEach(function(series,i) {
2351                     series.disabled = !state.active[i];
2352                 });
2353         }
2354     };
2355
2356     function chart(selection) {
2357         renderWatch.reset();
2358         renderWatch.models(lines);
2359         if (showXAxis) renderWatch.models(xAxis);
2360         if (showYAxis) renderWatch.models(yAxis);
2361         selection.each(function(data) {
2362             var container = d3.select(this);
2363             nv.utils.initSVG(container);
2364             container.classed('nv-chart-' + id, true);
2365             var that = this;
2366
2367             var availableWidth = (width  || parseInt(container.style('width')) || 960)
2368                     - margin.left - margin.right,
2369                 availableHeight = (height || parseInt(container.style('height')) || 400)
2370                     - margin.top - margin.bottom;
2371
2372             chart.update = function() {
2373                 if (duration === 0)
2374                     container.call(chart);
2375                 else
2376                     container.transition().duration(duration).call(chart)
2377             };
2378             chart.container = this;
2379
2380             state
2381                 .setter(stateSetter(data), chart.update)
2382                 .getter(stateGetter(data))
2383                 .update();
2384
2385             // DEPRECATED set state.disableddisabled
2386             state.disabled = data.map(function(d) { return !!d.disabled });
2387
2388             if (!defaultState) {
2389                 var key;
2390                 defaultState = {};
2391                 for (key in state) {
2392                     if (state[key] instanceof Array)
2393                         defaultState[key] = state[key].slice(0);
2394                     else
2395                         defaultState[key] = state[key];
2396                 }
2397             }
2398
2399             var indexDrag = d3.behavior.drag()
2400                 .on('dragstart', dragStart)
2401                 .on('drag', dragMove)
2402                 .on('dragend', dragEnd);
2403
2404
2405             function dragStart(d,i) {
2406                 d3.select(chart.container)
2407                     .style('cursor', 'ew-resize');
2408             }
2409
2410             function dragMove(d,i) {
2411                 index.x = d3.event.x;
2412                 index.i = Math.round(dx.invert(index.x));
2413                 updateZero();
2414             }
2415
2416             function dragEnd(d,i) {
2417                 d3.select(chart.container)
2418                     .style('cursor', 'auto');
2419
2420                 // update state and send stateChange with new index
2421                 state.index = index.i;
2422                 dispatch.stateChange(state);
2423             }
2424
2425             // Display No Data message if there's nothing to show.
2426             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2427                 var noDataText = container.selectAll('.nv-noData').data([noData]);
2428
2429                 noDataText.enter().append('text')
2430                     .attr('class', 'nvd3 nv-noData')
2431                     .attr('dy', '-.7em')
2432                     .style('text-anchor', 'middle');
2433
2434                 noDataText
2435                     .attr('x', margin.left + availableWidth / 2)
2436                     .attr('y', margin.top + availableHeight / 2)
2437                     .text(function(d) { return d });
2438
2439                 return chart;
2440             } else {
2441                 container.selectAll('.nv-noData').remove();
2442             }
2443
2444             // Setup Scales
2445             x = lines.xScale();
2446             y = lines.yScale();
2447
2448             if (!rescaleY) {
2449                 var seriesDomains = data
2450                     .filter(function(series) { return !series.disabled })
2451                     .map(function(series,i) {
2452                         var initialDomain = d3.extent(series.values, lines.y());
2453
2454                         //account for series being disabled when losing 95% or more
2455                         if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2456
2457                         return [
2458                                 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2459                                 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2460                         ];
2461                     });
2462
2463                 var completeDomain = [
2464                     d3.min(seriesDomains, function(d) { return d[0] }),
2465                     d3.max(seriesDomains, function(d) { return d[1] })
2466                 ];
2467
2468                 lines.yDomain(completeDomain);
2469             } else {
2470                 lines.yDomain(null);
2471             }
2472
2473             dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
2474                 .range([0, availableWidth])
2475                 .clamp(true);
2476
2477             var data = indexify(index.i, data);
2478
2479             // Setup containers and skeleton of chart
2480             var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2481             var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2482             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2483             var g = wrap.select('g');
2484
2485             gEnter.append('g').attr('class', 'nv-interactive');
2486             gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2487             gEnter.append('g').attr('class', 'nv-y nv-axis');
2488             gEnter.append('g').attr('class', 'nv-background');
2489             gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2490             gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2491             gEnter.append('g').attr('class', 'nv-legendWrap');
2492             gEnter.append('g').attr('class', 'nv-controlsWrap');
2493
2494             // Legend
2495             if (showLegend) {
2496                 legend.width(availableWidth);
2497
2498                 g.select('.nv-legendWrap')
2499                     .datum(data)
2500                     .call(legend);
2501
2502                 if ( margin.top != legend.height()) {
2503                     margin.top = legend.height();
2504                     availableHeight = (height || parseInt(container.style('height')) || 400)
2505                         - margin.top - margin.bottom;
2506                 }
2507
2508                 g.select('.nv-legendWrap')
2509                     .attr('transform', 'translate(0,' + (-margin.top) +')')
2510             }
2511
2512             // Controls
2513             if (showControls) {
2514                 var controlsData = [
2515                     { key: 'Re-scale y-axis', disabled: !rescaleY }
2516                 ];
2517
2518                 controls
2519                     .width(140)
2520                     .color(['#444', '#444', '#444'])
2521                     .rightAlign(false)
2522                     .margin({top: 5, right: 0, bottom: 5, left: 20})
2523                 ;
2524
2525                 g.select('.nv-controlsWrap')
2526                     .datum(controlsData)
2527                     .attr('transform', 'translate(0,' + (-margin.top) +')')
2528                     .call(controls);
2529             }
2530
2531             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2532
2533             if (rightAlignYAxis) {
2534                 g.select(".nv-y.nv-axis")
2535                     .attr("transform", "translate(" + availableWidth + ",0)");
2536             }
2537
2538             // Show error if series goes below 100%
2539             var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2540
2541             wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2542             if (tempDisabled.length) {
2543                 wrap.append('text').attr('class', 'tempDisabled')
2544                     .attr('x', availableWidth / 2)
2545                     .attr('y', '-.71em')
2546                     .style('text-anchor', 'end')
2547                     .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2548             }
2549
2550             //Set up interactive layer
2551             if (useInteractiveGuideline) {
2552                 interactiveLayer
2553                     .width(availableWidth)
2554                     .height(availableHeight)
2555                     .margin({left:margin.left,top:margin.top})
2556                     .svgContainer(container)
2557                     .xScale(x);
2558                 wrap.select(".nv-interactive").call(interactiveLayer);
2559             }
2560
2561             gEnter.select('.nv-background')
2562                 .append('rect');
2563
2564             g.select('.nv-background rect')
2565                 .attr('width', availableWidth)
2566                 .attr('height', availableHeight);
2567
2568             lines
2569                 //.x(function(d) { return d.x })
2570                 .y(function(d) { return d.display.y })
2571                 .width(availableWidth)
2572                 .height(availableHeight)
2573                 .color(data.map(function(d,i) {
2574                     return d.color || color(d, i);
2575                 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2576
2577             var linesWrap = g.select('.nv-linesWrap')
2578                 .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
2579
2580             linesWrap.call(lines);
2581
2582             //Store a series index number in the data array.
2583             data.forEach(function(d,i) {
2584                 d.seriesIndex = i;
2585             });
2586
2587             var avgLineData = data.filter(function(d) {
2588                 return !d.disabled && !!average(d);
2589             });
2590
2591             var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2592                 .data(avgLineData, function(d) { return d.key; });
2593
2594             var getAvgLineY = function(d) {
2595                 //If average lines go off the svg element, clamp them to the svg bounds.
2596                 var yVal = y(average(d));
2597                 if (yVal < 0) return 0;
2598                 if (yVal > availableHeight) return availableHeight;
2599                 return yVal;
2600             };
2601
2602             avgLines.enter()
2603                 .append('line')
2604                 .style('stroke-width',2)
2605                 .style('stroke-dasharray','10,10')
2606                 .style('stroke',function (d,i) {
2607                     return lines.color()(d,d.seriesIndex);
2608                 })
2609                 .attr('x1',0)
2610                 .attr('x2',availableWidth)
2611                 .attr('y1', getAvgLineY)
2612                 .attr('y2', getAvgLineY);
2613
2614             avgLines
2615                 .style('stroke-opacity',function(d){
2616                     //If average lines go offscreen, make them transparent
2617                     var yVal = y(average(d));
2618                     if (yVal < 0 || yVal > availableHeight) return 0;
2619                     return 1;
2620                 })
2621                 .attr('x1',0)
2622                 .attr('x2',availableWidth)
2623                 .attr('y1', getAvgLineY)
2624                 .attr('y2', getAvgLineY);
2625
2626             avgLines.exit().remove();
2627
2628             //Create index line
2629             var indexLine = linesWrap.selectAll('.nv-indexLine')
2630                 .data([index]);
2631             indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2632                 .attr('width', 3)
2633                 .attr('x', -2)
2634                 .attr('fill', 'red')
2635                 .attr('fill-opacity', .5)
2636                 .style("pointer-events","all")
2637                 .call(indexDrag);
2638
2639             indexLine
2640                 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2641                 .attr('height', availableHeight);
2642
2643             // Setup Axes
2644             if (showXAxis) {
2645                 xAxis
2646                     .scale(x)
2647                     .ticks( nv.utils.calcTicksX(availableWidth/70, data) )
2648                     .tickSize(-availableHeight, 0);
2649
2650                 g.select('.nv-x.nv-axis')
2651                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
2652                 g.select('.nv-x.nv-axis')
2653                     .call(xAxis);
2654             }
2655
2656             if (showYAxis) {
2657                 yAxis
2658                     .scale(y)
2659                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
2660                     .tickSize( -availableWidth, 0);
2661
2662                 g.select('.nv-y.nv-axis')
2663                     .call(yAxis);
2664             }
2665
2666             //============================================================
2667             // Event Handling/Dispatching (in chart's scope)
2668             //------------------------------------------------------------
2669
2670             function updateZero() {
2671                 indexLine
2672                     .data([index]);
2673
2674                 //When dragging the index line, turn off line transitions.
2675                 // Then turn them back on when done dragging.
2676                 var oldDuration = chart.duration();
2677                 chart.duration(0);
2678                 chart.update();
2679                 chart.duration(oldDuration);
2680             }
2681
2682             g.select('.nv-background rect')
2683                 .on('click', function() {
2684                     index.x = d3.mouse(this)[0];
2685                     index.i = Math.round(dx.invert(index.x));
2686
2687                     // update state and send stateChange with new index
2688                     state.index = index.i;
2689                     dispatch.stateChange(state);
2690
2691                     updateZero();
2692                 });
2693
2694             lines.dispatch.on('elementClick', function(e) {
2695                 index.i = e.pointIndex;
2696                 index.x = dx(index.i);
2697
2698                 // update state and send stateChange with new index
2699                 state.index = index.i;
2700                 dispatch.stateChange(state);
2701
2702                 updateZero();
2703             });
2704
2705             controls.dispatch.on('legendClick', function(d,i) {
2706                 d.disabled = !d.disabled;
2707                 rescaleY = !d.disabled;
2708
2709                 state.rescaleY = rescaleY;
2710                 dispatch.stateChange(state);
2711                 chart.update();
2712             });
2713
2714             legend.dispatch.on('stateChange', function(newState) {
2715                 for (var key in newState)
2716                     state[key] = newState[key];
2717                 dispatch.stateChange(state);
2718                 chart.update();
2719             });
2720
2721             interactiveLayer.dispatch.on('elementMousemove', function(e) {
2722                 lines.clearHighlights();
2723                 var singlePoint, pointIndex, pointXLocation, allData = [];
2724
2725                 data
2726                     .filter(function(series, i) {
2727                         series.seriesIndex = i;
2728                         return !series.disabled;
2729                     })
2730                     .forEach(function(series,i) {
2731                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2732                         lines.highlightPoint(i, pointIndex, true);
2733                         var point = series.values[pointIndex];
2734                         if (typeof point === 'undefined') return;
2735                         if (typeof singlePoint === 'undefined') singlePoint = point;
2736                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2737                         allData.push({
2738                             key: series.key,
2739                             value: chart.y()(point, pointIndex),
2740                             color: color(series,series.seriesIndex)
2741                         });
2742                     });
2743
2744                 //Highlight the tooltip entry based on which point the mouse is closest to.
2745                 if (allData.length > 2) {
2746                     var yValue = chart.yScale().invert(e.mouseY);
2747                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
2748                     var threshold = 0.03 * domainExtent;
2749                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
2750                     if (indexToHighlight !== null)
2751                         allData[indexToHighlight].highlight = true;
2752                 }
2753
2754                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
2755                 interactiveLayer.tooltip
2756                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2757                     .chartContainer(that.parentNode)
2758                     .enabled(tooltips)
2759                     .valueFormatter(function(d,i) {
2760                         return yAxis.tickFormat()(d);
2761                     })
2762                     .data(
2763                     {
2764                         value: xValue,
2765                         series: allData
2766                     }
2767                 )();
2768
2769                 interactiveLayer.renderGuideLine(pointXLocation);
2770             });
2771
2772             interactiveLayer.dispatch.on("elementMouseout",function(e) {
2773                 dispatch.tooltipHide();
2774                 lines.clearHighlights();
2775             });
2776
2777             dispatch.on('tooltipShow', function(e) {
2778                 if (tooltips) showTooltip(e, that.parentNode);
2779             });
2780
2781             // Update chart from a state object passed to event handler
2782             dispatch.on('changeState', function(e) {
2783
2784                 if (typeof e.disabled !== 'undefined') {
2785                     data.forEach(function(series,i) {
2786                         series.disabled = e.disabled[i];
2787                     });
2788
2789                     state.disabled = e.disabled;
2790                 }
2791
2792                 if (typeof e.index !== 'undefined') {
2793                     index.i = e.index;
2794                     index.x = dx(index.i);
2795
2796                     state.index = e.index;
2797
2798                     indexLine
2799                         .data([index]);
2800                 }
2801
2802                 if (typeof e.rescaleY !== 'undefined') {
2803                     rescaleY = e.rescaleY;
2804                 }
2805
2806                 chart.update();
2807             });
2808
2809         });
2810
2811         renderWatch.renderEnd('cumulativeLineChart immediate');
2812
2813         return chart;
2814     }
2815
2816     //============================================================
2817     // Event Handling/Dispatching (out of chart's scope)
2818     //------------------------------------------------------------
2819
2820     lines.dispatch.on('elementMouseover.tooltip', function(e) {
2821         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
2822         dispatch.tooltipShow(e);
2823     });
2824
2825     lines.dispatch.on('elementMouseout.tooltip', function(e) {
2826         dispatch.tooltipHide(e);
2827     });
2828
2829     dispatch.on('tooltipHide', function() {
2830         if (tooltips) nv.tooltip.cleanup();
2831     });
2832
2833     //============================================================
2834     // Functions
2835     //------------------------------------------------------------
2836
2837     var indexifyYGetter = null;
2838     /* Normalize the data according to an index point. */
2839     function indexify(idx, data) {
2840         if (!indexifyYGetter) indexifyYGetter = lines.y();
2841         return data.map(function(line, i) {
2842             if (!line.values) {
2843                 return line;
2844             }
2845             var indexValue = line.values[idx];
2846             if (indexValue == null) {
2847                 return line;
2848             }
2849             var v = indexifyYGetter(indexValue, idx);
2850
2851             //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2852             if (v < -.95 && !noErrorCheck) {
2853                 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2854
2855                 line.tempDisabled = true;
2856                 return line;
2857             }
2858
2859             line.tempDisabled = false;
2860
2861             line.values = line.values.map(function(point, pointIndex) {
2862                 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
2863                 return point;
2864             });
2865
2866             return line;
2867         })
2868     }
2869
2870     //============================================================
2871     // Expose Public Variables
2872     //------------------------------------------------------------
2873
2874     // expose chart's sub-components
2875     chart.dispatch = dispatch;
2876     chart.lines = lines;
2877     chart.legend = legend;
2878     chart.xAxis = xAxis;
2879     chart.yAxis = yAxis;
2880     chart.interactiveLayer = interactiveLayer;
2881     chart.state = state;
2882
2883     chart.options = nv.utils.optionsFunc.bind(chart);
2884
2885     chart._options = Object.create({}, {
2886         // simple options, just get/set the necessary values
2887         width:      {get: function(){return width;}, set: function(_){width=_;}},
2888         height:     {get: function(){return height;}, set: function(_){height=_;}},
2889         rescaleY:     {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
2890         showControls:     {get: function(){return showControls;}, set: function(_){showControls=_;}},
2891         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
2892         average: {get: function(){return average;}, set: function(_){average=_;}},
2893         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2894         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2895         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
2896         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
2897         showXAxis:    {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
2898         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
2899         noErrorCheck:    {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
2900
2901         // options that require extra logic in the setter
2902         margin: {get: function(){return margin;}, set: function(_){
2903             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2904             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2905             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2906             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2907         }},
2908         color:  {get: function(){return color;}, set: function(_){
2909             color = nv.utils.getColor(_);
2910             legend.color(color);
2911         }},
2912         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
2913             useInteractiveGuideline = _;
2914             if (_ === true) {
2915                 chart.interactive(false);
2916                 chart.useVoronoi(false);
2917             }
2918         }},
2919         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
2920             rightAlignYAxis = _;
2921             yAxis.orient( (_) ? 'right' : 'left');
2922         }},
2923         duration:    {get: function(){return duration;}, set: function(_){
2924             duration = _;
2925             lines.duration(duration);
2926             xAxis.duration(duration);
2927             yAxis.duration(duration);
2928             renderWatch.reset(duration);
2929         }}
2930     });
2931
2932     nv.utils.inheritOptions(chart, lines);
2933     nv.utils.initOptions(chart);
2934
2935     return chart;
2936 };//TODO: consider deprecating by adding necessary features to multiBar model
2937 nv.models.discreteBar = function() {
2938     "use strict";
2939
2940     //============================================================
2941     // Public Variables with Default Settings
2942     //------------------------------------------------------------
2943
2944     var margin = {top: 0, right: 0, bottom: 0, left: 0}
2945         , width = 960
2946         , height = 500
2947         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2948         , x = d3.scale.ordinal()
2949         , y = d3.scale.linear()
2950         , getX = function(d) { return d.x }
2951         , getY = function(d) { return d.y }
2952         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2953         , color = nv.utils.defaultColor()
2954         , showValues = false
2955         , valueFormat = d3.format(',.2f')
2956         , xDomain
2957         , yDomain
2958         , xRange
2959         , yRange
2960         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
2961         , rectClass = 'discreteBar'
2962         , duration = 250
2963         ;
2964
2965     //============================================================
2966     // Private Variables
2967     //------------------------------------------------------------
2968
2969     var x0, y0;
2970     var renderWatch = nv.utils.renderWatch(dispatch, duration);
2971
2972     function chart(selection) {
2973         renderWatch.reset();
2974         selection.each(function(data) {
2975             var availableWidth = width - margin.left - margin.right,
2976                 availableHeight = height - margin.top - margin.bottom,
2977                 container = d3.select(this);
2978             nv.utils.initSVG(container);
2979
2980             //add series index to each data point for reference
2981             data.forEach(function(series, i) {
2982                 series.values.forEach(function(point) {
2983                     point.series = i;
2984                 });
2985             });
2986
2987             // Setup Scales
2988             // remap and flatten the data for use in calculating the scales' domains
2989             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2990                 data.map(function(d) {
2991                     return d.values.map(function(d,i) {
2992                         return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2993                     })
2994                 });
2995
2996             x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2997                 .rangeBands(xRange || [0, availableWidth], .1);
2998             y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
2999
3000             // If showValues, pad the Y axis range to account for label height
3001             if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3002             else y.range(yRange || [availableHeight, 0]);
3003
3004             //store old scales if they exist
3005             x0 = x0 || x;
3006             y0 = y0 || y.copy().range([y(0),y(0)]);
3007
3008             // Setup containers and skeleton of chart
3009             var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3010             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3011             var gEnter = wrapEnter.append('g');
3012             var g = wrap.select('g');
3013
3014             gEnter.append('g').attr('class', 'nv-groups');
3015             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3016
3017             //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3018             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3019                 .data(function(d) { return d }, function(d) { return d.key });
3020             groups.enter().append('g')
3021                 .style('stroke-opacity', 1e-6)
3022                 .style('fill-opacity', 1e-6);
3023             groups.exit()
3024                 .watchTransition(renderWatch, 'discreteBar: exit groups')
3025                 .style('stroke-opacity', 1e-6)
3026                 .style('fill-opacity', 1e-6)
3027                 .remove();
3028             groups
3029                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3030                 .classed('hover', function(d) { return d.hover });
3031             groups
3032                 .watchTransition(renderWatch, 'discreteBar: groups')
3033                 .style('stroke-opacity', 1)
3034                 .style('fill-opacity', .75);
3035
3036             var bars = groups.selectAll('g.nv-bar')
3037                 .data(function(d) { return d.values });
3038             bars.exit().remove();
3039
3040             var barsEnter = bars.enter().append('g')
3041                 .attr('transform', function(d,i,j) {
3042                     return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3043                 })
3044                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3045                     d3.select(this).classed('hover', true);
3046                     dispatch.elementMouseover({
3047                         value: getY(d,i),
3048                         point: d,
3049                         series: data[d.series],
3050                         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
3051                         pointIndex: i,
3052                         seriesIndex: d.series,
3053                         e: d3.event
3054                     });
3055                 })
3056                 .on('mouseout', function(d,i) {
3057                     d3.select(this).classed('hover', false);
3058                     dispatch.elementMouseout({
3059                         value: getY(d,i),
3060                         point: d,
3061                         series: data[d.series],
3062                         pointIndex: i,
3063                         seriesIndex: d.series,
3064                         e: d3.event
3065                     });
3066                 })
3067                 .on('click', function(d,i) {
3068                     dispatch.elementClick({
3069                         value: getY(d,i),
3070                         point: d,
3071                         series: data[d.series],
3072                         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
3073                         pointIndex: i,
3074                         seriesIndex: d.series,
3075                         e: d3.event
3076                     });
3077                     d3.event.stopPropagation();
3078                 })
3079                 .on('dblclick', function(d,i) {
3080                     dispatch.elementDblClick({
3081                         value: getY(d,i),
3082                         point: d,
3083                         series: data[d.series],
3084                         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
3085                         pointIndex: i,
3086                         seriesIndex: d.series,
3087                         e: d3.event
3088                     });
3089                     d3.event.stopPropagation();
3090                 });
3091
3092             barsEnter.append('rect')
3093                 .attr('height', 0)
3094                 .attr('width', x.rangeBand() * .9 / data.length )
3095
3096             if (showValues) {
3097                 barsEnter.append('text')
3098                     .attr('text-anchor', 'middle')
3099                 ;
3100
3101                 bars.select('text')
3102                     .text(function(d,i) { return valueFormat(getY(d,i)) })
3103                     .watchTransition(renderWatch, 'discreteBar: bars text')
3104                     .attr('x', x.rangeBand() * .9 / 2)
3105                     .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3106
3107                 ;
3108             } else {
3109                 bars.selectAll('text').remove();
3110             }
3111
3112             bars
3113                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3114                 .style('fill', function(d,i) { return d.color || color(d,i) })
3115                 .style('stroke', function(d,i) { return d.color || color(d,i) })
3116                 .select('rect')
3117                 .attr('class', rectClass)
3118                 .watchTransition(renderWatch, 'discreteBar: bars rect')
3119                 .attr('width', x.rangeBand() * .9 / data.length);
3120             bars.watchTransition(renderWatch, 'discreteBar: bars')
3121                 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3122                 .attr('transform', function(d,i) {
3123                     var left = x(getX(d,i)) + x.rangeBand() * .05,
3124                         top = getY(d,i) < 0 ?
3125                             y(0) :
3126                                 y(0) - y(getY(d,i)) < 1 ?
3127                             y(0) - 1 : //make 1 px positive bars show up above y=0
3128                             y(getY(d,i));
3129
3130                     return 'translate(' + left + ', ' + top + ')'
3131                 })
3132                 .select('rect')
3133                 .attr('height', function(d,i) {
3134                     return  Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3135                 });
3136
3137
3138             //store old scales for use in transitions on update
3139             x0 = x.copy();
3140             y0 = y.copy();
3141
3142         });
3143
3144         renderWatch.renderEnd('discreteBar immediate');
3145         return chart;
3146     }
3147
3148     //============================================================
3149     // Expose Public Variables
3150     //------------------------------------------------------------
3151
3152     chart.dispatch = dispatch;
3153     chart.options = nv.utils.optionsFunc.bind(chart);
3154
3155     chart._options = Object.create({}, {
3156         // simple options, just get/set the necessary values
3157         width:   {get: function(){return width;}, set: function(_){width=_;}},
3158         height:  {get: function(){return height;}, set: function(_){height=_;}},
3159         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
3160         showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
3161         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
3162         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
3163         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
3164         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
3165         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3166         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3167         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
3168         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
3169         valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
3170         id:          {get: function(){return id;}, set: function(_){id=_;}},
3171         rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
3172
3173         // options that require extra logic in the setter
3174         margin: {get: function(){return margin;}, set: function(_){
3175             margin.top    = _.top    !== undefined ? _.top    : margin.top;
3176             margin.right  = _.right  !== undefined ? _.right  : margin.right;
3177             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3178             margin.left   = _.left   !== undefined ? _.left   : margin.left;
3179         }},
3180         color:  {get: function(){return color;}, set: function(_){
3181             color = nv.utils.getColor(_);
3182         }},
3183         duration: {get: function(){return duration;}, set: function(_){
3184             duration = _;
3185             renderWatch.reset(duration);
3186         }}
3187     });
3188
3189     nv.utils.initOptions(chart);
3190
3191     return chart;
3192 };
3193
3194 nv.models.discreteBarChart = function() {
3195     "use strict";
3196
3197     //============================================================
3198     // Public Variables with Default Settings
3199     //------------------------------------------------------------
3200
3201     var discretebar = nv.models.discreteBar()
3202         , xAxis = nv.models.axis()
3203         , yAxis = nv.models.axis()
3204         ;
3205
3206     var margin = {top: 15, right: 10, bottom: 50, left: 60}
3207         , width = null
3208         , height = null
3209         , color = nv.utils.getColor()
3210         , showXAxis = true
3211         , showYAxis = true
3212         , rightAlignYAxis = false
3213         , staggerLabels = false
3214         , tooltips = true
3215         , tooltip = function(key, x, y, e, graph) {
3216             return '<h3>' + x + '</h3>' +
3217                 '<p>' +  y + '</p>'
3218         }
3219         , x
3220         , y
3221         , noData = "No Data Available."
3222         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate','renderEnd')
3223         , duration = 250
3224         ;
3225
3226     xAxis
3227         .orient('bottom')
3228         .highlightZero(false)
3229         .showMaxMin(false)
3230         .tickFormat(function(d) { return d })
3231     ;
3232     yAxis
3233         .orient((rightAlignYAxis) ? 'right' : 'left')
3234         .tickFormat(d3.format(',.1f'))
3235     ;
3236
3237     //============================================================
3238     // Private Variables
3239     //------------------------------------------------------------
3240
3241     var showTooltip = function(e, offsetElement) {
3242         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3243             top = e.pos[1] + ( offsetElement.offsetTop || 0),
3244             x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3245             y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3246             content = tooltip(e.series.key, x, y, e, chart);
3247
3248         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3249     };
3250
3251     var renderWatch = nv.utils.renderWatch(dispatch, duration);
3252
3253     function chart(selection) {
3254         renderWatch.reset();
3255         renderWatch.models(discretebar);
3256         if (showXAxis) renderWatch.models(xAxis);
3257         if (showYAxis) renderWatch.models(yAxis);
3258
3259         selection.each(function(data) {
3260             var container = d3.select(this),
3261                 that = this;
3262             nv.utils.initSVG(container);
3263             var availableWidth = (width  || parseInt(container.style('width')) || 960)
3264                     - margin.left - margin.right,
3265                 availableHeight = (height || parseInt(container.style('height')) || 400)
3266                     - margin.top - margin.bottom;
3267
3268             chart.update = function() {
3269                 dispatch.beforeUpdate();
3270                 container.transition().duration(duration).call(chart);
3271             };
3272             chart.container = this;
3273
3274             // Display No Data message if there's nothing to show.
3275             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3276                 var noDataText = container.selectAll('.nv-noData').data([noData]);
3277
3278                 noDataText.enter().append('text')
3279                     .attr('class', 'nvd3 nv-noData')
3280                     .attr('dy', '-.7em')
3281                     .style('text-anchor', 'middle');
3282
3283                 noDataText
3284                     .attr('x', margin.left + availableWidth / 2)
3285                     .attr('y', margin.top + availableHeight / 2)
3286                     .text(function(d) { return d });
3287
3288                 return chart;
3289             } else {
3290                 container.selectAll('.nv-noData').remove();
3291             }
3292
3293             // Setup Scales
3294             x = discretebar.xScale();
3295             y = discretebar.yScale().clamp(true);
3296
3297             // Setup containers and skeleton of chart
3298             var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3299             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3300             var defsEnter = gEnter.append('defs');
3301             var g = wrap.select('g');
3302
3303             gEnter.append('g').attr('class', 'nv-x nv-axis');
3304             gEnter.append('g').attr('class', 'nv-y nv-axis')
3305                 .append('g').attr('class', 'nv-zeroLine')
3306                 .append('line');
3307
3308             gEnter.append('g').attr('class', 'nv-barsWrap');
3309
3310             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3311
3312             if (rightAlignYAxis) {
3313                 g.select(".nv-y.nv-axis")
3314                     .attr("transform", "translate(" + availableWidth + ",0)");
3315             }
3316
3317             // Main Chart Component(s)
3318             discretebar
3319                 .width(availableWidth)
3320                 .height(availableHeight);
3321
3322             var barsWrap = g.select('.nv-barsWrap')
3323                 .datum(data.filter(function(d) { return !d.disabled }))
3324
3325             barsWrap.transition().call(discretebar);
3326
3327
3328             defsEnter.append('clipPath')
3329                 .attr('id', 'nv-x-label-clip-' + discretebar.id())
3330                 .append('rect');
3331
3332             g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3333                 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3334                 .attr('height', 16)
3335                 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3336
3337             // Setup Axes
3338             if (showXAxis) {
3339                 xAxis
3340                     .scale(x)
3341                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
3342                     .tickSize(-availableHeight, 0);
3343
3344                 g.select('.nv-x.nv-axis')
3345                     .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3346                 g.select('.nv-x.nv-axis').call(xAxis);
3347
3348                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3349                 if (staggerLabels) {
3350                     xTicks
3351                         .selectAll('text')
3352                         .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3353                 }
3354             }
3355
3356             if (showYAxis) {
3357                 yAxis
3358                     .scale(y)
3359                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3360                     .tickSize( -availableWidth, 0);
3361
3362                 g.select('.nv-y.nv-axis').call(yAxis);
3363             }
3364
3365             // Zero line
3366             g.select(".nv-zeroLine line")
3367                 .attr("x1",0)
3368                 .attr("x2",availableWidth)
3369                 .attr("y1", y(0))
3370                 .attr("y2", y(0))
3371             ;
3372
3373             //============================================================
3374             // Event Handling/Dispatching (in chart's scope)
3375             //------------------------------------------------------------
3376
3377             dispatch.on('tooltipShow', function(e) {
3378                 if (tooltips) showTooltip(e, that.parentNode);
3379             });
3380
3381         });
3382
3383         renderWatch.renderEnd('discreteBar chart immediate');
3384         return chart;
3385     }
3386
3387     //============================================================
3388     // Event Handling/Dispatching (out of chart's scope)
3389     //------------------------------------------------------------
3390
3391     discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3392         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
3393         dispatch.tooltipShow(e);
3394     });
3395
3396     discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3397         dispatch.tooltipHide(e);
3398     });
3399
3400     dispatch.on('tooltipHide', function() {
3401         if (tooltips) nv.tooltip.cleanup();
3402     });
3403
3404     //============================================================
3405     // Expose Public Variables
3406     //------------------------------------------------------------
3407
3408     chart.dispatch = dispatch;
3409     chart.discretebar = discretebar;
3410     chart.xAxis = xAxis;
3411     chart.yAxis = yAxis;
3412
3413     chart.options = nv.utils.optionsFunc.bind(chart);
3414
3415     chart._options = Object.create({}, {
3416         // simple options, just get/set the necessary values
3417         width:      {get: function(){return width;}, set: function(_){width=_;}},
3418         height:     {get: function(){return height;}, set: function(_){height=_;}},
3419         staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
3420         showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
3421         showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
3422         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
3423         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
3424         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
3425
3426         // options that require extra logic in the setter
3427         margin: {get: function(){return margin;}, set: function(_){
3428             margin.top    = _.top    !== undefined ? _.top    : margin.top;
3429             margin.right  = _.right  !== undefined ? _.right  : margin.right;
3430             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3431             margin.left   = _.left   !== undefined ? _.left   : margin.left;
3432         }},
3433         duration: {get: function(){return duration;}, set: function(_){
3434             duration = _;
3435             renderWatch.reset(duration);
3436             discretebar.duration(duration);
3437             xAxis.duration(duration);
3438             yAxis.duration(duration);
3439         }},
3440         color:  {get: function(){return color;}, set: function(_){
3441             color = nv.utils.getColor(_);
3442             discretebar.color(color);
3443         }},
3444         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
3445             rightAlignYAxis = _;
3446             yAxis.orient( (_) ? 'right' : 'left');
3447         }}
3448     });
3449
3450     nv.utils.inheritOptions(chart, discretebar);
3451     nv.utils.initOptions(chart);
3452
3453     return chart;
3454 }
3455
3456 nv.models.distribution = function() {
3457     "use strict";
3458     //============================================================
3459     // Public Variables with Default Settings
3460     //------------------------------------------------------------
3461
3462     var margin = {top: 0, right: 0, bottom: 0, left: 0}
3463         , width = 400 //technically width or height depending on x or y....
3464         , size = 8
3465         , axis = 'x' // 'x' or 'y'... horizontal or vertical
3466         , getData = function(d) { return d[axis] }  // defaults d.x or d.y
3467         , color = nv.utils.defaultColor()
3468         , scale = d3.scale.linear()
3469         , domain
3470         , duration = 250
3471         , dispatch = d3.dispatch('renderEnd')
3472         ;
3473
3474     //============================================================
3475
3476
3477     //============================================================
3478     // Private Variables
3479     //------------------------------------------------------------
3480
3481     var scale0;
3482     var renderWatch = nv.utils.renderWatch(dispatch, duration);
3483
3484     //============================================================
3485
3486
3487     function chart(selection) {
3488         renderWatch.reset();
3489         selection.each(function(data) {
3490             var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3491                 naxis = axis == 'x' ? 'y' : 'x',
3492                 container = d3.select(this);
3493             nv.utils.initSVG(container);
3494
3495             //------------------------------------------------------------
3496             // Setup Scales
3497
3498             scale0 = scale0 || scale;
3499
3500             //------------------------------------------------------------
3501
3502
3503             //------------------------------------------------------------
3504             // Setup containers and skeleton of chart
3505
3506             var wrap = container.selectAll('g.nv-distribution').data([data]);
3507             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3508             var gEnter = wrapEnter.append('g');
3509             var g = wrap.select('g');
3510
3511             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
3512
3513             //------------------------------------------------------------
3514
3515
3516             var distWrap = g.selectAll('g.nv-dist')
3517                 .data(function(d) { return d }, function(d) { return d.key });
3518
3519             distWrap.enter().append('g');
3520             distWrap
3521                 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
3522                 .style('stroke', function(d,i) { return color(d, i) });
3523
3524             var dist = distWrap.selectAll('line.nv-dist' + axis)
3525                 .data(function(d) { return d.values })
3526             dist.enter().append('line')
3527                 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
3528                 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
3529             renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
3530                 // .transition()
3531                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3532                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3533                 .style('stroke-opacity', 0)
3534                 .remove();
3535             dist
3536                 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
3537                 .attr(naxis + '1', 0)
3538                 .attr(naxis + '2', size);
3539             renderWatch.transition(dist, 'dist')
3540                 // .transition()
3541                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3542                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3543
3544
3545             scale0 = scale.copy();
3546
3547         });
3548         renderWatch.renderEnd('distribution immediate');
3549         return chart;
3550     }
3551
3552
3553     //============================================================
3554     // Expose Public Variables
3555     //------------------------------------------------------------
3556     chart.options = nv.utils.optionsFunc.bind(chart);
3557     chart.dispatch = dispatch;
3558
3559     chart.margin = function(_) {
3560         if (!arguments.length) return margin;
3561         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3562         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3563         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3564         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3565         return chart;
3566     };
3567
3568     chart.width = function(_) {
3569         if (!arguments.length) return width;
3570         width = _;
3571         return chart;
3572     };
3573
3574     chart.axis = function(_) {
3575         if (!arguments.length) return axis;
3576         axis = _;
3577         return chart;
3578     };
3579
3580     chart.size = function(_) {
3581         if (!arguments.length) return size;
3582         size = _;
3583         return chart;
3584     };
3585
3586     chart.getData = function(_) {
3587         if (!arguments.length) return getData;
3588         getData = d3.functor(_);
3589         return chart;
3590     };
3591
3592     chart.scale = function(_) {
3593         if (!arguments.length) return scale;
3594         scale = _;
3595         return chart;
3596     };
3597
3598     chart.color = function(_) {
3599         if (!arguments.length) return color;
3600         color = nv.utils.getColor(_);
3601         return chart;
3602     };
3603
3604     chart.duration = function(_) {
3605         if (!arguments.length) return duration;
3606         duration = _;
3607         renderWatch.reset(duration);
3608         return chart;
3609     };
3610     //============================================================
3611
3612
3613     return chart;
3614 }
3615 //TODO: consider deprecating and using multibar with single series for this
3616 nv.models.historicalBar = function() {
3617     "use strict";
3618
3619     //============================================================
3620     // Public Variables with Default Settings
3621     //------------------------------------------------------------
3622
3623     var margin = {top: 0, right: 0, bottom: 0, left: 0}
3624         , width = null
3625         , height = null
3626         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3627         , x = d3.scale.linear()
3628         , y = d3.scale.linear()
3629         , getX = function(d) { return d.x }
3630         , getY = function(d) { return d.y }
3631         , forceX = []
3632         , forceY = [0]
3633         , padData = false
3634         , clipEdge = true
3635         , color = nv.utils.defaultColor()
3636         , xDomain
3637         , yDomain
3638         , xRange
3639         , yRange
3640         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
3641         , interactive = true
3642         ;
3643
3644     var renderWatch = nv.utils.renderWatch(dispatch, 0);
3645
3646     function chart(selection) {
3647         selection.each(function(data) {
3648             renderWatch.reset();
3649
3650             var container = d3.select(this);
3651             var availableWidth = (width  || parseInt(container.style('width')) || 960)
3652                 - margin.left - margin.right;
3653             var availableHeight = (height || parseInt(container.style('height')) || 400)
3654                 - margin.top - margin.bottom;
3655
3656             nv.utils.initSVG(container);
3657
3658             // Setup Scales
3659             x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
3660
3661             if (padData)
3662                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
3663             else
3664                 x.range(xRange || [0, availableWidth]);
3665
3666             y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
3667                 .range(yRange || [availableHeight, 0]);
3668
3669             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
3670             if (x.domain()[0] === x.domain()[1])
3671                 x.domain()[0] ?
3672                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3673                     : x.domain([-1,1]);
3674
3675             if (y.domain()[0] === y.domain()[1])
3676                 y.domain()[0] ?
3677                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3678                     : y.domain([-1,1]);
3679
3680             // Setup containers and skeleton of chart
3681             var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
3682             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
3683             var defsEnter = wrapEnter.append('defs');
3684             var gEnter = wrapEnter.append('g');
3685             var g = wrap.select('g');
3686
3687             gEnter.append('g').attr('class', 'nv-bars');
3688             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3689
3690             container
3691                 .on('click', function(d,i) {
3692                     dispatch.chartClick({
3693                         data: d,
3694                         index: i,
3695                         pos: d3.event,
3696                         id: id
3697                     });
3698                 });
3699
3700             defsEnter.append('clipPath')
3701                 .attr('id', 'nv-chart-clip-path-' + id)
3702                 .append('rect');
3703
3704             wrap.select('#nv-chart-clip-path-' + id + ' rect')
3705                 .attr('width', availableWidth)
3706                 .attr('height', availableHeight);
3707
3708             g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3709
3710             var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3711                 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
3712             bars.exit().remove();
3713
3714             var barsEnter = bars.enter().append('rect')
3715                 .attr('x', 0 )
3716                 .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
3717                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
3718                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3719                 .on('mouseover', function(d,i) {
3720                     if (!interactive) return;
3721                     d3.select(this).classed('hover', true);
3722                     dispatch.elementMouseover({
3723                         point: d,
3724                         series: data[0],
3725                         pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3726                         pointIndex: i,
3727                         seriesIndex: 0,
3728                         e: d3.event
3729                     });
3730
3731                 })
3732                 .on('mouseout', function(d,i) {
3733                     if (!interactive) return;
3734                     d3.select(this).classed('hover', false);
3735                     dispatch.elementMouseout({
3736                         point: d,
3737                         series: data[0],
3738                         pointIndex: i,
3739                         seriesIndex: 0,
3740                         e: d3.event
3741                     });
3742                 })
3743                 .on('click', function(d,i) {
3744                     if (!interactive) return;
3745                     dispatch.elementClick({
3746                         //label: d[label],
3747                         value: getY(d,i),
3748                         data: d,
3749                         index: i,
3750                         pos: [x(getX(d,i)), y(getY(d,i))],
3751                         e: d3.event,
3752                         id: id
3753                     });
3754                     d3.event.stopPropagation();
3755                 })
3756                 .on('dblclick', function(d,i) {
3757                     if (!interactive) return;
3758                     dispatch.elementDblClick({
3759                         //label: d[label],
3760                         value: getY(d,i),
3761                         data: d,
3762                         index: i,
3763                         pos: [x(getX(d,i)), y(getY(d,i))],
3764                         e: d3.event,
3765                         id: id
3766                     });
3767                     d3.event.stopPropagation();
3768                 });
3769
3770             bars
3771                 .attr('fill', function(d,i) { return color(d, i); })
3772                 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3773                 .watchTransition(renderWatch, 'bars')
3774                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3775                 //TODO: better width calculations that don't assume always uniform data spacing;w
3776                 .attr('width', (availableWidth / data[0].values.length) * .9 );
3777
3778             bars.watchTransition(renderWatch, 'bars')
3779                 .attr('y', function(d,i) {
3780                     var rval = getY(d,i) < 0 ?
3781                         y(0) :
3782                             y(0) - y(getY(d,i)) < 1 ?
3783                         y(0) - 1 :
3784                         y(getY(d,i));
3785                     return nv.utils.NaNtoZero(rval);
3786                 })
3787                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
3788
3789         });
3790
3791         renderWatch.renderEnd('historicalBar immediate');
3792         return chart;
3793     }
3794
3795     //Create methods to allow outside functions to highlight a specific bar.
3796     chart.highlightPoint = function(pointIndex, isHoverOver) {
3797         d3.select(".nv-historicalBar-" + id)
3798             .select(".nv-bars .nv-bar-0-" + pointIndex)
3799             .classed("hover", isHoverOver)
3800         ;
3801     };
3802
3803     chart.clearHighlights = function() {
3804         d3.select(".nv-historicalBar-" + id)
3805             .select(".nv-bars .nv-bar.hover")
3806             .classed("hover", false)
3807         ;
3808     };
3809
3810     //============================================================
3811     // Expose Public Variables
3812     //------------------------------------------------------------
3813
3814     chart.dispatch = dispatch;
3815     chart.options = nv.utils.optionsFunc.bind(chart);
3816
3817     chart._options = Object.create({}, {
3818         // simple options, just get/set the necessary values
3819         width:   {get: function(){return width;}, set: function(_){width=_;}},
3820         height:  {get: function(){return height;}, set: function(_){height=_;}},
3821         forceX:  {get: function(){return forceX;}, set: function(_){forceX=_;}},
3822         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
3823         padData: {get: function(){return padData;}, set: function(_){padData=_;}},
3824         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
3825         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
3826         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
3827         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
3828         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3829         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3830         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
3831         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
3832         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
3833         id:          {get: function(){return id;}, set: function(_){id=_;}},
3834         interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
3835
3836         // options that require extra logic in the setter
3837         margin: {get: function(){return margin;}, set: function(_){
3838             margin.top    = _.top    !== undefined ? _.top    : margin.top;
3839             margin.right  = _.right  !== undefined ? _.right  : margin.right;
3840             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3841             margin.left   = _.left   !== undefined ? _.left   : margin.left;
3842         }},
3843         color:  {get: function(){return color;}, set: function(_){
3844             color = nv.utils.getColor(_);
3845         }}
3846     });
3847
3848     nv.utils.initOptions(chart);
3849
3850     return chart;
3851 };
3852
3853 nv.models.historicalBarChart = function(bar_model) {
3854     "use strict";
3855
3856     //============================================================
3857     // Public Variables with Default Settings
3858     //------------------------------------------------------------
3859
3860     var bars = bar_model || nv.models.historicalBar()
3861         , xAxis = nv.models.axis()
3862         , yAxis = nv.models.axis()
3863         , legend = nv.models.legend()
3864         , interactiveLayer = nv.interactiveGuideline()
3865         ;
3866
3867
3868     var margin = {top: 30, right: 90, bottom: 50, left: 90}
3869         , color = nv.utils.defaultColor()
3870         , width = null
3871         , height = null
3872         , showLegend = false
3873         , showXAxis = true
3874         , showYAxis = true
3875         , rightAlignYAxis = false
3876         , useInteractiveGuideline = false
3877         , tooltips = true
3878         , tooltip = function(key, x, y, e, graph) {
3879             return '<h3>' + key + '</h3>' +
3880                 '<p>' +  y + ' at ' + x + '</p>'
3881         }
3882         , x
3883         , y
3884         , state = {}
3885         , defaultState = null
3886         , noData = 'No Data Available.'
3887         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
3888         , transitionDuration = 250
3889         ;
3890
3891     xAxis
3892         .orient('bottom')
3893         .tickPadding(7)
3894     ;
3895     yAxis
3896         .orient( (rightAlignYAxis) ? 'right' : 'left')
3897     ;
3898
3899     //============================================================
3900     // Private Variables
3901     //------------------------------------------------------------
3902
3903     var showTooltip = function(e, offsetElement) {
3904
3905         // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
3906         if (offsetElement) {
3907             var svg = d3.select(offsetElement).select('svg');
3908             var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
3909             if (viewBox) {
3910                 viewBox = viewBox.split(' ');
3911                 var ratio = parseInt(svg.style('width')) / viewBox[2];
3912                 e.pos[0] = e.pos[0] * ratio;
3913                 e.pos[1] = e.pos[1] * ratio;
3914             }
3915         }
3916
3917         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3918             top = e.pos[1] + ( offsetElement.offsetTop || 0),
3919             x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
3920             y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
3921             content = tooltip(e.series.key, x, y, e, chart);
3922
3923         nv.tooltip.show([left, top], content, null, null, offsetElement);
3924     };
3925     var renderWatch = nv.utils.renderWatch(dispatch, 0);
3926
3927     function chart(selection) {
3928         selection.each(function(data) {
3929             renderWatch.reset();
3930             renderWatch.models(bars);
3931             if (showXAxis) renderWatch.models(xAxis);
3932             if (showYAxis) renderWatch.models(yAxis);
3933
3934             var container = d3.select(this),
3935                 that = this;
3936             nv.utils.initSVG(container);
3937             var availableWidth = (width  || parseInt(container.style('width')) || 960)
3938                     - margin.left - margin.right,
3939                 availableHeight = (height || parseInt(container.style('height')) || 400)
3940                     - margin.top - margin.bottom;
3941
3942
3943             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
3944             chart.container = this;
3945
3946             //set state.disabled
3947             state.disabled = data.map(function(d) { return !!d.disabled });
3948
3949             if (!defaultState) {
3950                 var key;
3951                 defaultState = {};
3952                 for (key in state) {
3953                     if (state[key] instanceof Array)
3954                         defaultState[key] = state[key].slice(0);
3955                     else
3956                         defaultState[key] = state[key];
3957                 }
3958             }
3959
3960             // Display noData message if there's nothing to show.
3961             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3962                 var noDataText = container.selectAll('.nv-noData').data([noData]);
3963
3964                 noDataText.enter().append('text')
3965                     .attr('class', 'nvd3 nv-noData')
3966                     .attr('dy', '-.7em')
3967                     .style('text-anchor', 'middle');
3968
3969                 noDataText
3970                     .attr('x', margin.left + availableWidth / 2)
3971                     .attr('y', margin.top + availableHeight / 2)
3972                     .text(function(d) { return d });
3973
3974                 return chart;
3975             } else {
3976                 container.selectAll('.nv-noData').remove();
3977             }
3978
3979             // Setup Scales
3980             x = bars.xScale();
3981             y = bars.yScale();
3982
3983             // Setup containers and skeleton of chart
3984             var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
3985             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
3986             var g = wrap.select('g');
3987
3988             gEnter.append('g').attr('class', 'nv-x nv-axis');
3989             gEnter.append('g').attr('class', 'nv-y nv-axis');
3990             gEnter.append('g').attr('class', 'nv-barsWrap');
3991             gEnter.append('g').attr('class', 'nv-legendWrap');
3992             gEnter.append('g').attr('class', 'nv-interactive');
3993
3994             // Legend
3995             if (showLegend) {
3996                 legend.width(availableWidth);
3997
3998                 g.select('.nv-legendWrap')
3999                     .datum(data)
4000                     .call(legend);
4001
4002                 if ( margin.top != legend.height()) {
4003                     margin.top = legend.height();
4004                     availableHeight = (height || parseInt(container.style('height')) || 400)
4005                         - margin.top - margin.bottom;
4006                 }
4007
4008                 wrap.select('.nv-legendWrap')
4009                     .attr('transform', 'translate(0,' + (-margin.top) +')')
4010             }
4011             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4012
4013             if (rightAlignYAxis) {
4014                 g.select(".nv-y.nv-axis")
4015                     .attr("transform", "translate(" + availableWidth + ",0)");
4016             }
4017
4018             //Set up interactive layer
4019             if (useInteractiveGuideline) {
4020                 interactiveLayer
4021                     .width(availableWidth)
4022                     .height(availableHeight)
4023                     .margin({left:margin.left, top:margin.top})
4024                     .svgContainer(container)
4025                     .xScale(x);
4026                 wrap.select(".nv-interactive").call(interactiveLayer);
4027             }
4028             bars
4029                 .width(availableWidth)
4030                 .height(availableHeight)
4031                 .color(data.map(function(d,i) {
4032                     return d.color || color(d, i);
4033                 }).filter(function(d,i) { return !data[i].disabled }));
4034
4035             var barsWrap = g.select('.nv-barsWrap')
4036                 .datum(data.filter(function(d) { return !d.disabled }));
4037             barsWrap.transition().call(bars);
4038
4039             // Setup Axes
4040             if (showXAxis) {
4041                 xAxis
4042                     .scale(x)
4043                     .tickSize(-availableHeight, 0);
4044
4045                 g.select('.nv-x.nv-axis')
4046                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
4047                 g.select('.nv-x.nv-axis')
4048                     .transition()
4049                     .call(xAxis);
4050             }
4051
4052             if (showYAxis) {
4053                 yAxis
4054                     .scale(y)
4055                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4056                     .tickSize( -availableWidth, 0);
4057
4058                 g.select('.nv-y.nv-axis')
4059                     .transition()
4060                     .call(yAxis);
4061             }
4062
4063             //============================================================
4064             // Event Handling/Dispatching (in chart's scope)
4065             //------------------------------------------------------------
4066
4067             interactiveLayer.dispatch.on('elementMousemove', function(e) {
4068                 bars.clearHighlights();
4069
4070                 var singlePoint, pointIndex, pointXLocation, allData = [];
4071                 data
4072                     .filter(function(series, i) {
4073                         series.seriesIndex = i;
4074                         return !series.disabled;
4075                     })
4076                     .forEach(function(series,i) {
4077                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4078                         bars.highlightPoint(pointIndex,true);
4079                         var point = series.values[pointIndex];
4080                         if (typeof point === 'undefined') return;
4081                         if (typeof singlePoint === 'undefined') singlePoint = point;
4082                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4083                         allData.push({
4084                             key: series.key,
4085                             value: chart.y()(point, pointIndex),
4086                             color: color(series,series.seriesIndex),
4087                             data: series.values[pointIndex]
4088                         });
4089                     });
4090
4091                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4092                 interactiveLayer.tooltip
4093                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4094                     .chartContainer(that.parentNode)
4095                     .enabled(tooltips)
4096                     .valueFormatter(function(d,i) {
4097                         return yAxis.tickFormat()(d);
4098                     })
4099                     .data(
4100                     {
4101                         value: xValue,
4102                         series: allData
4103                     }
4104                 )();
4105
4106                 interactiveLayer.renderGuideLine(pointXLocation);
4107
4108             });
4109
4110             interactiveLayer.dispatch.on("elementMouseout",function(e) {
4111                 dispatch.tooltipHide();
4112                 bars.clearHighlights();
4113             });
4114
4115             legend.dispatch.on('legendClick', function(d,i) {
4116                 d.disabled = !d.disabled;
4117
4118                 if (!data.filter(function(d) { return !d.disabled }).length) {
4119                     data.map(function(d) {
4120                         d.disabled = false;
4121                         wrap.selectAll('.nv-series').classed('disabled', false);
4122                         return d;
4123                     });
4124                 }
4125
4126                 state.disabled = data.map(function(d) { return !!d.disabled });
4127                 dispatch.stateChange(state);
4128
4129                 selection.transition().call(chart);
4130             });
4131
4132             legend.dispatch.on('legendDblclick', function(d) {
4133                 //Double clicking should always enable current series, and disabled all others.
4134                 data.forEach(function(d) {
4135                     d.disabled = true;
4136                 });
4137                 d.disabled = false;
4138
4139                 state.disabled = data.map(function(d) { return !!d.disabled });
4140                 dispatch.stateChange(state);
4141                 chart.update();
4142             });
4143
4144             dispatch.on('tooltipShow', function(e) {
4145                 if (tooltips) showTooltip(e, that.parentNode);
4146             });
4147
4148             dispatch.on('changeState', function(e) {
4149
4150                 if (typeof e.disabled !== 'undefined') {
4151                     data.forEach(function(series,i) {
4152                         series.disabled = e.disabled[i];
4153                     });
4154
4155                     state.disabled = e.disabled;
4156                 }
4157
4158                 chart.update();
4159             });
4160         });
4161
4162         renderWatch.renderEnd('historicalBarChart immediate');
4163         return chart;
4164     }
4165
4166     //============================================================
4167     // Event Handling/Dispatching (out of chart's scope)
4168     //------------------------------------------------------------
4169
4170     bars.dispatch.on('elementMouseover.tooltip', function(e) {
4171         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
4172         dispatch.tooltipShow(e);
4173     });
4174
4175     bars.dispatch.on('elementMouseout.tooltip', function(e) {
4176         dispatch.tooltipHide(e);
4177     });
4178
4179     dispatch.on('tooltipHide', function() {
4180         if (tooltips) nv.tooltip.cleanup();
4181     });
4182
4183     //============================================================
4184     // Expose Public Variables
4185     //------------------------------------------------------------
4186
4187     // expose chart's sub-components
4188     chart.dispatch = dispatch;
4189     chart.bars = bars;
4190     chart.legend = legend;
4191     chart.xAxis = xAxis;
4192     chart.yAxis = yAxis;
4193     chart.interactiveLayer = interactiveLayer;
4194
4195     chart.options = nv.utils.optionsFunc.bind(chart);
4196
4197     chart._options = Object.create({}, {
4198         // simple options, just get/set the necessary values
4199         width:      {get: function(){return width;}, set: function(_){width=_;}},
4200         height:     {get: function(){return height;}, set: function(_){height=_;}},
4201         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
4202         showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
4203         showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
4204         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
4205         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
4206         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
4207         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
4208
4209         // options that require extra logic in the setter
4210         margin: {get: function(){return margin;}, set: function(_){
4211             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4212             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4213             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4214             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4215         }},
4216         color:  {get: function(){return color;}, set: function(_){
4217             color = nv.utils.getColor(_);
4218             legend.color(color);
4219             bars.color(color);
4220         }},
4221         duration:    {get: function(){return transitionDuration;}, set: function(_){
4222             transitionDuration=_;
4223             renderWatch.reset(transitionDuration);
4224             yAxis.duration(transitionDuration);
4225             xAxis.duration(transitionDuration);
4226         }},
4227         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
4228             rightAlignYAxis = _;
4229             yAxis.orient( (_) ? 'right' : 'left');
4230         }},
4231         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
4232             useInteractiveGuideline = _;
4233             if (_ === true) {
4234                 chart.interactive(false);
4235             }
4236         }}
4237     });
4238
4239     nv.utils.inheritOptions(chart, bars);
4240     nv.utils.initOptions(chart);
4241
4242     return chart;
4243 };
4244
4245
4246 // ohlcChart is just a historical chart with oclc bars and some tweaks
4247 nv.models.ohlcBarChart = function() {
4248     var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
4249
4250     // special default tooltip since we show multiple values per x
4251     chart.useInteractiveGuideline(true);
4252     chart.interactiveLayer.tooltip.contentGenerator(function(data) {
4253         // we assume only one series exists for this chart
4254         var d = data.series[0].data;
4255         // match line colors as defined in nv.d3.css
4256         var color = d.open < d.close ? "2ca02c" : "d62728";
4257         return '' +
4258             '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
4259             '<table>' +
4260             '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
4261             '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
4262             '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
4263             '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
4264             '</table>';
4265     });
4266     return chart;
4267 };nv.models.legend = function() {
4268     "use strict";
4269
4270     //============================================================
4271     // Public Variables with Default Settings
4272     //------------------------------------------------------------
4273
4274     var margin = {top: 5, right: 0, bottom: 5, left: 0}
4275         , width = 400
4276         , height = 20
4277         , getKey = function(d) { return d.key }
4278         , color = nv.utils.defaultColor()
4279         , align = true
4280         , rightAlign = true
4281         , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4282         , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4283         , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4284         ;
4285
4286     function chart(selection) {
4287         selection.each(function(data) {
4288             var availableWidth = width - margin.left - margin.right,
4289                 container = d3.select(this);
4290             nv.utils.initSVG(container);
4291
4292             // Setup containers and skeleton of chart
4293             var wrap = container.selectAll('g.nv-legend').data([data]);
4294             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4295             var g = wrap.select('g');
4296
4297             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4298
4299             var series = g.selectAll('.nv-series')
4300                 .data(function(d) { return d });
4301             var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4302                 .on('mouseover', function(d,i) {
4303                     dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
4304                 })
4305                 .on('mouseout', function(d,i) {
4306                     dispatch.legendMouseout(d,i);
4307                 })
4308                 .on('click', function(d,i) {
4309                     dispatch.legendClick(d,i);
4310                     if (updateState) {
4311                         if (radioButtonMode) {
4312                             //Radio button mode: set every series to disabled,
4313                             //  and enable the clicked series.
4314                             data.forEach(function(series) { series.disabled = true});
4315                             d.disabled = false;
4316                         }
4317                         else {
4318                             d.disabled = !d.disabled;
4319                             if (data.every(function(series) { return series.disabled})) {
4320                                 //the default behavior of NVD3 legends is, if every single series
4321                                 // is disabled, turn all series' back on.
4322                                 data.forEach(function(series) { series.disabled = false});
4323                             }
4324                         }
4325                         dispatch.stateChange({
4326                             disabled: data.map(function(d) { return !!d.disabled })
4327                         });
4328                     }
4329                 })
4330                 .on('dblclick', function(d,i) {
4331                     dispatch.legendDblclick(d,i);
4332                     if (updateState) {
4333                         //the default behavior of NVD3 legends, when double clicking one,
4334                         // is to set all other series' to false, and make the double clicked series enabled.
4335                         data.forEach(function(series) {
4336                             series.disabled = true;
4337                         });
4338                         d.disabled = false;
4339                         dispatch.stateChange({
4340                             disabled: data.map(function(d) { return !!d.disabled })
4341                         });
4342                     }
4343                 });
4344             seriesEnter.append('circle')
4345                 .style('stroke-width', 2)
4346                 .attr('class','nv-legend-symbol')
4347                 .attr('r', 5);
4348             seriesEnter.append('text')
4349                 .attr('text-anchor', 'start')
4350                 .attr('class','nv-legend-text')
4351                 .attr('dy', '.32em')
4352                 .attr('dx', '8');
4353             series.classed('nv-disabled', function(d) { return d.disabled });
4354             series.exit().remove();
4355             series.select('circle')
4356                 .style('fill', function(d,i) { return d.color || color(d,i)})
4357                 .style('stroke', function(d,i) { return d.color || color(d, i) });
4358             series.select('text').text(getKey);
4359
4360             //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4361             // NEW ALIGNING CODE, TODO: clean up
4362             if (align) {
4363
4364                 var seriesWidths = [];
4365                 series.each(function(d,i) {
4366                     var legendText = d3.select(this).select('text');
4367                     var nodeTextLength;
4368                     try {
4369                         nodeTextLength = legendText.node().getComputedTextLength();
4370                         // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
4371                         if(nodeTextLength <= 0) throw Error();
4372                     }
4373                     catch(e) {
4374                         nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4375                     }
4376
4377                     seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4378                 });
4379
4380                 var seriesPerRow = 0;
4381                 var legendWidth = 0;
4382                 var columnWidths = [];
4383
4384                 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4385                     columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4386                     legendWidth += seriesWidths[seriesPerRow++];
4387                 }
4388                 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4389
4390                 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4391                     columnWidths = [];
4392                     seriesPerRow--;
4393
4394                     for (var k = 0; k < seriesWidths.length; k++) {
4395                         if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4396                             columnWidths[k % seriesPerRow] = seriesWidths[k];
4397                     }
4398
4399                     legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4400                         return prev + cur;
4401                     });
4402                 }
4403
4404                 var xPositions = [];
4405                 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4406                     xPositions[i] = curX;
4407                     curX += columnWidths[i];
4408                 }
4409
4410                 series
4411                     .attr('transform', function(d, i) {
4412                         return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4413                     });
4414
4415                 //position legend as far right as possible within the total width
4416                 if (rightAlign) {
4417                     g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4418                 }
4419                 else {
4420                     g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4421                 }
4422
4423                 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4424
4425             } else {
4426
4427                 var ypos = 5,
4428                     newxpos = 5,
4429                     maxwidth = 0,
4430                     xpos;
4431                 series
4432                     .attr('transform', function(d, i) {
4433                         var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4434                         xpos = newxpos;
4435
4436                         if (width < margin.left + margin.right + xpos + length) {
4437                             newxpos = xpos = 5;
4438                             ypos += 20;
4439                         }
4440
4441                         newxpos += length;
4442                         if (newxpos > maxwidth) maxwidth = newxpos;
4443
4444                         return 'translate(' + xpos + ',' + ypos + ')';
4445                     });
4446
4447                 //position legend as far right as possible within the total width
4448                 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4449
4450                 height = margin.top + margin.bottom + ypos + 15;
4451             }
4452         });
4453
4454         return chart;
4455     }
4456
4457     //============================================================
4458     // Expose Public Variables
4459     //------------------------------------------------------------
4460
4461     chart.dispatch = dispatch;
4462     chart.options = nv.utils.optionsFunc.bind(chart);
4463
4464     chart._options = Object.create({}, {
4465         // simple options, just get/set the necessary values
4466         width:      {get: function(){return width;}, set: function(_){width=_;}},
4467         height:     {get: function(){return height;}, set: function(_){height=_;}},
4468         key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
4469         align:      {get: function(){return align;}, set: function(_){align=_;}},
4470         rightAlign:    {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
4471         updateState:    {get: function(){return updateState;}, set: function(_){updateState=_;}},
4472         radioButtonMode:    {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
4473
4474         // options that require extra logic in the setter
4475         margin: {get: function(){return margin;}, set: function(_){
4476             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4477             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4478             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4479             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4480         }},
4481         color:  {get: function(){return color;}, set: function(_){
4482             color = nv.utils.getColor(_);
4483         }}
4484     });
4485
4486     nv.utils.initOptions(chart);
4487
4488     return chart;
4489 };
4490
4491 nv.models.line = function() {
4492     "use strict";
4493     //============================================================
4494     // Public Variables with Default Settings
4495     //------------------------------------------------------------
4496
4497     var  scatter = nv.models.scatter()
4498         ;
4499
4500     var margin = {top: 0, right: 0, bottom: 0, left: 0}
4501         , width = 960
4502         , height = 500
4503         , color = nv.utils.defaultColor() // a function that returns a color
4504         , getX = function(d) { return d.x } // accessor to get the x value from a data point
4505         , getY = function(d) { return d.y } // accessor to get the y value from a data point
4506         , 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
4507         , isArea = function(d) { return d.area } // decides if a line is an area or just a line
4508         , clipEdge = false // if true, masks lines within x and y scale
4509         , x //can be accessed via chart.xScale()
4510         , y //can be accessed via chart.yScale()
4511         , interpolate = "linear" // controls the line interpolation
4512         , duration = 250
4513         , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
4514         ;
4515
4516     scatter
4517         .pointSize(16) // default size
4518         .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
4519     ;
4520
4521     //============================================================
4522
4523
4524     //============================================================
4525     // Private Variables
4526     //------------------------------------------------------------
4527
4528     var x0, y0 //used to store previous scales
4529         , renderWatch = nv.utils.renderWatch(dispatch, duration)
4530         ;
4531
4532     //============================================================
4533
4534
4535     function chart(selection) {
4536         renderWatch.reset();
4537         renderWatch.models(scatter);
4538         selection.each(function(data) {
4539             var availableWidth = width - margin.left - margin.right,
4540                 availableHeight = height - margin.top - margin.bottom,
4541                 container = d3.select(this);
4542             nv.utils.initSVG(container);
4543
4544             // Setup Scales
4545             x = scatter.xScale();
4546             y = scatter.yScale();
4547
4548             x0 = x0 || x;
4549             y0 = y0 || y;
4550
4551             // Setup containers and skeleton of chart
4552             var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
4553             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
4554             var defsEnter = wrapEnter.append('defs');
4555             var gEnter = wrapEnter.append('g');
4556             var g = wrap.select('g');
4557
4558             gEnter.append('g').attr('class', 'nv-groups');
4559             gEnter.append('g').attr('class', 'nv-scatterWrap');
4560
4561             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4562
4563             scatter
4564                 .width(availableWidth)
4565                 .height(availableHeight);
4566
4567             var scatterWrap = wrap.select('.nv-scatterWrap');
4568             scatterWrap.call(scatter);
4569
4570             defsEnter.append('clipPath')
4571                 .attr('id', 'nv-edge-clip-' + scatter.id())
4572                 .append('rect');
4573
4574             wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
4575                 .attr('width', availableWidth)
4576                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
4577
4578             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4579             scatterWrap
4580                 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4581
4582             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
4583                 .data(function(d) { return d }, function(d) { return d.key });
4584             groups.enter().append('g')
4585                 .style('stroke-opacity', 1e-6)
4586                 .style('fill-opacity', 1e-6);
4587
4588             groups.exit().remove();
4589
4590             groups
4591                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
4592                 .classed('hover', function(d) { return d.hover })
4593                 .style('fill', function(d,i){ return color(d, i) })
4594                 .style('stroke', function(d,i){ return color(d, i)});
4595             groups.watchTransition(renderWatch, 'line: groups')
4596                 .style('stroke-opacity', 1)
4597                 .style('fill-opacity', .5);
4598
4599             var areaPaths = groups.selectAll('path.nv-area')
4600                 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
4601             areaPaths.enter().append('path')
4602                 .attr('class', 'nv-area')
4603                 .attr('d', function(d) {
4604                     return d3.svg.area()
4605                         .interpolate(interpolate)
4606                         .defined(defined)
4607                         .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
4608                         .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
4609                         .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4610                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4611                         .apply(this, [d.values])
4612                 });
4613             groups.exit().selectAll('path.nv-area')
4614                 .remove();
4615
4616             areaPaths.watchTransition(renderWatch, 'line: areaPaths')
4617                 .attr('d', function(d) {
4618                     return d3.svg.area()
4619                         .interpolate(interpolate)
4620                         .defined(defined)
4621                         .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
4622                         .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
4623                         .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4624                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4625                         .apply(this, [d.values])
4626                 });
4627
4628             var linePaths = groups.selectAll('path.nv-line')
4629                 .data(function(d) { return [d.values] });
4630             linePaths.enter().append('path')
4631                 .attr('class', 'nv-line')
4632                 .attr('d',
4633                     d3.svg.line()
4634                     .interpolate(interpolate)
4635                     .defined(defined)
4636                     .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
4637                     .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
4638             );
4639
4640             linePaths.watchTransition(renderWatch, 'line: linePaths')
4641                 .attr('d',
4642                     d3.svg.line()
4643                     .interpolate(interpolate)
4644                     .defined(defined)
4645                     .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
4646                     .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
4647             );
4648
4649             //store old scales for use in transitions on update
4650             x0 = x.copy();
4651             y0 = y.copy();
4652         });
4653         renderWatch.renderEnd('line immediate');
4654         return chart;
4655     }
4656
4657
4658     //============================================================
4659     // Expose Public Variables
4660     //------------------------------------------------------------
4661
4662     chart.dispatch = dispatch;
4663     chart.scatter = scatter;
4664     // Pass through events
4665     scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); })
4666     scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); })
4667     scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); })
4668
4669     chart.options = nv.utils.optionsFunc.bind(chart);
4670
4671     chart._options = Object.create({}, {
4672         // simple options, just get/set the necessary values
4673         width:      {get: function(){return width;}, set: function(_){width=_;}},
4674         height:     {get: function(){return height;}, set: function(_){height=_;}},
4675         defined: {get: function(){return defined;}, set: function(_){defined=_;}},
4676         interpolate:      {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
4677         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
4678
4679         // options that require extra logic in the setter
4680         margin: {get: function(){return margin;}, set: function(_){
4681             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4682             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4683             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4684             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4685         }},
4686         duration: {get: function(){return duration;}, set: function(_){
4687             duration = _;
4688             renderWatch.reset(duration);
4689             scatter.duration(duration);
4690         }},
4691         isArea: {get: function(){return isArea;}, set: function(_){
4692             isArea = d3.functor(_);
4693         }},
4694         x: {get: function(){return getX;}, set: function(_){
4695             getX = _;
4696             scatter.x(_);
4697         }},
4698         y: {get: function(){return getY;}, set: function(_){
4699             getY = _;
4700             scatter.y(_);
4701         }},
4702         color:  {get: function(){return color;}, set: function(_){
4703             color = nv.utils.getColor(_);
4704             scatter.color(color);
4705         }}
4706     });
4707
4708     nv.utils.inheritOptions(chart, scatter);
4709     nv.utils.initOptions(chart);
4710
4711     return chart;
4712 };
4713 nv.models.lineChart = function() {
4714     "use strict";
4715
4716     //============================================================
4717     // Public Variables with Default Settings
4718     //------------------------------------------------------------
4719
4720     var lines = nv.models.line()
4721         , xAxis = nv.models.axis()
4722         , yAxis = nv.models.axis()
4723         , legend = nv.models.legend()
4724         , interactiveLayer = nv.interactiveGuideline()
4725         ;
4726
4727     var margin = {top: 30, right: 20, bottom: 50, left: 60}
4728         , color = nv.utils.defaultColor()
4729         , width = null
4730         , height = null
4731         , showLegend = true
4732         , showXAxis = true
4733         , showYAxis = true
4734         , rightAlignYAxis = false
4735         , useInteractiveGuideline = false
4736         , tooltips = true
4737         , tooltip = function(key, x, y, e, graph) {
4738             return '<h3>' + key + '</h3>' +
4739                 '<p>' +  y + ' at ' + x + '</p>'
4740         }
4741         , x
4742         , y
4743         , state = nv.utils.state()
4744         , defaultState = null
4745         , noData = 'No Data Available.'
4746         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
4747         , duration = 250
4748         ;
4749
4750     xAxis
4751         .orient('bottom')
4752         .tickPadding(7)
4753     ;
4754     yAxis
4755         .orient((rightAlignYAxis) ? 'right' : 'left')
4756     ;
4757
4758     //============================================================
4759     // Private Variables
4760     //------------------------------------------------------------
4761
4762     var showTooltip = function(e, offsetElement) {
4763         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4764             top = e.pos[1] + ( offsetElement.offsetTop || 0),
4765             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
4766             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
4767             content = tooltip(e.series.key, x, y, e, chart);
4768
4769         nv.tooltip.show([left, top], content, null, null, offsetElement);
4770     };
4771
4772     var renderWatch = nv.utils.renderWatch(dispatch, duration);
4773
4774     var stateGetter = function(data) {
4775         return function(){
4776             return {
4777                 active: data.map(function(d) { return !d.disabled })
4778             };
4779         }
4780     };
4781
4782     var stateSetter = function(data) {
4783         return function(state) {
4784             if (state.active !== undefined)
4785                 data.forEach(function(series,i) {
4786                     series.disabled = !state.active[i];
4787                 });
4788         }
4789     };
4790
4791     function chart(selection) {
4792         renderWatch.reset();
4793         renderWatch.models(lines);
4794         if (showXAxis) renderWatch.models(xAxis);
4795         if (showYAxis) renderWatch.models(yAxis);
4796
4797         selection.each(function(data) {
4798             var container = d3.select(this),
4799                 that = this;
4800             nv.utils.initSVG(container);
4801             var availableWidth = (width  || parseInt(container.style('width')) || 960)
4802                     - margin.left - margin.right,
4803                 availableHeight = (height || parseInt(container.style('height')) || 400)
4804                     - margin.top - margin.bottom;
4805
4806
4807             chart.update = function() {
4808                 if (duration === 0)
4809                     container.call(chart);
4810                 else
4811                     container.transition().duration(duration).call(chart)
4812             };
4813             chart.container = this;
4814
4815             state
4816                 .setter(stateSetter(data), chart.update)
4817                 .getter(stateGetter(data))
4818                 .update();
4819
4820             // DEPRECATED set state.disableddisabled
4821             state.disabled = data.map(function(d) { return !!d.disabled });
4822
4823             if (!defaultState) {
4824                 var key;
4825                 defaultState = {};
4826                 for (key in state) {
4827                     if (state[key] instanceof Array)
4828                         defaultState[key] = state[key].slice(0);
4829                     else
4830                         defaultState[key] = state[key];
4831                 }
4832             }
4833
4834             // Display noData message if there's nothing to show.
4835             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4836                 var noDataText = container.selectAll('.nv-noData').data([noData]);
4837
4838                 noDataText.enter().append('text')
4839                     .attr('class', 'nvd3 nv-noData')
4840                     .attr('dy', '-.7em')
4841                     .style('text-anchor', 'middle');
4842
4843                 noDataText
4844                     .attr('x', margin.left + availableWidth / 2)
4845                     .attr('y', margin.top + availableHeight / 2)
4846                     .text(function(d) { return d });
4847
4848                 return chart;
4849             } else {
4850                 container.selectAll('.nv-noData').remove();
4851             }
4852
4853
4854             // Setup Scales
4855             x = lines.xScale();
4856             y = lines.yScale();
4857
4858             // Setup containers and skeleton of chart
4859             var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
4860             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
4861             var g = wrap.select('g');
4862
4863             gEnter.append("rect").style("opacity",0);
4864             gEnter.append('g').attr('class', 'nv-x nv-axis');
4865             gEnter.append('g').attr('class', 'nv-y nv-axis');
4866             gEnter.append('g').attr('class', 'nv-linesWrap');
4867             gEnter.append('g').attr('class', 'nv-legendWrap');
4868             gEnter.append('g').attr('class', 'nv-interactive');
4869
4870             g.select("rect")
4871                 .attr("width",availableWidth)
4872                 .attr("height",(availableHeight > 0) ? availableHeight : 0);
4873
4874             // Legend
4875             if (showLegend) {
4876                 legend.width(availableWidth);
4877
4878                 g.select('.nv-legendWrap')
4879                     .datum(data)
4880                     .call(legend);
4881
4882                 if ( margin.top != legend.height()) {
4883                     margin.top = legend.height();
4884                     availableHeight = (height || parseInt(container.style('height')) || 400)
4885                         - margin.top - margin.bottom;
4886                 }
4887
4888                 wrap.select('.nv-legendWrap')
4889                     .attr('transform', 'translate(0,' + (-margin.top) +')')
4890             }
4891
4892             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4893
4894             if (rightAlignYAxis) {
4895                 g.select(".nv-y.nv-axis")
4896                     .attr("transform", "translate(" + availableWidth + ",0)");
4897             }
4898
4899             //Set up interactive layer
4900             if (useInteractiveGuideline) {
4901                 interactiveLayer
4902                     .width(availableWidth)
4903                     .height(availableHeight)
4904                     .margin({left:margin.left, top:margin.top})
4905                     .svgContainer(container)
4906                     .xScale(x);
4907                 wrap.select(".nv-interactive").call(interactiveLayer);
4908             }
4909
4910             lines
4911                 .width(availableWidth)
4912                 .height(availableHeight)
4913                 .color(data.map(function(d,i) {
4914                     return d.color || color(d, i);
4915                 }).filter(function(d,i) { return !data[i].disabled }));
4916
4917
4918             var linesWrap = g.select('.nv-linesWrap')
4919                 .datum(data.filter(function(d) { return !d.disabled }));
4920
4921             linesWrap.call(lines);
4922
4923             // Setup Axes
4924             if (showXAxis) {
4925                 xAxis
4926                     .scale(x)
4927                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
4928                     .tickSize(-availableHeight, 0);
4929
4930                 g.select('.nv-x.nv-axis')
4931                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
4932                 g.select('.nv-x.nv-axis')
4933                     .call(xAxis);
4934             }
4935
4936             if (showYAxis) {
4937                 yAxis
4938                     .scale(y)
4939                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4940                     .tickSize( -availableWidth, 0);
4941
4942                 g.select('.nv-y.nv-axis')
4943                     .call(yAxis);
4944             }
4945
4946             //============================================================
4947             // Event Handling/Dispatching (in chart's scope)
4948             //------------------------------------------------------------
4949
4950             legend.dispatch.on('stateChange', function(newState) {
4951                 for (var key in newState)
4952                     state[key] = newState[key];
4953                 dispatch.stateChange(state);
4954                 chart.update();
4955             });
4956
4957             interactiveLayer.dispatch.on('elementMousemove', function(e) {
4958                 lines.clearHighlights();
4959                 var singlePoint, pointIndex, pointXLocation, allData = [];
4960                 data
4961                     .filter(function(series, i) {
4962                         series.seriesIndex = i;
4963                         return !series.disabled;
4964                     })
4965                     .forEach(function(series,i) {
4966                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4967                         lines.highlightPoint(i, pointIndex, true);
4968                         var point = series.values[pointIndex];
4969                         if (typeof point === 'undefined') return;
4970                         if (typeof singlePoint === 'undefined') singlePoint = point;
4971                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4972                         allData.push({
4973                             key: series.key,
4974                             value: chart.y()(point, pointIndex),
4975                             color: color(series,series.seriesIndex)
4976                         });
4977                     });
4978                 //Highlight the tooltip entry based on which point the mouse is closest to.
4979                 if (allData.length > 2) {
4980                     var yValue = chart.yScale().invert(e.mouseY);
4981                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
4982                     var threshold = 0.03 * domainExtent;
4983                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
4984                     if (indexToHighlight !== null)
4985                         allData[indexToHighlight].highlight = true;
4986                 }
4987
4988                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4989                 interactiveLayer.tooltip
4990                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4991                     .chartContainer(that.parentNode)
4992                     .enabled(tooltips)
4993                     .valueFormatter(function(d,i) {
4994                         return yAxis.tickFormat()(d);
4995                     })
4996                     .data(
4997                     {
4998                         value: xValue,
4999                         series: allData
5000                     }
5001                 )();
5002
5003                 interactiveLayer.renderGuideLine(pointXLocation);
5004
5005             });
5006
5007             interactiveLayer.dispatch.on('elementClick', function(e) {
5008                 var pointXLocation, allData = [];
5009
5010                 data.filter(function(series, i) {
5011                     series.seriesIndex = i;
5012                     return !series.disabled;
5013                 }).forEach(function(series) {
5014                     var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5015                     var point = series.values[pointIndex];
5016                     if (typeof point === 'undefined') return;
5017                     if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5018                     var yPos = chart.yScale()(chart.y()(point,pointIndex));
5019                     allData.push({
5020                         point: point,
5021                         pointIndex: pointIndex,
5022                         pos: [pointXLocation, yPos],
5023                         seriesIndex: series.seriesIndex,
5024                         series: series
5025                     });
5026                 });
5027
5028                 lines.dispatch.elementClick(allData);
5029             });
5030
5031             interactiveLayer.dispatch.on("elementMouseout",function(e) {
5032                 dispatch.tooltipHide();
5033                 lines.clearHighlights();
5034             });
5035
5036             dispatch.on('tooltipShow', function(e) {
5037                 if (tooltips) showTooltip(e, that.parentNode);
5038             });
5039
5040             dispatch.on('changeState', function(e) {
5041
5042                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
5043                     data.forEach(function(series,i) {
5044                         series.disabled = e.disabled[i];
5045                     });
5046
5047                     state.disabled = e.disabled;
5048                 }
5049
5050                 chart.update();
5051             });
5052
5053         });
5054
5055         renderWatch.renderEnd('lineChart immediate');
5056         return chart;
5057     }
5058
5059     //============================================================
5060     // Event Handling/Dispatching (out of chart's scope)
5061     //------------------------------------------------------------
5062
5063     lines.dispatch.on('elementMouseover.tooltip', function(e) {
5064         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5065         dispatch.tooltipShow(e);
5066     });
5067
5068     lines.dispatch.on('elementMouseout.tooltip', function(e) {
5069         dispatch.tooltipHide(e);
5070     });
5071
5072     dispatch.on('tooltipHide', function() {
5073         if (tooltips) nv.tooltip.cleanup();
5074     });
5075
5076     //============================================================
5077     // Expose Public Variables
5078     //------------------------------------------------------------
5079
5080     // expose chart's sub-components
5081     chart.dispatch = dispatch;
5082     chart.lines = lines;
5083     chart.legend = legend;
5084     chart.xAxis = xAxis;
5085     chart.yAxis = yAxis;
5086     chart.interactiveLayer = interactiveLayer;
5087
5088     chart.dispatch = dispatch;
5089     chart.options = nv.utils.optionsFunc.bind(chart);
5090
5091     chart._options = Object.create({}, {
5092         // simple options, just get/set the necessary values
5093         width:      {get: function(){return width;}, set: function(_){width=_;}},
5094         height:     {get: function(){return height;}, set: function(_){height=_;}},
5095         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5096         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
5097         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
5098         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
5099         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
5100         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
5101         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
5102
5103         // options that require extra logic in the setter
5104         margin: {get: function(){return margin;}, set: function(_){
5105             margin.top    = _.top    !== undefined ? _.top    : margin.top;
5106             margin.right  = _.right  !== undefined ? _.right  : margin.right;
5107             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5108             margin.left   = _.left   !== undefined ? _.left   : margin.left;
5109         }},
5110         duration: {get: function(){return duration;}, set: function(_){
5111             duration = _;
5112             renderWatch.reset(duration);
5113             lines.duration(duration);
5114             xAxis.duration(duration);
5115             yAxis.duration(duration);
5116         }},
5117         color:  {get: function(){return color;}, set: function(_){
5118             color = nv.utils.getColor(_);
5119             legend.color(color);
5120             lines.color(color);
5121         }},
5122         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
5123             rightAlignYAxis = _;
5124             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
5125         }},
5126         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
5127             useInteractiveGuideline = _;
5128             if (useInteractiveGuideline) {
5129                 lines.interactive(false);
5130                 lines.useVoronoi(false);
5131             }
5132         }}
5133     });
5134
5135     nv.utils.inheritOptions(chart, lines);
5136     nv.utils.initOptions(chart);
5137
5138     return chart;
5139 };
5140 nv.models.linePlusBarChart = function() {
5141     "use strict";
5142
5143     //============================================================
5144     // Public Variables with Default Settings
5145     //------------------------------------------------------------
5146
5147     var lines = nv.models.line()
5148         , lines2 = nv.models.line()
5149         , bars = nv.models.historicalBar()
5150         , bars2 = nv.models.historicalBar()
5151         , xAxis = nv.models.axis()
5152         , x2Axis = nv.models.axis()
5153         , y1Axis = nv.models.axis()
5154         , y2Axis = nv.models.axis()
5155         , y3Axis = nv.models.axis()
5156         , y4Axis = nv.models.axis()
5157         , legend = nv.models.legend()
5158         , brush = d3.svg.brush()
5159         ;
5160
5161     var margin = {top: 30, right: 30, bottom: 30, left: 60}
5162         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5163         , width = null
5164         , height = null
5165         , getX = function(d) { return d.x }
5166         , getY = function(d) { return d.y }
5167         , color = nv.utils.defaultColor()
5168         , showLegend = true
5169         , focusEnable = true
5170         , focusShowAxisY = false
5171         , focusShowAxisX = true
5172         , focusHeight = 50
5173         , extent
5174         , brushExtent = null
5175         , tooltips = true
5176         , tooltip = function(key, x, y, e, graph) {
5177             return '<h3>' + key + '</h3>' +
5178                 '<p>' +  y + ' at ' + x + '</p>';
5179         }
5180         , x
5181         , x2
5182         , y1
5183         , y2
5184         , y3
5185         , y4
5186         , noData = "No Data Available."
5187         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
5188         , transitionDuration = 0
5189         , state = nv.utils.state()
5190         , defaultState = null
5191         ;
5192
5193     lines
5194         .clipEdge(true)
5195     ;
5196     lines2
5197         .interactive(false)
5198     ;
5199     xAxis
5200         .orient('bottom')
5201         .tickPadding(5)
5202     ;
5203     y1Axis
5204         .orient('left')
5205     ;
5206     y2Axis
5207         .orient('right')
5208     ;
5209     x2Axis
5210         .orient('bottom')
5211         .tickPadding(5)
5212     ;
5213     y3Axis
5214         .orient('left')
5215     ;
5216     y4Axis
5217         .orient('right')
5218     ;
5219
5220     //============================================================
5221     // Private Variables
5222     //------------------------------------------------------------
5223
5224     var showTooltip = function(e, offsetElement) {
5225         if (extent) {
5226             e.pointIndex += Math.ceil(extent[0]);
5227         }
5228         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5229             top = e.pos[1] + ( offsetElement.offsetTop || 0),
5230             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5231             y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5232             content = tooltip(e.series.key, x, y, e, chart);
5233
5234         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5235     };
5236
5237     var stateGetter = function(data) {
5238         return function(){
5239             return {
5240                 active: data.map(function(d) { return !d.disabled })
5241             };
5242         }
5243     };
5244
5245     var stateSetter = function(data) {
5246         return function(state) {
5247             if (state.active !== undefined)
5248                 data.forEach(function(series,i) {
5249                     series.disabled = !state.active[i];
5250                 });
5251         }
5252     };
5253
5254     function chart(selection) {
5255         selection.each(function(data) {
5256             var container = d3.select(this),
5257                 that = this;
5258             nv.utils.initSVG(container);
5259             var availableWidth = (width  || parseInt(container.style('width')) || 960)
5260                     - margin.left - margin.right,
5261                 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5262                     - margin.top - margin.bottom - (focusEnable ? focusHeight : 0) ,
5263                 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
5264
5265             chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
5266             chart.container = this;
5267
5268             state
5269                 .setter(stateSetter(data), chart.update)
5270                 .getter(stateGetter(data))
5271                 .update();
5272
5273             // DEPRECATED set state.disableddisabled
5274             state.disabled = data.map(function(d) { return !!d.disabled });
5275
5276             if (!defaultState) {
5277                 var key;
5278                 defaultState = {};
5279                 for (key in state) {
5280                     if (state[key] instanceof Array)
5281                         defaultState[key] = state[key].slice(0);
5282                     else
5283                         defaultState[key] = state[key];
5284                 }
5285             }
5286
5287             // Display No Data message if there's nothing to show.
5288             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5289                 var noDataText = container.selectAll('.nv-noData').data([noData]);
5290
5291                 noDataText.enter().append('text')
5292                     .attr('class', 'nvd3 nv-noData')
5293                     .attr('dy', '-.7em')
5294                     .style('text-anchor', 'middle');
5295
5296                 noDataText
5297                     .attr('x', margin.left + availableWidth / 2)
5298                     .attr('y', margin.top + availableHeight1 / 2)
5299                     .text(function(d) { return d });
5300
5301                 return chart;
5302             } else {
5303                 container.selectAll('.nv-noData').remove();
5304             }
5305
5306             // Setup Scales
5307             var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5308             var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5309
5310             x = bars.xScale();
5311             x2 = x2Axis.scale();
5312             y1 = bars.yScale();
5313             y2 = lines.yScale();
5314             y3 = bars2.yScale();
5315             y4 = lines2.yScale();
5316
5317             var series1 = data
5318                 .filter(function(d) { return !d.disabled && d.bar })
5319                 .map(function(d) {
5320                     return d.values.map(function(d,i) {
5321                         return { x: getX(d,i), y: getY(d,i) }
5322                     })
5323                 });
5324
5325             var series2 = data
5326                 .filter(function(d) { return !d.disabled && !d.bar })
5327                 .map(function(d) {
5328                     return d.values.map(function(d,i) {
5329                         return { x: getX(d,i), y: getY(d,i) }
5330                     })
5331                 });
5332
5333             x.range([0, availableWidth]);
5334
5335             x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
5336                 .range([0, availableWidth]);
5337
5338             // Setup containers and skeleton of chart
5339             var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5340             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5341             var g = wrap.select('g');
5342
5343             gEnter.append('g').attr('class', 'nv-legendWrap');
5344
5345             // this is the main chart
5346             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
5347             focusEnter.append('g').attr('class', 'nv-x nv-axis');
5348             focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
5349             focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
5350             focusEnter.append('g').attr('class', 'nv-barsWrap');
5351             focusEnter.append('g').attr('class', 'nv-linesWrap');
5352
5353             // context chart is where you can focus in
5354             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
5355             contextEnter.append('g').attr('class', 'nv-x nv-axis');
5356             contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
5357             contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
5358             contextEnter.append('g').attr('class', 'nv-barsWrap');
5359             contextEnter.append('g').attr('class', 'nv-linesWrap');
5360             contextEnter.append('g').attr('class', 'nv-brushBackground');
5361             contextEnter.append('g').attr('class', 'nv-x nv-brush');
5362
5363             //============================================================
5364             // Legend
5365             //------------------------------------------------------------
5366
5367             if (showLegend) {
5368                 legend.width( availableWidth / 2 );
5369
5370                 g.select('.nv-legendWrap')
5371                     .datum(data.map(function(series) {
5372                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5373                         series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5374                         return series;
5375                     }))
5376                     .call(legend);
5377
5378                 if ( margin.top != legend.height()) {
5379                     margin.top = legend.height();
5380                     availableHeight1 = (height || parseInt(container.style('height')) || 400)
5381                         - margin.top - margin.bottom - focusHeight;
5382                 }
5383
5384                 g.select('.nv-legendWrap')
5385                     .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5386             }
5387
5388             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5389
5390             //============================================================
5391             // Context chart (focus chart) components
5392             //------------------------------------------------------------
5393
5394             // hide or show the focus context chart
5395             g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
5396
5397             bars2
5398                 .width(availableWidth)
5399                 .height(availableHeight2)
5400                 .color(data.map(function (d, i) {
5401                     return d.color || color(d, i);
5402                 }).filter(function (d, i) {
5403                     return !data[i].disabled && data[i].bar
5404                 }));
5405             lines2
5406                 .width(availableWidth)
5407                 .height(availableHeight2)
5408                 .color(data.map(function (d, i) {
5409                     return d.color || color(d, i);
5410                 }).filter(function (d, i) {
5411                     return !data[i].disabled && !data[i].bar
5412                 }));
5413
5414             var bars2Wrap = g.select('.nv-context .nv-barsWrap')
5415                 .datum(dataBars.length ? dataBars : [
5416                     {values: []}
5417                 ]);
5418             var lines2Wrap = g.select('.nv-context .nv-linesWrap')
5419                 .datum(!dataLines[0].disabled ? dataLines : [
5420                     {values: []}
5421                 ]);
5422
5423             g.select('.nv-context')
5424                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
5425
5426             bars2Wrap.transition().call(bars2);
5427             lines2Wrap.transition().call(lines2);
5428
5429             // context (focus chart) axis controls
5430             if (focusShowAxisX) {
5431                 x2Axis
5432                     .ticks(nv.utils.calcTicksX(availableWidth / 100, data))
5433                     .tickSize(-availableHeight2, 0);
5434                 g.select('.nv-context .nv-x.nv-axis')
5435                     .attr('transform', 'translate(0,' + y3.range()[0] + ')');
5436                 g.select('.nv-context .nv-x.nv-axis').transition()
5437                     .call(x2Axis);
5438             }
5439
5440             if (focusShowAxisY) {
5441                 y3Axis
5442                     .scale(y3)
5443                     .ticks( availableHeight2 / 36 )
5444                     .tickSize( -availableWidth, 0);
5445                 y4Axis
5446                     .scale(y4)
5447                     .ticks( availableHeight2 / 36 )
5448                     .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
5449
5450                 g.select('.nv-context .nv-y3.nv-axis')
5451                     .style('opacity', dataBars.length ? 1 : 0)
5452                     .attr('transform', 'translate(0,' + x2.range()[0] + ')');
5453                 g.select('.nv-context .nv-y2.nv-axis')
5454                     .style('opacity', dataLines.length ? 1 : 0)
5455                     .attr('transform', 'translate(' + x2.range()[1] + ',0)');
5456
5457                 g.select('.nv-context .nv-y1.nv-axis').transition()
5458                     .call(y3Axis);
5459                 g.select('.nv-context .nv-y2.nv-axis').transition()
5460                     .call(y4Axis);
5461             }
5462
5463             // Setup Brush
5464             brush.x(x2).on('brush', onBrush);
5465
5466             if (brushExtent) brush.extent(brushExtent);
5467
5468             var brushBG = g.select('.nv-brushBackground').selectAll('g')
5469                 .data([brushExtent || brush.extent()]);
5470
5471             var brushBGenter = brushBG.enter()
5472                 .append('g');
5473
5474             brushBGenter.append('rect')
5475                 .attr('class', 'left')
5476                 .attr('x', 0)
5477                 .attr('y', 0)
5478                 .attr('height', availableHeight2);
5479
5480             brushBGenter.append('rect')
5481                 .attr('class', 'right')
5482                 .attr('x', 0)
5483                 .attr('y', 0)
5484                 .attr('height', availableHeight2);
5485
5486             var gBrush = g.select('.nv-x.nv-brush')
5487                 .call(brush);
5488             gBrush.selectAll('rect')
5489                 //.attr('y', -5)
5490                 .attr('height', availableHeight2);
5491             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
5492
5493             //============================================================
5494             // Event Handling/Dispatching (in chart's scope)
5495             //------------------------------------------------------------
5496
5497             legend.dispatch.on('stateChange', function(newState) {
5498                 for (var key in newState)
5499                     state[key] = newState[key];
5500                 dispatch.stateChange(state);
5501                 chart.update();
5502             });
5503
5504             dispatch.on('tooltipShow', function(e) {
5505                 if (tooltips) showTooltip(e, that.parentNode);
5506             });
5507
5508             // Update chart from a state object passed to event handler
5509             dispatch.on('changeState', function(e) {
5510                 if (typeof e.disabled !== 'undefined') {
5511                     data.forEach(function(series,i) {
5512                         series.disabled = e.disabled[i];
5513                     });
5514                     state.disabled = e.disabled;
5515                 }
5516                 chart.update();
5517             });
5518
5519             //============================================================
5520             // Functions
5521             //------------------------------------------------------------
5522
5523             // Taken from crossfilter (http://square.github.com/crossfilter/)
5524             function resizePath(d) {
5525                 var e = +(d == 'e'),
5526                     x = e ? 1 : -1,
5527                     y = availableHeight2 / 3;
5528                 return 'M' + (.5 * x) + ',' + y
5529                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
5530                     + 'V' + (2 * y - 6)
5531                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
5532                     + 'Z'
5533                     + 'M' + (2.5 * x) + ',' + (y + 8)
5534                     + 'V' + (2 * y - 8)
5535                     + 'M' + (4.5 * x) + ',' + (y + 8)
5536                     + 'V' + (2 * y - 8);
5537             }
5538
5539
5540             function updateBrushBG() {
5541                 if (!brush.empty()) brush.extent(brushExtent);
5542                 brushBG
5543                     .data([brush.empty() ? x2.domain() : brushExtent])
5544                     .each(function(d,i) {
5545                         var leftWidth = x2(d[0]) - x2.range()[0],
5546                             rightWidth = x2.range()[1] - x2(d[1]);
5547                         d3.select(this).select('.left')
5548                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
5549
5550                         d3.select(this).select('.right')
5551                             .attr('x', x2(d[1]))
5552                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
5553                     });
5554             }
5555
5556             function onBrush() {
5557                 brushExtent = brush.empty() ? null : brush.extent();
5558                 extent = brush.empty() ? x2.domain() : brush.extent();
5559                 dispatch.brush({extent: extent, brush: brush});
5560                 updateBrushBG();
5561
5562                 // Prepare Main (Focus) Bars and Lines
5563                 bars
5564                     .width(availableWidth)
5565                     .height(availableHeight1)
5566                     .color(data.map(function(d,i) {
5567                         return d.color || color(d, i);
5568                     }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
5569
5570                 lines
5571                     .width(availableWidth)
5572                     .height(availableHeight1)
5573                     .color(data.map(function(d,i) {
5574                         return d.color || color(d, i);
5575                     }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
5576
5577                 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
5578                     .datum(!dataBars.length ? [{values:[]}] :
5579                         dataBars
5580                             .map(function(d,i) {
5581                                 return {
5582                                     key: d.key,
5583                                     values: d.values.filter(function(d,i) {
5584                                         return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
5585                                     })
5586                                 }
5587                             })
5588                 );
5589
5590                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5591                     .datum(dataLines[0].disabled ? [{values:[]}] :
5592                         dataLines
5593                             .map(function(d,i) {
5594                                 return {
5595                                     key: d.key,
5596                                     values: d.values.filter(function(d,i) {
5597                                         return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
5598                                     })
5599                                 }
5600                             })
5601                 );
5602
5603                 // Update Main (Focus) X Axis
5604                 if (dataBars.length) {
5605                     x = bars.xScale();
5606                 } else {
5607                     x = lines.xScale();
5608                 }
5609
5610                 xAxis
5611                     .scale(x)
5612                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5613                     .tickSize(-availableHeight1, 0);
5614
5615                 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
5616
5617                 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
5618                     .call(xAxis);
5619
5620                 // Update Main (Focus) Bars and Lines
5621                 focusBarsWrap.transition().duration(transitionDuration).call(bars);
5622                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
5623
5624                 // Setup and Update Main (Focus) Y Axes
5625                 g.select('.nv-focus .nv-x.nv-axis')
5626                     .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5627
5628                 y1Axis
5629                     .scale(y1)
5630                     .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5631                     .tickSize(-availableWidth, 0);
5632                 y2Axis
5633                     .scale(y2)
5634                     .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5635                     .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
5636
5637                 g.select('.nv-focus .nv-y1.nv-axis')
5638                     .style('opacity', dataBars.length ? 1 : 0);
5639                 g.select('.nv-focus .nv-y2.nv-axis')
5640                     .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
5641                     .attr('transform', 'translate(' + x.range()[1] + ',0)');
5642
5643                 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
5644                     .call(y1Axis);
5645                 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
5646                     .call(y2Axis);
5647             }
5648
5649             onBrush();
5650
5651         });
5652
5653         return chart;
5654     }
5655
5656     //============================================================
5657     // Event Handling/Dispatching (out of chart's scope)
5658     //------------------------------------------------------------
5659
5660     lines.dispatch.on('elementMouseover.tooltip', function(e) {
5661         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5662         dispatch.tooltipShow(e);
5663     });
5664
5665     lines.dispatch.on('elementMouseout.tooltip', function(e) {
5666         dispatch.tooltipHide(e);
5667     });
5668
5669     bars.dispatch.on('elementMouseover.tooltip', function(e) {
5670         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5671         dispatch.tooltipShow(e);
5672     });
5673
5674     bars.dispatch.on('elementMouseout.tooltip', function(e) {
5675         dispatch.tooltipHide(e);
5676     });
5677
5678     dispatch.on('tooltipHide', function() {
5679         if (tooltips) nv.tooltip.cleanup();
5680     });
5681
5682     //============================================================
5683
5684
5685     //============================================================
5686     // Expose Public Variables
5687     //------------------------------------------------------------
5688
5689     // expose chart's sub-components
5690     chart.dispatch = dispatch;
5691     chart.legend = legend;
5692     chart.lines = lines;
5693     chart.lines2 = lines2;
5694     chart.bars = bars;
5695     chart.bars2 = bars2;
5696     chart.xAxis = xAxis;
5697     chart.x2Axis = x2Axis;
5698     chart.y1Axis = y1Axis;
5699     chart.y2Axis = y2Axis;
5700     chart.y3Axis = y3Axis;
5701     chart.y4Axis = y4Axis;
5702
5703     chart.options = nv.utils.optionsFunc.bind(chart);
5704
5705     chart._options = Object.create({}, {
5706         // simple options, just get/set the necessary values
5707         width:      {get: function(){return width;}, set: function(_){width=_;}},
5708         height:     {get: function(){return height;}, set: function(_){height=_;}},
5709         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5710         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
5711         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
5712         brushExtent:    {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
5713         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
5714         focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
5715         focusHeight:    {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
5716         focusShowAxisX:    {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
5717         focusShowAxisY:    {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
5718
5719         // options that require extra logic in the setter
5720         margin: {get: function(){return margin;}, set: function(_){
5721             margin.top    = _.top    !== undefined ? _.top    : margin.top;
5722             margin.right  = _.right  !== undefined ? _.right  : margin.right;
5723             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5724             margin.left   = _.left   !== undefined ? _.left   : margin.left;
5725         }},
5726         duration: {get: function(){return transitionDuration;}, set: function(_){
5727             transitionDuration = _;
5728         }},
5729         color:  {get: function(){return color;}, set: function(_){
5730             color = nv.utils.getColor(_);
5731             legend.color(color);
5732         }},
5733         x: {get: function(){return getX;}, set: function(_){
5734             getX = _;
5735             lines.x(_);
5736             lines2.x(_);
5737             bars.x(_);
5738             bars2.x(_);
5739         }},
5740         y: {get: function(){return getY;}, set: function(_){
5741             getY = _;
5742             lines.y(_);
5743             lines2.y(_);
5744             bars.y(_);
5745             bars2.y(_);
5746         }}
5747     });
5748
5749     nv.utils.inheritOptions(chart, lines);
5750     nv.utils.initOptions(chart);
5751
5752     return chart;
5753 };
5754 nv.models.lineWithFocusChart = function() {
5755     "use strict";
5756
5757     //============================================================
5758     // Public Variables with Default Settings
5759     //------------------------------------------------------------
5760
5761     var lines = nv.models.line()
5762         , lines2 = nv.models.line()
5763         , xAxis = nv.models.axis()
5764         , yAxis = nv.models.axis()
5765         , x2Axis = nv.models.axis()
5766         , y2Axis = nv.models.axis()
5767         , legend = nv.models.legend()
5768         , brush = d3.svg.brush()
5769         ;
5770
5771     var margin = {top: 30, right: 30, bottom: 30, left: 60}
5772         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5773         , color = nv.utils.defaultColor()
5774         , width = null
5775         , height = null
5776         , height2 = 100
5777         , x
5778         , y
5779         , x2
5780         , y2
5781         , showLegend = true
5782         , brushExtent = null
5783         , tooltips = true
5784         , tooltip = function(key, x, y, e, graph) {
5785             return '<h3>' + key + '</h3>' +
5786                 '<p>' +  y + ' at ' + x + '</p>'
5787         }
5788         , noData = "No Data Available."
5789         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
5790         , transitionDuration = 250
5791         , state = nv.utils.state()
5792         , defaultState = null
5793         ;
5794
5795     lines
5796         .clipEdge(true)
5797     ;
5798     lines2
5799         .interactive(false)
5800     ;
5801     xAxis
5802         .orient('bottom')
5803         .tickPadding(5)
5804     ;
5805     yAxis
5806         .orient('left')
5807     ;
5808     x2Axis
5809         .orient('bottom')
5810         .tickPadding(5)
5811     ;
5812     y2Axis
5813         .orient('left')
5814     ;
5815
5816     //============================================================
5817     // Private Variables
5818     //------------------------------------------------------------
5819
5820     var showTooltip = function(e, offsetElement) {
5821         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5822             top = e.pos[1] + ( offsetElement.offsetTop || 0),
5823             x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5824             y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5825             content = tooltip(e.series.key, x, y, e, chart);
5826
5827         nv.tooltip.show([left, top], content, null, null, offsetElement);
5828     };
5829
5830     var stateGetter = function(data) {
5831         return function(){
5832             return {
5833                 active: data.map(function(d) { return !d.disabled })
5834             };
5835         }
5836     };
5837
5838     var stateSetter = function(data) {
5839         return function(state) {
5840             if (state.active !== undefined)
5841                 data.forEach(function(series,i) {
5842                     series.disabled = !state.active[i];
5843                 });
5844         }
5845     };
5846
5847     function chart(selection) {
5848         selection.each(function(data) {
5849             var container = d3.select(this),
5850                 that = this;
5851             nv.utils.initSVG(container);
5852             var availableWidth = (width  || parseInt(container.style('width')) || 960)
5853                     - margin.left - margin.right,
5854                 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5855                     - margin.top - margin.bottom - height2,
5856                 availableHeight2 = height2 - margin2.top - margin2.bottom;
5857
5858             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5859             chart.container = this;
5860
5861             state
5862                 .setter(stateSetter(data), chart.update)
5863                 .getter(stateGetter(data))
5864                 .update();
5865
5866             // DEPRECATED set state.disableddisabled
5867             state.disabled = data.map(function(d) { return !!d.disabled });
5868
5869             if (!defaultState) {
5870                 var key;
5871                 defaultState = {};
5872                 for (key in state) {
5873                     if (state[key] instanceof Array)
5874                         defaultState[key] = state[key].slice(0);
5875                     else
5876                         defaultState[key] = state[key];
5877                 }
5878             }
5879
5880             // Display No Data message if there's nothing to show.
5881             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5882                 var noDataText = container.selectAll('.nv-noData').data([noData]);
5883
5884                 noDataText.enter().append('text')
5885                     .attr('class', 'nvd3 nv-noData')
5886                     .attr('dy', '-.7em')
5887                     .style('text-anchor', 'middle');
5888
5889                 noDataText
5890                     .attr('x', margin.left + availableWidth / 2)
5891                     .attr('y', margin.top + availableHeight1 / 2)
5892                     .text(function(d) { return d });
5893
5894                 return chart;
5895             } else {
5896                 container.selectAll('.nv-noData').remove();
5897             }
5898
5899             // Setup Scales
5900             x = lines.xScale();
5901             y = lines.yScale();
5902             x2 = lines2.xScale();
5903             y2 = lines2.yScale();
5904
5905             // Setup containers and skeleton of chart
5906             var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
5907             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
5908             var g = wrap.select('g');
5909
5910             gEnter.append('g').attr('class', 'nv-legendWrap');
5911
5912             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
5913             focusEnter.append('g').attr('class', 'nv-x nv-axis');
5914             focusEnter.append('g').attr('class', 'nv-y nv-axis');
5915             focusEnter.append('g').attr('class', 'nv-linesWrap');
5916
5917             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
5918             contextEnter.append('g').attr('class', 'nv-x nv-axis');
5919             contextEnter.append('g').attr('class', 'nv-y nv-axis');
5920             contextEnter.append('g').attr('class', 'nv-linesWrap');
5921             contextEnter.append('g').attr('class', 'nv-brushBackground');
5922             contextEnter.append('g').attr('class', 'nv-x nv-brush');
5923
5924             // Legend
5925             if (showLegend) {
5926                 legend.width(availableWidth);
5927
5928                 g.select('.nv-legendWrap')
5929                     .datum(data)
5930                     .call(legend);
5931
5932                 if ( margin.top != legend.height()) {
5933                     margin.top = legend.height();
5934                     availableHeight1 = (height || parseInt(container.style('height')) || 400)
5935                         - margin.top - margin.bottom - height2;
5936                 }
5937
5938                 g.select('.nv-legendWrap')
5939                     .attr('transform', 'translate(0,' + (-margin.top) +')')
5940             }
5941
5942             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5943
5944             // Main Chart Component(s)
5945             lines
5946                 .width(availableWidth)
5947                 .height(availableHeight1)
5948                 .color(
5949                 data
5950                     .map(function(d,i) {
5951                         return d.color || color(d, i);
5952                     })
5953                     .filter(function(d,i) {
5954                         return !data[i].disabled;
5955                     })
5956             );
5957
5958             lines2
5959                 .defined(lines.defined())
5960                 .width(availableWidth)
5961                 .height(availableHeight2)
5962                 .color(
5963                 data
5964                     .map(function(d,i) {
5965                         return d.color || color(d, i);
5966                     })
5967                     .filter(function(d,i) {
5968                         return !data[i].disabled;
5969                     })
5970             );
5971
5972             g.select('.nv-context')
5973                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
5974
5975             var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
5976                 .datum(data.filter(function(d) { return !d.disabled }))
5977
5978             d3.transition(contextLinesWrap).call(lines2);
5979
5980             // Setup Main (Focus) Axes
5981             xAxis
5982                 .scale(x)
5983                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5984                 .tickSize(-availableHeight1, 0);
5985
5986             yAxis
5987                 .scale(y)
5988                 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5989                 .tickSize( -availableWidth, 0);
5990
5991             g.select('.nv-focus .nv-x.nv-axis')
5992                 .attr('transform', 'translate(0,' + availableHeight1 + ')');
5993
5994             // Setup Brush
5995             brush
5996                 .x(x2)
5997                 .on('brush', function() {
5998                     //When brushing, turn off transitions because chart needs to change immediately.
5999                     var oldTransition = chart.duration();
6000                     chart.duration(0);
6001                     onBrush();
6002                     chart.duration(oldTransition);
6003                 });
6004
6005             if (brushExtent) brush.extent(brushExtent);
6006
6007             var brushBG = g.select('.nv-brushBackground').selectAll('g')
6008                 .data([brushExtent || brush.extent()])
6009
6010             var brushBGenter = brushBG.enter()
6011                 .append('g');
6012
6013             brushBGenter.append('rect')
6014                 .attr('class', 'left')
6015                 .attr('x', 0)
6016                 .attr('y', 0)
6017                 .attr('height', availableHeight2);
6018
6019             brushBGenter.append('rect')
6020                 .attr('class', 'right')
6021                 .attr('x', 0)
6022                 .attr('y', 0)
6023                 .attr('height', availableHeight2);
6024
6025             var gBrush = g.select('.nv-x.nv-brush')
6026                 .call(brush);
6027             gBrush.selectAll('rect')
6028                 //.attr('y', -5)
6029                 .attr('height', availableHeight2);
6030             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6031
6032             onBrush();
6033
6034             // Setup Secondary (Context) Axes
6035             x2Axis
6036                 .scale(x2)
6037                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6038                 .tickSize(-availableHeight2, 0);
6039
6040             g.select('.nv-context .nv-x.nv-axis')
6041                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6042             d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6043                 .call(x2Axis);
6044
6045             y2Axis
6046                 .scale(y2)
6047                 .ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
6048                 .tickSize( -availableWidth, 0);
6049
6050             d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6051                 .call(y2Axis);
6052
6053             g.select('.nv-context .nv-x.nv-axis')
6054                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6055
6056             //============================================================
6057             // Event Handling/Dispatching (in chart's scope)
6058             //------------------------------------------------------------
6059
6060             legend.dispatch.on('stateChange', function(newState) {
6061                 for (var key in newState)
6062                     state[key] = newState[key];
6063                 dispatch.stateChange(state);
6064                 chart.update();
6065             });
6066
6067             dispatch.on('tooltipShow', function(e) {
6068                 if (tooltips) showTooltip(e, that.parentNode);
6069             });
6070
6071             dispatch.on('changeState', function(e) {
6072                 if (typeof e.disabled !== 'undefined') {
6073                     data.forEach(function(series,i) {
6074                         series.disabled = e.disabled[i];
6075                     });
6076                 }
6077                 chart.update();
6078             });
6079
6080             //============================================================
6081             // Functions
6082             //------------------------------------------------------------
6083
6084             // Taken from crossfilter (http://square.github.com/crossfilter/)
6085             function resizePath(d) {
6086                 var e = +(d == 'e'),
6087                     x = e ? 1 : -1,
6088                     y = availableHeight2 / 3;
6089                 return 'M' + (.5 * x) + ',' + y
6090                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6091                     + 'V' + (2 * y - 6)
6092                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6093                     + 'Z'
6094                     + 'M' + (2.5 * x) + ',' + (y + 8)
6095                     + 'V' + (2 * y - 8)
6096                     + 'M' + (4.5 * x) + ',' + (y + 8)
6097                     + 'V' + (2 * y - 8);
6098             }
6099
6100
6101             function updateBrushBG() {
6102                 if (!brush.empty()) brush.extent(brushExtent);
6103                 brushBG
6104                     .data([brush.empty() ? x2.domain() : brushExtent])
6105                     .each(function(d,i) {
6106                         var leftWidth = x2(d[0]) - x.range()[0],
6107                             rightWidth = x.range()[1] - x2(d[1]);
6108                         d3.select(this).select('.left')
6109                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
6110
6111                         d3.select(this).select('.right')
6112                             .attr('x', x2(d[1]))
6113                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
6114                     });
6115             }
6116
6117
6118             function onBrush() {
6119                 brushExtent = brush.empty() ? null : brush.extent();
6120                 var extent = brush.empty() ? x2.domain() : brush.extent();
6121
6122                 //The brush extent cannot be less than one.  If it is, don't update the line chart.
6123                 if (Math.abs(extent[0] - extent[1]) <= 1) {
6124                     return;
6125                 }
6126
6127                 dispatch.brush({extent: extent, brush: brush});
6128
6129
6130                 updateBrushBG();
6131
6132                 // Update Main (Focus)
6133                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6134                     .datum(
6135                     data
6136                         .filter(function(d) { return !d.disabled })
6137                         .map(function(d,i) {
6138                             return {
6139                                 key: d.key,
6140                                 area: d.area,
6141                                 values: d.values.filter(function(d,i) {
6142                                     return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6143                                 })
6144                             }
6145                         })
6146                 );
6147                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6148
6149
6150                 // Update Main (Focus) Axes
6151                 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6152                     .call(xAxis);
6153                 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6154                     .call(yAxis);
6155             }
6156         });
6157
6158         return chart;
6159     }
6160
6161     //============================================================
6162     // Event Handling/Dispatching (out of chart's scope)
6163     //------------------------------------------------------------
6164
6165     lines.dispatch.on('elementMouseover.tooltip', function(e) {
6166         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6167         dispatch.tooltipShow(e);
6168     });
6169
6170     lines.dispatch.on('elementMouseout.tooltip', function(e) {
6171         dispatch.tooltipHide(e);
6172     });
6173
6174     dispatch.on('tooltipHide', function() {
6175         if (tooltips) nv.tooltip.cleanup();
6176     });
6177
6178     //============================================================
6179     // Expose Public Variables
6180     //------------------------------------------------------------
6181
6182     // expose chart's sub-components
6183     chart.dispatch = dispatch;
6184     chart.legend = legend;
6185     chart.lines = lines;
6186     chart.lines2 = lines2;
6187     chart.xAxis = xAxis;
6188     chart.yAxis = yAxis;
6189     chart.x2Axis = x2Axis;
6190     chart.y2Axis = y2Axis;
6191
6192     chart.options = nv.utils.optionsFunc.bind(chart);
6193
6194     chart._options = Object.create({}, {
6195         // simple options, just get/set the necessary values
6196         width:      {get: function(){return width;}, set: function(_){width=_;}},
6197         height:     {get: function(){return height;}, set: function(_){height=_;}},
6198         focusHeight:     {get: function(){return height2;}, set: function(_){height2=_;}},
6199         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6200         brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
6201         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
6202         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
6203         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
6204         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
6205
6206         // options that require extra logic in the setter
6207         margin: {get: function(){return margin;}, set: function(_){
6208             margin.top    = _.top    !== undefined ? _.top    : margin.top;
6209             margin.right  = _.right  !== undefined ? _.right  : margin.right;
6210             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6211             margin.left   = _.left   !== undefined ? _.left   : margin.left;
6212         }},
6213         color:  {get: function(){return color;}, set: function(_){
6214             color = nv.utils.getColor(_);
6215             legend.color(color);
6216             // line color is handled above?
6217         }},
6218         interpolate: {get: function(){return lines.interpolate();}, set: function(_){
6219             lines.interpolate(_);
6220             lines2.interpolate(_);
6221         }},
6222         xTickFormat: {get: function(){return xAxis.xTickFormat();}, set: function(_){
6223             xAxis.xTickFormat(_);
6224             x2Axis.xTickFormat(_);
6225         }},
6226         yTickFormat: {get: function(){return yAxis.yTickFormat();}, set: function(_){
6227             yAxis.yTickFormat(_);
6228             y2Axis.yTickFormat(_);
6229         }},
6230         duration:    {get: function(){return transitionDuration;}, set: function(_){
6231             transitionDuration=_;
6232             yAxis.duration(transitionDuration);
6233             xAxis.duration(transitionDuration);
6234         }},
6235         x: {get: function(){return lines.x();}, set: function(_){
6236             lines.x(_);
6237             lines2.x(_);
6238         }},
6239         y: {get: function(){return lines.y();}, set: function(_){
6240             lines.y(_);
6241             lines2.y(_);
6242         }}
6243     });
6244
6245     nv.utils.inheritOptions(chart, lines);
6246     nv.utils.initOptions(chart);
6247
6248     return chart;
6249 };
6250
6251 nv.models.multiBar = function() {
6252     "use strict";
6253
6254     //============================================================
6255     // Public Variables with Default Settings
6256     //------------------------------------------------------------
6257
6258     var margin = {top: 0, right: 0, bottom: 0, left: 0}
6259         , width = 960
6260         , height = 500
6261         , x = d3.scale.ordinal()
6262         , y = d3.scale.linear()
6263         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
6264         , getX = function(d) { return d.x }
6265         , getY = function(d) { return d.y }
6266         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
6267         , clipEdge = true
6268         , stacked = false
6269         , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
6270         , color = nv.utils.defaultColor()
6271         , hideable = false
6272         , barColor = null // adding the ability to set the color for each rather than the whole group
6273         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
6274         , duration = 500
6275         , xDomain
6276         , yDomain
6277         , xRange
6278         , yRange
6279         , groupSpacing = 0.1
6280         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
6281         ;
6282
6283     //============================================================
6284     // Private Variables
6285     //------------------------------------------------------------
6286
6287     var x0, y0 //used to store previous scales
6288         , renderWatch = nv.utils.renderWatch(dispatch, duration)
6289         ;
6290
6291     var last_datalength = 0;
6292
6293     function chart(selection) {
6294         renderWatch.reset();
6295         selection.each(function(data) {
6296             var availableWidth = width - margin.left - margin.right,
6297                 availableHeight = height - margin.top - margin.bottom,
6298                 container = d3.select(this);
6299             nv.utils.initSVG(container);
6300
6301             // This function defines the requirements for render complete
6302             var endFn = function(d, i) {
6303                 if (d.series === data.length - 1 && i === data[0].values.length - 1)
6304                     return true;
6305                 return false;
6306             };
6307
6308             if(hideable && data.length) hideable = [{
6309                 values: data[0].values.map(function(d) {
6310                         return {
6311                             x: d.x,
6312                             y: 0,
6313                             series: d.series,
6314                             size: 0.01
6315                         };}
6316                 )}];
6317
6318             if (stacked)
6319                 data = d3.layout.stack()
6320                     .offset(stackOffset)
6321                     .values(function(d){ return d.values })
6322                     .y(getY)
6323                 (!data.length && hideable ? hideable : data);
6324
6325
6326             //add series index to each data point for reference
6327             data.forEach(function(series, i) {
6328                 series.values.forEach(function(point) {
6329                     point.series = i;
6330                 });
6331             });
6332
6333             // HACK for negative value stacking
6334             if (stacked)
6335                 data[0].values.map(function(d,i) {
6336                     var posBase = 0, negBase = 0;
6337                     data.map(function(d) {
6338                         var f = d.values[i]
6339                         f.size = Math.abs(f.y);
6340                         if (f.y<0)  {
6341                             f.y1 = negBase;
6342                             negBase = negBase - f.size;
6343                         } else
6344                         {
6345                             f.y1 = f.size + posBase;
6346                             posBase = posBase + f.size;
6347                         }
6348                     });
6349                 });
6350
6351             // Setup Scales
6352             // remap and flatten the data for use in calculating the scales' domains
6353             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
6354                 data.map(function(d) {
6355                     return d.values.map(function(d,i) {
6356                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
6357                     })
6358                 });
6359
6360             x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
6361                 .rangeBands(xRange || [0, availableWidth], groupSpacing);
6362
6363             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)))
6364                 .range(yRange || [availableHeight, 0]);
6365
6366             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
6367             if (x.domain()[0] === x.domain()[1])
6368                 x.domain()[0] ?
6369                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
6370                     : x.domain([-1,1]);
6371
6372             if (y.domain()[0] === y.domain()[1])
6373                 y.domain()[0] ?
6374                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
6375                     : y.domain([-1,1]);
6376
6377             x0 = x0 || x;
6378             y0 = y0 || y;
6379
6380             // Setup containers and skeleton of chart
6381             var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
6382             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
6383             var defsEnter = wrapEnter.append('defs');
6384             var gEnter = wrapEnter.append('g');
6385             var g = wrap.select('g')
6386
6387             gEnter.append('g').attr('class', 'nv-groups');
6388             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6389
6390             defsEnter.append('clipPath')
6391                 .attr('id', 'nv-edge-clip-' + id)
6392                 .append('rect');
6393             wrap.select('#nv-edge-clip-' + id + ' rect')
6394                 .attr('width', availableWidth)
6395                 .attr('height', availableHeight);
6396
6397             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
6398
6399             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
6400                 .data(function(d) { return d }, function(d,i) { return i });
6401             groups.enter().append('g')
6402                 .style('stroke-opacity', 1e-6)
6403                 .style('fill-opacity', 1e-6);
6404
6405             var exitTransition = renderWatch
6406                 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
6407                 .attr('y', function(d) { return (stacked ? y0(d.y0) : y0(0)) || 0 })
6408                 .attr('height', 0)
6409                 .remove();
6410             if (exitTransition.delay)
6411                 exitTransition.delay(function(d,i) {
6412                     var delay = i * (duration / (last_datalength + 1)) - i;
6413                     return delay;
6414                 });
6415             groups
6416                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
6417                 .classed('hover', function(d) { return d.hover })
6418                 .style('fill', function(d,i){ return color(d, i) })
6419                 .style('stroke', function(d,i){ return color(d, i) });
6420             groups
6421                 .style('stroke-opacity', 1)
6422                 .style('fill-opacity', 0.75);
6423
6424             var bars = groups.selectAll('rect.nv-bar')
6425                 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
6426             bars.exit().remove();
6427
6428             var barsEnter = bars.enter().append('rect')
6429                     .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6430                     .attr('x', function(d,i,j) {
6431                         return stacked ? 0 : (j * x.rangeBand() / data.length )
6432                     })
6433                     .attr('y', function(d) { return y0(stacked ? d.y0 : 0) || 0 })
6434                     .attr('height', 0)
6435                     .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
6436                     .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
6437                 ;
6438             bars
6439                 .style('fill', function(d,i,j){ return color(d, j, i);  })
6440                 .style('stroke', function(d,i,j){ return color(d, j, i); })
6441                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
6442                     d3.select(this).classed('hover', true);
6443                     dispatch.elementMouseover({
6444                         value: getY(d,i),
6445                         point: d,
6446                         series: data[d.series],
6447                         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
6448                         pointIndex: i,
6449                         seriesIndex: d.series,
6450                         e: d3.event
6451                     });
6452                 })
6453                 .on('mouseout', function(d,i) {
6454                     d3.select(this).classed('hover', false);
6455                     dispatch.elementMouseout({
6456                         value: getY(d,i),
6457                         point: d,
6458                         series: data[d.series],
6459                         pointIndex: i,
6460                         seriesIndex: d.series,
6461                         e: d3.event
6462                     });
6463                 })
6464                 .on('click', function(d,i) {
6465                     dispatch.elementClick({
6466                         value: getY(d,i),
6467                         point: d,
6468                         series: data[d.series],
6469                         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
6470                         pointIndex: i,
6471                         seriesIndex: d.series,
6472                         e: d3.event
6473                     });
6474                     d3.event.stopPropagation();
6475                 })
6476                 .on('dblclick', function(d,i) {
6477                     dispatch.elementDblClick({
6478                         value: getY(d,i),
6479                         point: d,
6480                         series: data[d.series],
6481                         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
6482                         pointIndex: i,
6483                         seriesIndex: d.series,
6484                         e: d3.event
6485                     });
6486                     d3.event.stopPropagation();
6487                 });
6488             bars
6489                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6490                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
6491
6492             if (barColor) {
6493                 if (!disabled) disabled = data.map(function() { return true });
6494                 bars
6495                     .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(); })
6496                     .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(); });
6497             }
6498
6499             var barSelection =
6500                 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
6501                     .delay(function(d,i) {
6502                         return i * duration / data[0].values.length;
6503                     });
6504             if (stacked)
6505                 barSelection
6506                     .attr('y', function(d,i) {
6507                         return y((stacked ? d.y1 : 0));
6508                     })
6509                     .attr('height', function(d,i) {
6510                         return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
6511                     })
6512                     .attr('x', function(d,i) {
6513                         return stacked ? 0 : (d.series * x.rangeBand() / data.length )
6514                     })
6515                     .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
6516             else
6517                 barSelection
6518                     .attr('x', function(d,i) {
6519                         return d.series * x.rangeBand() / data.length
6520                     })
6521                     .attr('width', x.rangeBand() / data.length)
6522                     .attr('y', function(d,i) {
6523                         return getY(d,i) < 0 ?
6524                             y(0) :
6525                                 y(0) - y(getY(d,i)) < 1 ?
6526                             y(0) - 1 :
6527                             y(getY(d,i)) || 0;
6528                     })
6529                     .attr('height', function(d,i) {
6530                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
6531                     });
6532
6533             //store old scales for use in transitions on update
6534             x0 = x.copy();
6535             y0 = y.copy();
6536
6537             // keep track of the last data value length for transition calculations
6538             if (data[0] && data[0].values) {
6539                 last_datalength = data[0].values.length;
6540             }
6541
6542         });
6543
6544         renderWatch.renderEnd('multibar immediate');
6545
6546         return chart;
6547     }
6548
6549     //============================================================
6550     // Expose Public Variables
6551     //------------------------------------------------------------
6552
6553     chart.dispatch = dispatch;
6554
6555     chart.options = nv.utils.optionsFunc.bind(chart);
6556
6557     chart._options = Object.create({}, {
6558         // simple options, just get/set the necessary values
6559         width:   {get: function(){return width;}, set: function(_){width=_;}},
6560         height:  {get: function(){return height;}, set: function(_){height=_;}},
6561         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
6562         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
6563         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
6564         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
6565         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
6566         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
6567         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
6568         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
6569         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
6570         stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
6571         stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
6572         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
6573         disabled:    {get: function(){return disabled;}, set: function(_){disabled=_;}},
6574         id:          {get: function(){return id;}, set: function(_){id=_;}},
6575         hideable:    {get: function(){return hideable;}, set: function(_){hideable=_;}},
6576         groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
6577
6578         // options that require extra logic in the setter
6579         margin: {get: function(){return margin;}, set: function(_){
6580             margin.top    = _.top    !== undefined ? _.top    : margin.top;
6581             margin.right  = _.right  !== undefined ? _.right  : margin.right;
6582             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6583             margin.left   = _.left   !== undefined ? _.left   : margin.left;
6584         }},
6585         duration: {get: function(){return duration;}, set: function(_){
6586             duration = _;
6587             renderWatch.reset(duration);
6588         }},
6589         color:  {get: function(){return color;}, set: function(_){
6590             color = nv.utils.getColor(_);
6591         }},
6592         barColor:  {get: function(){return barColor;}, set: function(_){
6593             barColor = nv.utils.getColor(_);
6594         }}
6595     });
6596
6597     nv.utils.initOptions(chart);
6598
6599     return chart;
6600 };
6601
6602 nv.models.multiBarChart = function() {
6603     "use strict";
6604
6605     //============================================================
6606     // Public Variables with Default Settings
6607     //------------------------------------------------------------
6608
6609     var multibar = nv.models.multiBar()
6610         , xAxis = nv.models.axis()
6611         , yAxis = nv.models.axis()
6612         , legend = nv.models.legend()
6613         , controls = nv.models.legend()
6614         ;
6615
6616     var margin = {top: 30, right: 20, bottom: 50, left: 60}
6617         , width = null
6618         , height = null
6619         , color = nv.utils.defaultColor()
6620         , showControls = true
6621         , showLegend = true
6622         , showXAxis = true
6623         , showYAxis = true
6624         , rightAlignYAxis = false
6625         , reduceXTicks = true // if false a tick will show for every data point
6626         , staggerLabels = false
6627         , rotateLabels = 0
6628         , tooltips = true
6629         , tooltip = function(key, x, y, e, graph) {
6630             return '<h3>' + key + '</h3>' +
6631                 '<p>' +  y + ' on ' + x + '</p>'
6632         }
6633         , x //can be accessed via chart.xScale()
6634         , y //can be accessed via chart.yScale()
6635         , state = nv.utils.state()
6636         , defaultState = null
6637         , noData = "No Data Available."
6638         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
6639         , controlWidth = function() { return showControls ? 180 : 0 }
6640         , duration = 250
6641         ;
6642
6643     state.stacked = false // DEPRECATED Maintained for backward compatibility
6644
6645     multibar
6646         .stacked(false)
6647     ;
6648     xAxis
6649         .orient('bottom')
6650         .tickPadding(7)
6651         .highlightZero(true)
6652         .showMaxMin(false)
6653         .tickFormat(function(d) { return d })
6654     ;
6655     yAxis
6656         .orient((rightAlignYAxis) ? 'right' : 'left')
6657         .tickFormat(d3.format(',.1f'))
6658     ;
6659
6660     controls.updateState(false);
6661
6662     //============================================================
6663     // Private Variables
6664     //------------------------------------------------------------
6665
6666     var renderWatch = nv.utils.renderWatch(dispatch);
6667     var stacked = false;
6668
6669     var showTooltip = function(e, offsetElement) {
6670         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6671             top = e.pos[1] + ( offsetElement.offsetTop || 0),
6672             x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
6673             y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
6674             content = tooltip(e.series.key, x, y, e, chart);
6675
6676         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6677     };
6678
6679     var stateGetter = function(data) {
6680         return function(){
6681             return {
6682                 active: data.map(function(d) { return !d.disabled }),
6683                 stacked: stacked
6684             };
6685         }
6686     };
6687
6688     var stateSetter = function(data) {
6689         return function(state) {
6690             if (state.stacked !== undefined)
6691                 stacked = state.stacked;
6692             if (state.active !== undefined)
6693                 data.forEach(function(series,i) {
6694                     series.disabled = !state.active[i];
6695                 });
6696         }
6697     };
6698
6699     function chart(selection) {
6700         renderWatch.reset();
6701         renderWatch.models(multibar);
6702         if (showXAxis) renderWatch.models(xAxis);
6703         if (showYAxis) renderWatch.models(yAxis);
6704
6705         selection.each(function(data) {
6706             var container = d3.select(this),
6707                 that = this;
6708             nv.utils.initSVG(container);
6709             var availableWidth = (width  || parseInt(container.style('width')) || 960)
6710                     - margin.left - margin.right,
6711                 availableHeight = (height || parseInt(container.style('height')) || 400)
6712                     - margin.top - margin.bottom;
6713
6714             chart.update = function() {
6715                 if (duration === 0)
6716                     container.call(chart);
6717                 else
6718                     container.transition()
6719                         .duration(duration)
6720                         .call(chart);
6721             };
6722             chart.container = this;
6723
6724             state
6725                 .setter(stateSetter(data), chart.update)
6726                 .getter(stateGetter(data))
6727                 .update();
6728
6729             // DEPRECATED set state.disableddisabled
6730             state.disabled = data.map(function(d) { return !!d.disabled });
6731
6732             if (!defaultState) {
6733                 var key;
6734                 defaultState = {};
6735                 for (key in state) {
6736                     if (state[key] instanceof Array)
6737                         defaultState[key] = state[key].slice(0);
6738                     else
6739                         defaultState[key] = state[key];
6740                 }
6741             }
6742
6743             // Display noData message if there's nothing to show.
6744             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6745                 var noDataText = container.selectAll('.nv-noData').data([noData]);
6746
6747                 noDataText.enter().append('text')
6748                     .attr('class', 'nvd3 nv-noData')
6749                     .attr('dy', '-.7em')
6750                     .style('text-anchor', 'middle');
6751
6752                 noDataText
6753                     .attr('x', margin.left + availableWidth / 2)
6754                     .attr('y', margin.top + availableHeight / 2)
6755                     .text(function(d) { return d });
6756
6757                 return chart;
6758             } else {
6759                 container.selectAll('.nv-noData').remove();
6760             }
6761
6762             // Setup Scales
6763             x = multibar.xScale();
6764             y = multibar.yScale();
6765
6766             // Setup containers and skeleton of chart
6767             var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
6768             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
6769             var g = wrap.select('g');
6770
6771             gEnter.append('g').attr('class', 'nv-x nv-axis');
6772             gEnter.append('g').attr('class', 'nv-y nv-axis');
6773             gEnter.append('g').attr('class', 'nv-barsWrap');
6774             gEnter.append('g').attr('class', 'nv-legendWrap');
6775             gEnter.append('g').attr('class', 'nv-controlsWrap');
6776
6777             // Legend
6778             if (showLegend) {
6779                 legend.width(availableWidth - controlWidth());
6780
6781                 if (multibar.barColor())
6782                     data.forEach(function(series,i) {
6783                         series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
6784                     });
6785
6786                 g.select('.nv-legendWrap')
6787                     .datum(data)
6788                     .call(legend);
6789
6790                 if ( margin.top != legend.height()) {
6791                     margin.top = legend.height();
6792                     availableHeight = (height || parseInt(container.style('height')) || 400)
6793                         - margin.top - margin.bottom;
6794                 }
6795
6796                 g.select('.nv-legendWrap')
6797                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
6798             }
6799
6800             // Controls
6801             if (showControls) {
6802                 var controlsData = [
6803                     { key: 'Grouped', disabled: multibar.stacked() },
6804                     { key: 'Stacked', disabled: !multibar.stacked() }
6805                 ];
6806
6807                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
6808                 g.select('.nv-controlsWrap')
6809                     .datum(controlsData)
6810                     .attr('transform', 'translate(0,' + (-margin.top) +')')
6811                     .call(controls);
6812             }
6813
6814             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6815             if (rightAlignYAxis) {
6816                 g.select(".nv-y.nv-axis")
6817                     .attr("transform", "translate(" + availableWidth + ",0)");
6818             }
6819
6820             // Main Chart Component(s)
6821             multibar
6822                 .disabled(data.map(function(series) { return series.disabled }))
6823                 .width(availableWidth)
6824                 .height(availableHeight)
6825                 .color(data.map(function(d,i) {
6826                     return d.color || color(d, i);
6827                 }).filter(function(d,i) { return !data[i].disabled }));
6828
6829
6830             var barsWrap = g.select('.nv-barsWrap')
6831                 .datum(data.filter(function(d) { return !d.disabled }));
6832
6833             barsWrap.call(multibar);
6834
6835             // Setup Axes
6836             if (showXAxis) {
6837                 xAxis
6838                     .scale(x)
6839                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6840                     .tickSize(-availableHeight, 0);
6841
6842                 g.select('.nv-x.nv-axis')
6843                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
6844                 g.select('.nv-x.nv-axis')
6845                     .call(xAxis);
6846
6847                 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
6848
6849                 xTicks
6850                     .selectAll('line, text')
6851                     .style('opacity', 1)
6852
6853                 if (staggerLabels) {
6854                     var getTranslate = function(x,y) {
6855                         return "translate(" + x + "," + y + ")";
6856                     };
6857
6858                     var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
6859                     // Issue #140
6860                     xTicks
6861                         .selectAll("text")
6862                         .attr('transform', function(d,i,j) {
6863                             return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
6864                         });
6865
6866                     var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
6867                     g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
6868                         .attr("transform", function(d,i) {
6869                             return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
6870                         });
6871                 }
6872
6873                 if (reduceXTicks)
6874                     xTicks
6875                         .filter(function(d,i) {
6876                             return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
6877                         })
6878                         .selectAll('text, line')
6879                         .style('opacity', 0);
6880
6881                 if(rotateLabels)
6882                     xTicks
6883                         .selectAll('.tick text')
6884                         .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
6885                         .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
6886
6887                 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
6888                     .style('opacity', 1);
6889             }
6890
6891             if (showYAxis) {
6892                 yAxis
6893                     .scale(y)
6894                     .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6895                     .tickSize( -availableWidth, 0);
6896
6897                 g.select('.nv-y.nv-axis')
6898                     .call(yAxis);
6899             }
6900
6901             //============================================================
6902             // Event Handling/Dispatching (in chart's scope)
6903             //------------------------------------------------------------
6904
6905             legend.dispatch.on('stateChange', function(newState) {
6906                 for (var key in newState)
6907                     state[key] = newState[key];
6908                 dispatch.stateChange(state);
6909                 chart.update();
6910             });
6911
6912             controls.dispatch.on('legendClick', function(d,i) {
6913                 if (!d.disabled) return;
6914                 controlsData = controlsData.map(function(s) {
6915                     s.disabled = true;
6916                     return s;
6917                 });
6918                 d.disabled = false;
6919
6920                 switch (d.key) {
6921                     case 'Grouped':
6922                         multibar.stacked(false);
6923                         break;
6924                     case 'Stacked':
6925                         multibar.stacked(true);
6926                         break;
6927                 }
6928
6929                 state.stacked = multibar.stacked();
6930                 dispatch.stateChange(state);
6931
6932                 chart.update();
6933             });
6934
6935             dispatch.on('tooltipShow', function(e) {
6936                 if (tooltips) showTooltip(e, that.parentNode)
6937             });
6938
6939             // Update chart from a state object passed to event handler
6940             dispatch.on('changeState', function(e) {
6941
6942                 if (typeof e.disabled !== 'undefined') {
6943                     data.forEach(function(series,i) {
6944                         series.disabled = e.disabled[i];
6945                     });
6946
6947                     state.disabled = e.disabled;
6948                 }
6949
6950                 if (typeof e.stacked !== 'undefined') {
6951                     multibar.stacked(e.stacked);
6952                     state.stacked = e.stacked;
6953                     stacked = e.stacked;
6954                 }
6955
6956                 chart.update();
6957             });
6958         });
6959
6960         renderWatch.renderEnd('multibarchart immediate');
6961         return chart;
6962     }
6963
6964     //============================================================
6965     // Event Handling/Dispatching (out of chart's scope)
6966     //------------------------------------------------------------
6967
6968     multibar.dispatch.on('elementMouseover.tooltip', function(e) {
6969         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6970         dispatch.tooltipShow(e);
6971     });
6972
6973     multibar.dispatch.on('elementMouseout.tooltip', function(e) {
6974         dispatch.tooltipHide(e);
6975     });
6976     dispatch.on('tooltipHide', function() {
6977         if (tooltips) nv.tooltip.cleanup();
6978     });
6979
6980     //============================================================
6981     // Expose Public Variables
6982     //------------------------------------------------------------
6983
6984     // expose chart's sub-components
6985     chart.dispatch = dispatch;
6986     chart.multibar = multibar;
6987     chart.legend = legend;
6988     chart.xAxis = xAxis;
6989     chart.yAxis = yAxis;
6990     chart.state = state;
6991
6992     chart.options = nv.utils.optionsFunc.bind(chart);
6993
6994     chart._options = Object.create({}, {
6995         // simple options, just get/set the necessary values
6996         width:      {get: function(){return width;}, set: function(_){width=_;}},
6997         height:     {get: function(){return height;}, set: function(_){height=_;}},
6998         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6999         showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
7000         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
7001         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
7002         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
7003         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
7004         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7005         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
7006         reduceXTicks:    {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
7007         rotateLabels:    {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
7008         staggerLabels:    {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
7009
7010         // options that require extra logic in the setter
7011         margin: {get: function(){return margin;}, set: function(_){
7012             margin.top    = _.top    !== undefined ? _.top    : margin.top;
7013             margin.right  = _.right  !== undefined ? _.right  : margin.right;
7014             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7015             margin.left   = _.left   !== undefined ? _.left   : margin.left;
7016         }},
7017         duration: {get: function(){return duration;}, set: function(_){
7018             duration = _;
7019             multibar.duration(duration);
7020             xAxis.duration(duration);
7021             yAxis.duration(duration);
7022             renderWatch.reset(duration);
7023         }},
7024         color:  {get: function(){return color;}, set: function(_){
7025             color = nv.utils.getColor(_);
7026             legend.color(color);
7027         }},
7028         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
7029             rightAlignYAxis = _;
7030             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
7031         }}
7032     });
7033
7034     nv.utils.inheritOptions(chart, multibar);
7035     nv.utils.initOptions(chart);
7036
7037     return chart;
7038 };
7039
7040 nv.models.multiBarHorizontal = function() {
7041     "use strict";
7042
7043     //============================================================
7044     // Public Variables with Default Settings
7045     //------------------------------------------------------------
7046
7047     var margin = {top: 0, right: 0, bottom: 0, left: 0}
7048         , width = 960
7049         , height = 500
7050         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7051         , x = d3.scale.ordinal()
7052         , y = d3.scale.linear()
7053         , getX = function(d) { return d.x }
7054         , getY = function(d) { return d.y }
7055         , getYerr = function(d) { return d.yErr }
7056         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7057         , color = nv.utils.defaultColor()
7058         , barColor = null // adding the ability to set the color for each rather than the whole group
7059         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7060         , stacked = false
7061         , showValues = false
7062         , showBarLabels = false
7063         , valuePadding = 60
7064         , valueFormat = d3.format(',.2f')
7065         , delay = 1200
7066         , xDomain
7067         , yDomain
7068         , xRange
7069         , yRange
7070         , duration = 250
7071         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
7072         ;
7073
7074     //============================================================
7075     // Private Variables
7076     //------------------------------------------------------------
7077
7078     var x0, y0; //used to store previous scales
7079     var renderWatch = nv.utils.renderWatch(dispatch, duration);
7080
7081     function chart(selection) {
7082         renderWatch.reset();
7083         selection.each(function(data) {
7084             var availableWidth = width - margin.left - margin.right,
7085                 availableHeight = height - margin.top - margin.bottom,
7086                 container = d3.select(this);
7087             nv.utils.initSVG(container);
7088
7089             if (stacked)
7090                 data = d3.layout.stack()
7091                     .offset('zero')
7092                     .values(function(d){ return d.values })
7093                     .y(getY)
7094                 (data);
7095
7096             //add series index to each data point for reference
7097             data.forEach(function(series, i) {
7098                 series.values.forEach(function(point) {
7099                     point.series = i;
7100                 });
7101             });
7102
7103             // HACK for negative value stacking
7104             if (stacked)
7105                 data[0].values.map(function(d,i) {
7106                     var posBase = 0, negBase = 0;
7107                     data.map(function(d) {
7108                         var f = d.values[i]
7109                         f.size = Math.abs(f.y);
7110                         if (f.y<0)  {
7111                             f.y1 = negBase - f.size;
7112                             negBase = negBase - f.size;
7113                         } else
7114                         {
7115                             f.y1 = posBase;
7116                             posBase = posBase + f.size;
7117                         }
7118                     });
7119                 });
7120
7121             // Setup Scales
7122             // remap and flatten the data for use in calculating the scales' domains
7123             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7124                 data.map(function(d) {
7125                     return d.values.map(function(d,i) {
7126                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7127                     })
7128                 });
7129
7130             x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7131                 .rangeBands(xRange || [0, availableHeight], .1);
7132
7133             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)))
7134
7135             if (showValues && !stacked)
7136                 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
7137             else
7138                 y.range(yRange || [0, availableWidth]);
7139
7140             x0 = x0 || x;
7141             y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
7142
7143             // Setup containers and skeleton of chart
7144             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
7145             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
7146             var defsEnter = wrapEnter.append('defs');
7147             var gEnter = wrapEnter.append('g');
7148             var g = wrap.select('g');
7149
7150             gEnter.append('g').attr('class', 'nv-groups');
7151             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7152
7153             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7154                 .data(function(d) { return d }, function(d,i) { return i });
7155             groups.enter().append('g')
7156                 .style('stroke-opacity', 1e-6)
7157                 .style('fill-opacity', 1e-6);
7158             groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
7159                 .style('stroke-opacity', 1e-6)
7160                 .style('fill-opacity', 1e-6)
7161                 .remove();
7162             groups
7163                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7164                 .classed('hover', function(d) { return d.hover })
7165                 .style('fill', function(d,i){ return color(d, i) })
7166                 .style('stroke', function(d,i){ return color(d, i) });
7167             groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
7168                 .style('stroke-opacity', 1)
7169                 .style('fill-opacity', .75);
7170
7171             var bars = groups.selectAll('g.nv-bar')
7172                 .data(function(d) { return d.values });
7173             bars.exit().remove();
7174
7175             var barsEnter = bars.enter().append('g')
7176                 .attr('transform', function(d,i,j) {
7177                     return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
7178                 });
7179
7180             barsEnter.append('rect')
7181                 .attr('width', 0)
7182                 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
7183
7184             bars
7185                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7186                     d3.select(this).classed('hover', true);
7187                     dispatch.elementMouseover({
7188                         value: getY(d,i),
7189                         point: d,
7190                         series: data[d.series],
7191                         pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
7192                         pointIndex: i,
7193                         seriesIndex: d.series,
7194                         e: d3.event
7195                     });
7196                 })
7197                 .on('mouseout', function(d,i) {
7198                     d3.select(this).classed('hover', false);
7199                     dispatch.elementMouseout({
7200                         value: getY(d,i),
7201                         point: d,
7202                         series: data[d.series],
7203                         pointIndex: i,
7204                         seriesIndex: d.series,
7205                         e: d3.event
7206                     });
7207                 })
7208                 .on('click', function(d,i) {
7209                     dispatch.elementClick({
7210                         value: getY(d,i),
7211                         point: d,
7212                         series: data[d.series],
7213                         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
7214                         pointIndex: i,
7215                         seriesIndex: d.series,
7216                         e: d3.event
7217                     });
7218                     d3.event.stopPropagation();
7219                 })
7220                 .on('dblclick', function(d,i) {
7221                     dispatch.elementDblClick({
7222                         value: getY(d,i),
7223                         point: d,
7224                         series: data[d.series],
7225                         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
7226                         pointIndex: i,
7227                         seriesIndex: d.series,
7228                         e: d3.event
7229                     });
7230                     d3.event.stopPropagation();
7231                 });
7232
7233             if (getYerr(data[0],0)) {
7234                 barsEnter.append('polyline');
7235
7236                 bars.select('polyline')
7237                     .attr('fill', 'none')
7238                     .attr('points', function(d,i) {
7239                         var xerr = getYerr(d,i)
7240                             , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
7241                         xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
7242                         xerr = xerr.map(function(e) { return y(e) - y(0); });
7243                         var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
7244                         return a.map(function (path) { return path.join(',') }).join(' ');
7245                     })
7246                     .attr('transform', function(d,i) {
7247                         var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
7248                         return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
7249                     });
7250             }
7251
7252             barsEnter.append('text');
7253
7254             if (showValues && !stacked) {
7255                 bars.select('text')
7256                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
7257                     .attr('y', x.rangeBand() / (data.length * 2))
7258                     .attr('dy', '.32em')
7259                     .html(function(d,i) {
7260                         var t = valueFormat(getY(d,i))
7261                             , yerr = getYerr(d,i);
7262                         if (yerr === undefined)
7263                             return t;
7264                         if (!yerr.length)
7265                             return t + '&plusmn;' + valueFormat(Math.abs(yerr));
7266                         return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
7267                     });
7268                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7269                     .select('text')
7270                     .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
7271             } else {
7272                 bars.selectAll('text').text('');
7273             }
7274
7275             if (showBarLabels && !stacked) {
7276                 barsEnter.append('text').classed('nv-bar-label',true);
7277                 bars.select('text.nv-bar-label')
7278                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
7279                     .attr('y', x.rangeBand() / (data.length * 2))
7280                     .attr('dy', '.32em')
7281                     .text(function(d,i) { return getX(d,i) });
7282                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7283                     .select('text.nv-bar-label')
7284                     .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
7285             }
7286             else {
7287                 bars.selectAll('text.nv-bar-label').text('');
7288             }
7289
7290             bars
7291                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7292
7293             if (barColor) {
7294                 if (!disabled) disabled = data.map(function() { return true });
7295                 bars
7296                     .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(); })
7297                     .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(); });
7298             }
7299
7300             if (stacked)
7301                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7302                     .attr('transform', function(d,i) {
7303                         return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
7304                     })
7305                     .select('rect')
7306                     .attr('width', function(d,i) {
7307                         return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
7308                     })
7309                     .attr('height', x.rangeBand() );
7310             else
7311                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7312                     .attr('transform', function(d,i) {
7313                         //TODO: stacked must be all positive or all negative, not both?
7314                         return 'translate(' +
7315                             (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
7316                             + ',' +
7317                             (d.series * x.rangeBand() / data.length
7318                                 +
7319                                 x(getX(d,i)) )
7320                             + ')'
7321                     })
7322                     .select('rect')
7323                     .attr('height', x.rangeBand() / data.length )
7324                     .attr('width', function(d,i) {
7325                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
7326                     });
7327
7328             //store old scales for use in transitions on update
7329             x0 = x.copy();
7330             y0 = y.copy();
7331
7332         });
7333
7334         renderWatch.renderEnd('multibarHorizontal immediate');
7335         return chart;
7336     }
7337
7338     //============================================================
7339     // Expose Public Variables
7340     //------------------------------------------------------------
7341
7342     chart.dispatch = dispatch;
7343
7344     chart.options = nv.utils.optionsFunc.bind(chart);
7345
7346     chart._options = Object.create({}, {
7347         // simple options, just get/set the necessary values
7348         width:   {get: function(){return width;}, set: function(_){width=_;}},
7349         height:  {get: function(){return height;}, set: function(_){height=_;}},
7350         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
7351         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
7352         yErr:       {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
7353         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
7354         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
7355         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
7356         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
7357         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
7358         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
7359         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
7360         stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
7361         showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
7362         // this shows the group name, seems pointless?
7363         //showBarLabels:    {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
7364         disabled:     {get: function(){return disabled;}, set: function(_){disabled=_;}},
7365         id:           {get: function(){return id;}, set: function(_){id=_;}},
7366         valueFormat:  {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
7367         valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
7368
7369         // options that require extra logic in the setter
7370         margin: {get: function(){return margin;}, set: function(_){
7371             margin.top    = _.top    !== undefined ? _.top    : margin.top;
7372             margin.right  = _.right  !== undefined ? _.right  : margin.right;
7373             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7374             margin.left   = _.left   !== undefined ? _.left   : margin.left;
7375         }},
7376         duration: {get: function(){return duration;}, set: function(_){
7377             duration = _;
7378             renderWatch.reset(duration);
7379         }},
7380         color:  {get: function(){return color;}, set: function(_){
7381             color = nv.utils.getColor(_);
7382         }},
7383         barColor:  {get: function(){return color;}, set: function(_){
7384             barColor = nv.utils.getColor(_);
7385         }}
7386     });
7387
7388     nv.utils.initOptions(chart);
7389
7390     return chart;
7391 };
7392 nv.models.multiBarHorizontalChart = function() {
7393     "use strict";
7394
7395     //============================================================
7396     // Public Variables with Default Settings
7397     //------------------------------------------------------------
7398
7399     var multibar = nv.models.multiBarHorizontal()
7400         , xAxis = nv.models.axis()
7401         , yAxis = nv.models.axis()
7402         , legend = nv.models.legend().height(30)
7403         , controls = nv.models.legend().height(30)
7404         ;
7405
7406     var margin = {top: 30, right: 20, bottom: 50, left: 60}
7407         , width = null
7408         , height = null
7409         , color = nv.utils.defaultColor()
7410         , showControls = true
7411         , showLegend = true
7412         , showXAxis = true
7413         , showYAxis = true
7414         , stacked = false
7415         , tooltips = true
7416         , tooltip = function(key, x, y, e, graph) {
7417             return '<h3>' + key + ' - ' + x + '</h3>' +
7418                 '<p>' +  y + '</p>'
7419         }
7420         , x //can be accessed via chart.xScale()
7421         , y //can be accessed via chart.yScale()
7422         , state = nv.utils.state()
7423         , defaultState = null
7424         , noData = 'No Data Available.'
7425         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
7426         , controlWidth = function() { return showControls ? 180 : 0 }
7427         , duration = 250
7428         ;
7429
7430     state.stacked = false; // DEPRECATED Maintained for backward compatibility
7431
7432     multibar
7433         .stacked(stacked)
7434     ;
7435     xAxis
7436         .orient('left')
7437         .tickPadding(5)
7438         .highlightZero(false)
7439         .showMaxMin(false)
7440         .tickFormat(function(d) { return d })
7441     ;
7442     yAxis
7443         .orient('bottom')
7444         .tickFormat(d3.format(',.1f'))
7445     ;
7446
7447     controls.updateState(false);
7448
7449     //============================================================
7450     // Private Variables
7451     //------------------------------------------------------------
7452
7453     var showTooltip = function(e, offsetElement) {
7454         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7455             top = e.pos[1] + ( offsetElement.offsetTop || 0),
7456             x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7457             y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7458             content = tooltip(e.series.key, x, y, e, chart);
7459
7460         nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
7461     };
7462
7463     var stateGetter = function(data) {
7464         return function(){
7465             return {
7466                 active: data.map(function(d) { return !d.disabled }),
7467                 stacked: stacked
7468             };
7469         }
7470     };
7471
7472     var stateSetter = function(data) {
7473         return function(state) {
7474             if (state.stacked !== undefined)
7475                 stacked = state.stacked;
7476             if (state.active !== undefined)
7477                 data.forEach(function(series,i) {
7478                     series.disabled = !state.active[i];
7479                 });
7480         }
7481     };
7482
7483     var renderWatch = nv.utils.renderWatch(dispatch, duration);
7484
7485     function chart(selection) {
7486         renderWatch.reset();
7487         renderWatch.models(multibar);
7488         if (showXAxis) renderWatch.models(xAxis);
7489         if (showYAxis) renderWatch.models(yAxis);
7490
7491         selection.each(function(data) {
7492             var container = d3.select(this),
7493                 that = this;
7494             nv.utils.initSVG(container);
7495             var availableWidth = (width  || parseInt(container.style('width')) || 960)
7496                     - margin.left - margin.right,
7497                 availableHeight = (height || parseInt(container.style('height')) || 400)
7498                     - margin.top - margin.bottom;
7499
7500             chart.update = function() { container.transition().duration(duration).call(chart) };
7501             chart.container = this;
7502
7503             stacked = multibar.stacked();
7504
7505             state
7506                 .setter(stateSetter(data), chart.update)
7507                 .getter(stateGetter(data))
7508                 .update();
7509
7510             // DEPRECATED set state.disableddisabled
7511             state.disabled = data.map(function(d) { return !!d.disabled });
7512
7513             if (!defaultState) {
7514                 var key;
7515                 defaultState = {};
7516                 for (key in state) {
7517                     if (state[key] instanceof Array)
7518                         defaultState[key] = state[key].slice(0);
7519                     else
7520                         defaultState[key] = state[key];
7521                 }
7522             }
7523
7524             // Display No Data message if there's nothing to show.
7525             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7526                 var noDataText = container.selectAll('.nv-noData').data([noData]);
7527
7528                 noDataText.enter().append('text')
7529                     .attr('class', 'nvd3 nv-noData')
7530                     .attr('dy', '-.7em')
7531                     .style('text-anchor', 'middle');
7532
7533                 noDataText
7534                     .attr('x', margin.left + availableWidth / 2)
7535                     .attr('y', margin.top + availableHeight / 2)
7536                     .text(function(d) { return d });
7537
7538                 return chart;
7539             } else {
7540                 container.selectAll('.nv-noData').remove();
7541             }
7542
7543             // Setup Scales
7544             x = multibar.xScale();
7545             y = multibar.yScale();
7546
7547             // Setup containers and skeleton of chart
7548             var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
7549             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
7550             var g = wrap.select('g');
7551
7552             gEnter.append('g').attr('class', 'nv-x nv-axis');
7553             gEnter.append('g').attr('class', 'nv-y nv-axis')
7554                 .append('g').attr('class', 'nv-zeroLine')
7555                 .append('line');
7556             gEnter.append('g').attr('class', 'nv-barsWrap');
7557             gEnter.append('g').attr('class', 'nv-legendWrap');
7558             gEnter.append('g').attr('class', 'nv-controlsWrap');
7559
7560             // Legend
7561             if (showLegend) {
7562                 legend.width(availableWidth - controlWidth());
7563
7564                 if (multibar.barColor())
7565                     data.forEach(function(series,i) {
7566                         series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
7567                     });
7568
7569                 g.select('.nv-legendWrap')
7570                     .datum(data)
7571                     .call(legend);
7572
7573                 if ( margin.top != legend.height()) {
7574                     margin.top = legend.height();
7575                     availableHeight = (height || parseInt(container.style('height')) || 400)
7576                         - margin.top - margin.bottom;
7577                 }
7578
7579                 g.select('.nv-legendWrap')
7580                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
7581             }
7582
7583             // Controls
7584             if (showControls) {
7585                 var controlsData = [
7586                     { key: 'Grouped', disabled: multibar.stacked() },
7587                     { key: 'Stacked', disabled: !multibar.stacked() }
7588                 ];
7589
7590                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
7591                 g.select('.nv-controlsWrap')
7592                     .datum(controlsData)
7593                     .attr('transform', 'translate(0,' + (-margin.top) +')')
7594                     .call(controls);
7595             }
7596
7597             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7598
7599             // Main Chart Component(s)
7600             multibar
7601                 .disabled(data.map(function(series) { return series.disabled }))
7602                 .width(availableWidth)
7603                 .height(availableHeight)
7604                 .color(data.map(function(d,i) {
7605                     return d.color || color(d, i);
7606                 }).filter(function(d,i) { return !data[i].disabled }));
7607
7608             var barsWrap = g.select('.nv-barsWrap')
7609                 .datum(data.filter(function(d) { return !d.disabled }));
7610
7611             barsWrap.transition().call(multibar);
7612
7613             // Setup Axes
7614             if (showXAxis) {
7615                 xAxis
7616                     .scale(x)
7617                     .ticks( nv.utils.calcTicksY(availableHeight/24, data) )
7618                     .tickSize(-availableWidth, 0);
7619
7620                 g.select('.nv-x.nv-axis').call(xAxis);
7621
7622                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
7623
7624                 xTicks
7625                     .selectAll('line, text');
7626             }
7627
7628             if (showYAxis) {
7629                 yAxis
7630                     .scale(y)
7631                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7632                     .tickSize( -availableHeight, 0);
7633
7634                 g.select('.nv-y.nv-axis')
7635                     .attr('transform', 'translate(0,' + availableHeight + ')');
7636                 g.select('.nv-y.nv-axis').call(yAxis);
7637             }
7638
7639             // Zero line
7640             g.select(".nv-zeroLine line")
7641                 .attr("x1", y(0))
7642                 .attr("x2", y(0))
7643                 .attr("y1", 0)
7644                 .attr("y2", -availableHeight)
7645             ;
7646
7647             //============================================================
7648             // Event Handling/Dispatching (in chart's scope)
7649             //------------------------------------------------------------
7650
7651             legend.dispatch.on('stateChange', function(newState) {
7652                 for (var key in newState)
7653                     state[key] = newState[key];
7654                 dispatch.stateChange(state);
7655                 chart.update();
7656             });
7657
7658             controls.dispatch.on('legendClick', function(d,i) {
7659                 if (!d.disabled) return;
7660                 controlsData = controlsData.map(function(s) {
7661                     s.disabled = true;
7662                     return s;
7663                 });
7664                 d.disabled = false;
7665
7666                 switch (d.key) {
7667                     case 'Grouped':
7668                         multibar.stacked(false);
7669                         break;
7670                     case 'Stacked':
7671                         multibar.stacked(true);
7672                         break;
7673                 }
7674
7675                 state.stacked = multibar.stacked();
7676                 dispatch.stateChange(state);
7677                 stacked = multibar.stacked();
7678
7679                 chart.update();
7680             });
7681
7682             dispatch.on('tooltipShow', function(e) {
7683                 if (tooltips) showTooltip(e, that.parentNode);
7684             });
7685
7686             // Update chart from a state object passed to event handler
7687             dispatch.on('changeState', function(e) {
7688
7689                 if (typeof e.disabled !== 'undefined') {
7690                     data.forEach(function(series,i) {
7691                         series.disabled = e.disabled[i];
7692                     });
7693
7694                     state.disabled = e.disabled;
7695                 }
7696
7697                 if (typeof e.stacked !== 'undefined') {
7698                     multibar.stacked(e.stacked);
7699                     state.stacked = e.stacked;
7700                     stacked = e.stacked;
7701                 }
7702
7703                 chart.update();
7704             });
7705         });
7706         renderWatch.renderEnd('multibar horizontal chart immediate');
7707         return chart;
7708     }
7709
7710     //============================================================
7711     // Event Handling/Dispatching (out of chart's scope)
7712     //------------------------------------------------------------
7713
7714     multibar.dispatch.on('elementMouseover.tooltip', function(e) {
7715         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7716         dispatch.tooltipShow(e);
7717     });
7718
7719     multibar.dispatch.on('elementMouseout.tooltip', function(e) {
7720         dispatch.tooltipHide(e);
7721     });
7722     dispatch.on('tooltipHide', function() {
7723         if (tooltips) nv.tooltip.cleanup();
7724     });
7725
7726     //============================================================
7727     // Expose Public Variables
7728     //------------------------------------------------------------
7729
7730     // expose chart's sub-components
7731     chart.dispatch = dispatch;
7732     chart.multibar = multibar;
7733     chart.legend = legend;
7734     chart.xAxis = xAxis;
7735     chart.yAxis = yAxis;
7736     chart.state = state;
7737
7738     chart.options = nv.utils.optionsFunc.bind(chart);
7739
7740     chart._options = Object.create({}, {
7741         // simple options, just get/set the necessary values
7742         width:      {get: function(){return width;}, set: function(_){width=_;}},
7743         height:     {get: function(){return height;}, set: function(_){height=_;}},
7744         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7745         showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
7746         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
7747         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
7748         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
7749         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
7750         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7751         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
7752
7753         // options that require extra logic in the setter
7754         margin: {get: function(){return margin;}, set: function(_){
7755             margin.top    = _.top    !== undefined ? _.top    : margin.top;
7756             margin.right  = _.right  !== undefined ? _.right  : margin.right;
7757             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7758             margin.left   = _.left   !== undefined ? _.left   : margin.left;
7759         }},
7760         duration: {get: function(){return duration;}, set: function(_){
7761             duration = _;
7762             renderWatch.reset(duration);
7763             multibar.duration(duration);
7764             xAxis.duration(duration);
7765             yAxis.duration(duration);
7766         }},
7767         color:  {get: function(){return color;}, set: function(_){
7768             color = nv.utils.getColor(_);
7769             legend.color(color);
7770         }}
7771     });
7772
7773     nv.utils.inheritOptions(chart, multibar);
7774     nv.utils.initOptions(chart);
7775
7776     return chart;
7777 };
7778 nv.models.multiChart = function() {
7779     "use strict";
7780
7781     //============================================================
7782     // Public Variables with Default Settings
7783     //------------------------------------------------------------
7784
7785     var margin = {top: 30, right: 20, bottom: 50, left: 60},
7786         color = nv.utils.defaultColor(),
7787         width = null,
7788         height = null,
7789         showLegend = true,
7790         tooltips = true,
7791         tooltip = function(key, x, y, e, graph) {
7792             return '<h3>' + key + '</h3>' +
7793                 '<p>' +  y + ' at ' + x + '</p>'
7794         },
7795         x,
7796         y,
7797         noData = 'No Data Available.',
7798         yDomain1,
7799         yDomain2,
7800         getX = function(d) { return d.x },
7801         getY = function(d) { return d.y},
7802         interpolate = 'monotone'
7803         ;
7804
7805     //============================================================
7806     // Private Variables
7807     //------------------------------------------------------------
7808
7809     var x = d3.scale.linear(),
7810         yScale1 = d3.scale.linear(),
7811         yScale2 = d3.scale.linear(),
7812
7813         lines1 = nv.models.line().yScale(yScale1),
7814         lines2 = nv.models.line().yScale(yScale2),
7815
7816         bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
7817         bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
7818
7819         stack1 = nv.models.stackedArea().yScale(yScale1),
7820         stack2 = nv.models.stackedArea().yScale(yScale2),
7821
7822         xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
7823         yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
7824         yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
7825
7826         legend = nv.models.legend().height(30),
7827         dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
7828
7829     var showTooltip = function(e, offsetElement) {
7830         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7831             top = e.pos[1] + ( offsetElement.offsetTop || 0),
7832             x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
7833             y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
7834             content = tooltip(e.series.key, x, y, e, chart);
7835
7836         nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
7837     };
7838
7839     function chart(selection) {
7840         selection.each(function(data) {
7841             var container = d3.select(this),
7842                 that = this;
7843             nv.utils.initSVG(container);
7844
7845             chart.update = function() { container.transition().call(chart); };
7846             chart.container = this;
7847
7848             var availableWidth = (width  || parseInt(container.style('width')) || 960)
7849                     - margin.left - margin.right,
7850                 availableHeight = (height || parseInt(container.style('height')) || 400)
7851                     - margin.top - margin.bottom;
7852
7853             var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
7854             var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
7855             var dataBars1 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 1});
7856             var dataBars2 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 2});
7857             var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
7858             var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
7859
7860             // Display noData message if there's nothing to show.
7861             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7862                 var noDataText = container.selectAll('.nv-noData').data([noData]);
7863
7864                 noDataText.enter().append('text')
7865                     .attr('class', 'nvd3 nv-noData')
7866                     .attr('dy', '-.7em')
7867                     .style('text-anchor', 'middle');
7868
7869                 noDataText
7870                     .attr('x', margin.left + availableWidth / 2)
7871                     .attr('y', margin.top + availableHeight / 2)
7872                     .text(function(d) { return d });
7873
7874                 return chart;
7875             } else {
7876                 container.selectAll('.nv-noData').remove();
7877             }
7878
7879             var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
7880                 .map(function(d) {
7881                     return d.values.map(function(d,i) {
7882                         return { x: d.x, y: d.y }
7883                     })
7884                 });
7885
7886             var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
7887                 .map(function(d) {
7888                     return d.values.map(function(d,i) {
7889                         return { x: d.x, y: d.y }
7890                     })
7891                 });
7892
7893             x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
7894                 .range([0, availableWidth]);
7895
7896             var wrap = container.selectAll('g.wrap.multiChart').data([data]);
7897             var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
7898
7899             gEnter.append('g').attr('class', 'x axis');
7900             gEnter.append('g').attr('class', 'y1 axis');
7901             gEnter.append('g').attr('class', 'y2 axis');
7902             gEnter.append('g').attr('class', 'lines1Wrap');
7903             gEnter.append('g').attr('class', 'lines2Wrap');
7904             gEnter.append('g').attr('class', 'bars1Wrap');
7905             gEnter.append('g').attr('class', 'bars2Wrap');
7906             gEnter.append('g').attr('class', 'stack1Wrap');
7907             gEnter.append('g').attr('class', 'stack2Wrap');
7908             gEnter.append('g').attr('class', 'legendWrap');
7909
7910             var g = wrap.select('g');
7911
7912             var color_array = data.map(function(d,i) {
7913                 return data[i].color || color(d, i);
7914             });
7915
7916             if (showLegend) {
7917                 legend.color(color_array);
7918                 legend.width( availableWidth / 2 );
7919
7920                 g.select('.legendWrap')
7921                     .datum(data.map(function(series) {
7922                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
7923                         series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
7924                         return series;
7925                     }))
7926                     .call(legend);
7927
7928                 if ( margin.top != legend.height()) {
7929                     margin.top = legend.height();
7930                     availableHeight = (height || parseInt(container.style('height')) || 400)
7931                         - margin.top - margin.bottom;
7932                 }
7933
7934                 g.select('.legendWrap')
7935                     .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
7936             }
7937
7938             lines1
7939                 .width(availableWidth)
7940                 .height(availableHeight)
7941                 .interpolate(interpolate)
7942                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
7943             lines2
7944                 .width(availableWidth)
7945                 .height(availableHeight)
7946                 .interpolate(interpolate)
7947                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
7948             bars1
7949                 .width(availableWidth)
7950                 .height(availableHeight)
7951                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
7952             bars2
7953                 .width(availableWidth)
7954                 .height(availableHeight)
7955                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
7956             stack1
7957                 .width(availableWidth)
7958                 .height(availableHeight)
7959                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
7960             stack2
7961                 .width(availableWidth)
7962                 .height(availableHeight)
7963                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
7964
7965             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7966
7967             var lines1Wrap = g.select('.lines1Wrap')
7968                 .datum(
7969                     dataLines1.filter(function(d){return !d.disabled})
7970                 );
7971             var bars1Wrap = g.select('.bars1Wrap')
7972                 .datum(
7973                     dataBars1.filter(function(d){return !d.disabled})
7974                 );
7975             var stack1Wrap = g.select('.stack1Wrap')
7976                 .datum(
7977                     dataStack1.filter(function(d){return !d.disabled})
7978                 );
7979
7980             var lines2Wrap = g.select('.lines2Wrap')
7981                 .datum(
7982                     dataLines2.filter(function(d){return !d.disabled})
7983                 );
7984             var bars2Wrap = g.select('.bars2Wrap')
7985                 .datum(
7986                     dataBars2.filter(function(d){return !d.disabled})
7987                 );
7988             var stack2Wrap = g.select('.stack2Wrap')
7989                 .datum(
7990                     dataStack2.filter(function(d){return !d.disabled})
7991                 );
7992
7993             var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
7994                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
7995             }).concat([{x:0, y:0}]) : []
7996             var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
7997                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
7998             }).concat([{x:0, y:0}]) : []
7999
8000             yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
8001                 .range([0, availableHeight])
8002
8003             yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
8004                 .range([0, availableHeight])
8005
8006             lines1.yDomain(yScale1.domain())
8007             bars1.yDomain(yScale1.domain())
8008             stack1.yDomain(yScale1.domain())
8009
8010             lines2.yDomain(yScale2.domain())
8011             bars2.yDomain(yScale2.domain())
8012             stack2.yDomain(yScale2.domain())
8013
8014             if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
8015             if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
8016
8017             if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
8018             if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
8019
8020             if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
8021             if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
8022
8023             xAxis
8024                 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8025                 .tickSize(-availableHeight, 0);
8026
8027             g.select('.x.axis')
8028                 .attr('transform', 'translate(0,' + availableHeight + ')');
8029             d3.transition(g.select('.x.axis'))
8030                 .call(xAxis);
8031
8032             yAxis1
8033                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8034                 .tickSize( -availableWidth, 0);
8035
8036
8037             d3.transition(g.select('.y1.axis'))
8038                 .call(yAxis1);
8039
8040             yAxis2
8041                 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8042                 .tickSize( -availableWidth, 0);
8043
8044             d3.transition(g.select('.y2.axis'))
8045                 .call(yAxis2);
8046
8047             g.select('.y1.axis')
8048                 .classed('nv-disabled', series1.length ? false : true)
8049                 .attr('transform', 'translate(' + x.range()[0] + ',0)');
8050
8051             g.select('.y2.axis')
8052                 .classed('nv-disabled', series2.length ? false : true)
8053                 .attr('transform', 'translate(' + x.range()[1] + ',0)');
8054
8055             legend.dispatch.on('stateChange', function(newState) {
8056                 chart.update();
8057             });
8058
8059             dispatch.on('tooltipShow', function(e) {
8060                 if (tooltips) showTooltip(e, that.parentNode);
8061             });
8062
8063         });
8064
8065         return chart;
8066     }
8067
8068     //============================================================
8069     // Event Handling/Dispatching (out of chart's scope)
8070     //------------------------------------------------------------
8071
8072     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8073         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8074         dispatch.tooltipShow(e);
8075     });
8076
8077     lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8078         dispatch.tooltipHide(e);
8079     });
8080
8081     lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8082         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8083         dispatch.tooltipShow(e);
8084     });
8085
8086     lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8087         dispatch.tooltipHide(e);
8088     });
8089
8090     bars1.dispatch.on('elementMouseover.tooltip', function(e) {
8091         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8092         dispatch.tooltipShow(e);
8093     });
8094
8095     bars1.dispatch.on('elementMouseout.tooltip', function(e) {
8096         dispatch.tooltipHide(e);
8097     });
8098
8099     bars2.dispatch.on('elementMouseover.tooltip', function(e) {
8100         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8101         dispatch.tooltipShow(e);
8102     });
8103
8104     bars2.dispatch.on('elementMouseout.tooltip', function(e) {
8105         dispatch.tooltipHide(e);
8106     });
8107
8108     stack1.dispatch.on('tooltipShow', function(e) {
8109         //disable tooltips when value ~= 0
8110         //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8111         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
8112             setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8113             return false;
8114         }
8115
8116         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8117             dispatch.tooltipShow(e);
8118     });
8119
8120     stack1.dispatch.on('tooltipHide', function(e) {
8121         dispatch.tooltipHide(e);
8122     });
8123
8124     stack2.dispatch.on('tooltipShow', function(e) {
8125         //disable tooltips when value ~= 0
8126         //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8127         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
8128             setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8129             return false;
8130         }
8131
8132         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8133             dispatch.tooltipShow(e);
8134     });
8135
8136     stack2.dispatch.on('tooltipHide', function(e) {
8137         dispatch.tooltipHide(e);
8138     });
8139
8140     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8141         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8142         dispatch.tooltipShow(e);
8143     });
8144
8145     lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8146         dispatch.tooltipHide(e);
8147     });
8148
8149     lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8150         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8151         dispatch.tooltipShow(e);
8152     });
8153
8154     lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8155         dispatch.tooltipHide(e);
8156     });
8157
8158     dispatch.on('tooltipHide', function() {
8159         if (tooltips) nv.tooltip.cleanup();
8160     });
8161
8162     //============================================================
8163     // Global getters and setters
8164     //------------------------------------------------------------
8165
8166     chart.dispatch = dispatch;
8167     chart.lines1 = lines1;
8168     chart.lines2 = lines2;
8169     chart.bars1 = bars1;
8170     chart.bars2 = bars2;
8171     chart.stack1 = stack1;
8172     chart.stack2 = stack2;
8173     chart.xAxis = xAxis;
8174     chart.yAxis1 = yAxis1;
8175     chart.yAxis2 = yAxis2;
8176
8177     chart.options = nv.utils.optionsFunc.bind(chart);
8178
8179     chart._options = Object.create({}, {
8180         // simple options, just get/set the necessary values
8181         width:      {get: function(){return width;}, set: function(_){width=_;}},
8182         height:     {get: function(){return height;}, set: function(_){height=_;}},
8183         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
8184         yDomain1:      {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
8185         yDomain2:    {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
8186         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
8187         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
8188         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
8189         interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
8190
8191         // options that require extra logic in the setter
8192         margin: {get: function(){return margin;}, set: function(_){
8193             margin.top    = _.top    !== undefined ? _.top    : margin.top;
8194             margin.right  = _.right  !== undefined ? _.right  : margin.right;
8195             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8196             margin.left   = _.left   !== undefined ? _.left   : margin.left;
8197         }},
8198         color:  {get: function(){return color;}, set: function(_){
8199             color = nv.utils.getColor(_);
8200         }},
8201         x: {get: function(){return getX;}, set: function(_){
8202             getX = _;
8203             lines1.x(_);
8204             bars1.x(_);
8205         }},
8206         y: {get: function(){return getY;}, set: function(_){
8207             getY = _;
8208             lines1.y(_);
8209             bars1.y(_);
8210         }}
8211     });
8212
8213     nv.utils.initOptions(chart);
8214
8215     return chart;
8216 };
8217
8218
8219 nv.models.ohlcBar = function() {
8220     "use strict";
8221
8222     //============================================================
8223     // Public Variables with Default Settings
8224     //------------------------------------------------------------
8225
8226     var margin = {top: 0, right: 0, bottom: 0, left: 0}
8227         , width = null
8228         , height = null
8229         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8230         , x = d3.scale.linear()
8231         , y = d3.scale.linear()
8232         , getX = function(d) { return d.x }
8233         , getY = function(d) { return d.y }
8234         , getOpen = function(d) { return d.open }
8235         , getClose = function(d) { return d.close }
8236         , getHigh = function(d) { return d.high }
8237         , getLow = function(d) { return d.low }
8238         , forceX = []
8239         , forceY = []
8240         , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
8241         , clipEdge = true
8242         , color = nv.utils.defaultColor()
8243         , interactive = false
8244         , xDomain
8245         , yDomain
8246         , xRange
8247         , yRange
8248         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8249         ;
8250
8251     //============================================================
8252     // Private Variables
8253     //------------------------------------------------------------
8254
8255     function chart(selection) {
8256         selection.each(function(data) {
8257             var container = d3.select(this);
8258             var availableWidth = (width  || parseInt(container.style('width')) || 960)
8259                 - margin.left - margin.right;
8260             var availableHeight = (height || parseInt(container.style('height')) || 400)
8261                 - margin.top - margin.bottom;
8262
8263             nv.utils.initSVG(container);
8264
8265             // Setup Scales
8266             x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
8267
8268             if (padData)
8269                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
8270             else
8271                 x.range(xRange || [0, availableWidth]);
8272
8273             y.domain(yDomain || [
8274                     d3.min(data[0].values.map(getLow).concat(forceY)),
8275                     d3.max(data[0].values.map(getHigh).concat(forceY))
8276                 ]
8277             ).range(yRange || [availableHeight, 0]);
8278
8279             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
8280             if (x.domain()[0] === x.domain()[1])
8281                 x.domain()[0] ?
8282                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8283                     : x.domain([-1,1]);
8284
8285             if (y.domain()[0] === y.domain()[1])
8286                 y.domain()[0] ?
8287                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8288                     : y.domain([-1,1]);
8289
8290             // Setup containers and skeleton of chart
8291             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
8292             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
8293             var defsEnter = wrapEnter.append('defs');
8294             var gEnter = wrapEnter.append('g');
8295             var g = wrap.select('g');
8296
8297             gEnter.append('g').attr('class', 'nv-ticks');
8298
8299             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8300
8301             container
8302                 .on('click', function(d,i) {
8303                     dispatch.chartClick({
8304                         data: d,
8305                         index: i,
8306                         pos: d3.event,
8307                         id: id
8308                     });
8309                 });
8310
8311             defsEnter.append('clipPath')
8312                 .attr('id', 'nv-chart-clip-path-' + id)
8313                 .append('rect');
8314
8315             wrap.select('#nv-chart-clip-path-' + id + ' rect')
8316                 .attr('width', availableWidth)
8317                 .attr('height', availableHeight);
8318
8319             g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
8320
8321             var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
8322                 .data(function(d) { return d });
8323             ticks.exit().remove();
8324
8325             var ticksEnter = ticks.enter().append('path')
8326                 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
8327                 .attr('d', function(d,i) {
8328                     var w = (availableWidth / data[0].values.length) * .9;
8329                     return 'm0,0l0,'
8330                         + (y(getOpen(d,i))
8331                             - y(getHigh(d,i)))
8332                         + 'l'
8333                         + (-w/2)
8334                         + ',0l'
8335                         + (w/2)
8336                         + ',0l0,'
8337                         + (y(getLow(d,i)) - y(getOpen(d,i)))
8338                         + 'l0,'
8339                         + (y(getClose(d,i))
8340                             - y(getLow(d,i)))
8341                         + 'l'
8342                         + (w/2)
8343                         + ',0l'
8344                         + (-w/2)
8345                         + ',0z';
8346                 })
8347                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
8348                 .attr('fill', function(d,i) { return color[0]; })
8349                 .attr('stroke', function(d,i) { return color[0]; })
8350                 .attr('x', 0 )
8351                 .attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
8352                 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
8353
8354             // the bar colors are controlled by CSS currently
8355             ticks.attr('class', function(d,i,j) {
8356                 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
8357             });
8358
8359             d3.transition(ticks)
8360                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
8361                 .attr('d', function(d,i) {
8362                     var w = (availableWidth / data[0].values.length) * .9;
8363                     return 'm0,0l0,'
8364                         + (y(getOpen(d,i))
8365                             - y(getHigh(d,i)))
8366                         + 'l'
8367                         + (-w/2)
8368                         + ',0l'
8369                         + (w/2)
8370                         + ',0l0,'
8371                         + (y(getLow(d,i))
8372                             - y(getOpen(d,i)))
8373                         + 'l0,'
8374                         + (y(getClose(d,i))
8375                             - y(getLow(d,i)))
8376                         + 'l'
8377                         + (w/2)
8378                         + ',0l'
8379                         + (-w/2)
8380                         + ',0z';
8381                 });
8382         });
8383
8384         return chart;
8385     }
8386
8387
8388     //Create methods to allow outside functions to highlight a specific bar.
8389     chart.highlightPoint = function(pointIndex, isHoverOver) {
8390         chart.clearHighlights();
8391         d3.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
8392             .classed("hover", isHoverOver)
8393         ;
8394     };
8395
8396     chart.clearHighlights = function() {
8397         d3.select(".nv-ohlcBar .nv-tick.hover")
8398             .classed("hover", false)
8399         ;
8400     };
8401
8402     //============================================================
8403     // Expose Public Variables
8404     //------------------------------------------------------------
8405
8406     chart.dispatch = dispatch;
8407     chart.options = nv.utils.optionsFunc.bind(chart);
8408
8409     chart._options = Object.create({}, {
8410         // simple options, just get/set the necessary values
8411         width:    {get: function(){return width;}, set: function(_){width=_;}},
8412         height:   {get: function(){return height;}, set: function(_){height=_;}},
8413         xScale:   {get: function(){return x;}, set: function(_){x=_;}},
8414         yScale:   {get: function(){return y;}, set: function(_){y=_;}},
8415         xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
8416         yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
8417         xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
8418         yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
8419         forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
8420         forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
8421         padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
8422         clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
8423         id:       {get: function(){return id;}, set: function(_){id=_;}},
8424         interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
8425
8426         x:     {get: function(){return getX;}, set: function(_){getX=_;}},
8427         y:     {get: function(){return getY;}, set: function(_){getY=_;}},
8428         open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
8429         close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
8430         high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
8431         low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},
8432
8433         // options that require extra logic in the setter
8434         margin: {get: function(){return margin;}, set: function(_){
8435             margin.top    = _.top    != undefined ? _.top    : margin.top;
8436             margin.right  = _.right  != undefined ? _.right  : margin.right;
8437             margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
8438             margin.left   = _.left   != undefined ? _.left   : margin.left;
8439         }},
8440         color:  {get: function(){return color;}, set: function(_){
8441             color = nv.utils.getColor(_);
8442         }}
8443     });
8444
8445     nv.utils.initOptions(chart);
8446     return chart;
8447 };
8448
8449 // Code adapted from Jason Davies' "Parallel Coordinates"
8450 // http://bl.ocks.org/jasondavies/1341281
8451
8452 nv.models.parallelCoordinates = function() {
8453     "use strict";
8454
8455     //============================================================
8456     // Public Variables with Default Settings
8457     //------------------------------------------------------------
8458
8459     var margin = {top: 30, right: 10, bottom: 10, left: 10}
8460         , width = null
8461         , height = null
8462         , x = d3.scale.ordinal()
8463         , y = {}
8464         , dimensions = []
8465         , color = nv.utils.defaultColor()
8466         , filters = []
8467         , active = []
8468         , dispatch = d3.dispatch('brush')
8469         ;
8470
8471     //============================================================
8472     // Private Variables
8473     //------------------------------------------------------------
8474
8475     function chart(selection) {
8476         selection.each(function(data) {
8477             var container = d3.select(this);
8478             var availableWidth = (width  || parseInt(container.style('width')) || 960)
8479                 - margin.left - margin.right;
8480             var availableHeight = (height || parseInt(container.style('height')) || 400)
8481                 - margin.top - margin.bottom;
8482
8483             nv.utils.initSVG(container);
8484
8485             active = data; //set all active before first brush call
8486
8487             //This is a placeholder until this chart is made resizeable
8488             chart.update = function() { };
8489
8490             // Setup Scales
8491             x.rangePoints([0, availableWidth], 1).domain(dimensions);
8492
8493             // Extract the list of dimensions and create a scale for each.
8494             dimensions.forEach(function(d) {
8495                 y[d] = d3.scale.linear()
8496                     .domain(d3.extent(data, function(p) { return +p[d]; }))
8497                     .range([availableHeight, 0]);
8498
8499                 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
8500
8501                 return d != 'name';
8502             });
8503
8504             // Setup containers and skeleton of chart
8505             var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
8506             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
8507             var gEnter = wrapEnter.append('g');
8508             var g = wrap.select('g');
8509
8510             gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
8511             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8512
8513             var line = d3.svg.line(),
8514                 axis = d3.svg.axis().orient('left'),
8515                 background,
8516                 foreground;
8517
8518             // Add grey background lines for context.
8519             background = gEnter.append('g')
8520                 .attr('class', 'background')
8521                 .selectAll('path')
8522                 .data(data)
8523                 .enter().append('path')
8524                 .attr('d', path)
8525             ;
8526
8527             // Add blue foreground lines for focus.
8528             foreground = gEnter.append('g')
8529                 .attr('class', 'foreground')
8530                 .selectAll('path')
8531                 .data(data)
8532                 .enter().append('path')
8533                 .attr('d', path)
8534                 .attr('stroke', color)
8535             ;
8536
8537             // Add a group element for each dimension.
8538             var dimension = g.selectAll('.dimension')
8539                 .data(dimensions)
8540                 .enter().append('g')
8541                 .attr('class', 'dimension')
8542                 .attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
8543
8544             // Add an axis and title.
8545             dimension.append('g')
8546                 .attr('class', 'axis')
8547                 .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
8548                 .append('text')
8549                 .attr('text-anchor', 'middle')
8550                 .attr('y', -9)
8551                 .text(String);
8552
8553             // Add and store a brush for each axis.
8554             dimension.append('g')
8555                 .attr('class', 'brush')
8556                 .each(function(d) { d3.select(this).call(y[d].brush); })
8557                 .selectAll('rect')
8558                 .attr('x', -8)
8559                 .attr('width', 16);
8560
8561             // Returns the path for a given data point.
8562             function path(d) {
8563                 return line(dimensions.map(function(p) { return [x(p), y[p](d[p])]; }));
8564             }
8565
8566             // Handles a brush event, toggling the display of foreground lines.
8567             function brush() {
8568                 var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
8569                     extents = actives.map(function(p) { return y[p].brush.extent(); });
8570
8571                 filters = []; //erase current filters
8572                 actives.forEach(function(d,i) {
8573                     filters[i] = {
8574                         dimension: d,
8575                         extent: extents[i]
8576                     }
8577                 });
8578
8579                 active = []; //erase current active list
8580                 foreground.style('display', function(d) {
8581                     var isActive = actives.every(function(p, i) {
8582                         return extents[i][0] <= d[p] && d[p] <= extents[i][1];
8583                     });
8584                     if (isActive) active.push(d);
8585                     return isActive ? null : 'none';
8586                 });
8587
8588                 dispatch.brush({
8589                     filters: filters,
8590                     active: active
8591                 });
8592             }
8593         });
8594
8595         return chart;
8596     }
8597
8598     //============================================================
8599     // Expose Public Variables
8600     //------------------------------------------------------------
8601
8602     chart.dispatch = dispatch;
8603     chart.options = nv.utils.optionsFunc.bind(chart);
8604
8605     chart._options = Object.create({}, {
8606         // simple options, just get/set the necessary values
8607         width:      {get: function(){return width;}, set: function(_){width=_;}},
8608         height:     {get: function(){return height;}, set: function(_){height=_;}},
8609         dimensions: {get: function(){return dimensions;}, set: function(_){dimensions=_;}},
8610
8611         // options that require extra logic in the setter
8612         margin: {get: function(){return margin;}, set: function(_){
8613             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8614             margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8615             margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8616             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8617         }},
8618         color:  {get: function(){return color;}, set: function(_){
8619             color = nv.utils.getColor(_);
8620         }}
8621     });
8622
8623     nv.utils.initOptions(chart);
8624     return chart;
8625 };
8626 nv.models.pie = function() {
8627     "use strict";
8628
8629     //============================================================
8630     // Public Variables with Default Settings
8631     //------------------------------------------------------------
8632
8633     var margin = {top: 0, right: 0, bottom: 0, left: 0}
8634         , width = 500
8635         , height = 500
8636         , getX = function(d) { return d.x }
8637         , getY = function(d) { return d.y }
8638         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8639         , color = nv.utils.defaultColor()
8640         , valueFormat = d3.format(',.2f')
8641         , labelFormat = d3.format('%')
8642         , showLabels = true
8643         , pieLabelsOutside = true
8644         , donutLabelsOutside = false
8645         , labelType = "key"
8646         , labelThreshold = .02 //if slice percentage is under this, don't show label
8647         , donut = false
8648         , title = false
8649         , growOnHover = true
8650         , titleOffset = 0
8651         , labelSunbeamLayout = false
8652         , startAngle = false
8653         , padAngle = false
8654         , endAngle = false
8655         , cornerRadius = 0
8656         , donutRatio = 0.5
8657         , duration = 250
8658         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
8659         ;
8660
8661
8662     //============================================================
8663     // chart function
8664     //------------------------------------------------------------
8665
8666     var renderWatch = nv.utils.renderWatch(dispatch);
8667
8668     function chart(selection) {
8669         renderWatch.reset();
8670         selection.each(function(data) {
8671             var availableWidth = width - margin.left - margin.right
8672                 ,availableHeight = height - margin.top - margin.bottom
8673                 ,radius = Math.min(availableWidth, availableHeight) / 2
8674                 ,arcRadius = radius-(radius / 5)
8675                 ,container = d3.select(this)
8676                 ;
8677             nv.utils.initSVG(container);
8678
8679             // Setup containers and skeleton of chart
8680             var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
8681             var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
8682             var gEnter = wrapEnter.append('g');
8683             var g = wrap.select('g');
8684             var g_pie = gEnter.append('g').attr('class', 'nv-pie');
8685             gEnter.append('g').attr('class', 'nv-pieLabels');
8686
8687             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8688             g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
8689             g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
8690
8691             //
8692             container.on('click', function(d,i) {
8693                 dispatch.chartClick({
8694                     data: d,
8695                     index: i,
8696                     pos: d3.event,
8697                     id: id
8698                 });
8699             });
8700
8701
8702             var arc = d3.svg.arc().outerRadius(arcRadius);
8703             var arcOver = d3.svg.arc().outerRadius(arcRadius + 5);
8704
8705             if (startAngle) {
8706                 arc.startAngle(startAngle);
8707                 arcOver.startAngle(startAngle);
8708             }
8709             if (endAngle) {
8710                 arc.endAngle(endAngle);
8711                 arcOver.endAngle(endAngle);
8712             }
8713             if (donut) {
8714                 arc.innerRadius(radius * donutRatio);
8715                 arcOver.innerRadius(radius * donutRatio);
8716             }
8717
8718             // Setup the Pie chart and choose the data element
8719             var pie = d3.layout.pie()
8720                 .sort(null)
8721                 .value(function(d) { return d.disabled ? 0 : getY(d) });
8722
8723             // padAngle added in d3 3.5
8724             if (pie.padAngle && padAngle) {
8725                 pie.padAngle(padAngle);
8726             }
8727
8728             if (arc.cornerRadius && cornerRadius) {
8729                 arc.cornerRadius(cornerRadius);
8730                 arcOver.cornerRadius(cornerRadius);
8731             }
8732
8733             // if title is specified and donut, put it in the middle
8734             if (donut && title) {
8735                 var title_g = g_pie.append('g').attr('class', 'nv-pie');
8736
8737                 title_g.append("text")
8738                     .style("text-anchor", "middle")
8739                     .attr('class', 'nv-pie-title')
8740                     .text(function (d) {
8741                         return title;
8742                     })
8743                     .attr("dy", "0.35em") // trick to vertically center text
8744                     .attr('transform', function(d, i) {
8745                         return 'translate(0, '+ titleOffset + ')';
8746                     });
8747             }
8748
8749             var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
8750             var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
8751
8752             slices.exit().remove();
8753             pieLabels.exit().remove();
8754
8755             var ae = slices.enter().append('g')
8756             ae.attr('class', 'nv-slice')
8757             ae.on('mouseover', function(d,i){
8758                 d3.select(this).classed('hover', true);
8759                 if (growOnHover) {
8760                     d3.select(this).select("path").transition()
8761                         .duration(70)
8762                         .attr("d", arcOver);
8763                 }
8764                 dispatch.elementMouseover({
8765                     label: getX(d.data),
8766                     value: getY(d.data),
8767                     point: d.data,
8768                     pointIndex: i,
8769                     pos: [d3.event.pageX, d3.event.pageY],
8770                     id: id,
8771                     color: d3.select(this).style("fill")
8772                 });
8773             });
8774             ae.on('mouseout', function(d,i){
8775                 d3.select(this).classed('hover', false);
8776                 if (growOnHover) {
8777                     d3.select(this).select("path").transition()
8778                         .duration(50)
8779                         .attr("d", arc);
8780                 }
8781                 dispatch.elementMouseout({
8782                     label: getX(d.data),
8783                     value: getY(d.data),
8784                     point: d.data,
8785                     index: i,
8786                     id: id
8787                 });
8788             });
8789             ae.on('click', function(d,i) {
8790                 dispatch.elementClick({
8791                     label: getX(d.data),
8792                     value: getY(d.data),
8793                     point: d.data,
8794                     index: i,
8795                     pos: d3.event,
8796                     id: id
8797                 });
8798                 d3.event.stopPropagation();
8799             });
8800             ae.on('dblclick', function(d,i) {
8801                 dispatch.elementDblClick({
8802                     label: getX(d.data),
8803                     value: getY(d.data),
8804                     point: d.data,
8805                     index: i,
8806                     pos: d3.event,
8807                     id: id
8808                 });
8809                 d3.event.stopPropagation();
8810             });
8811
8812             slices.attr('fill', function(d,i) { return color(d, i); })
8813             slices.attr('stroke', function(d,i) { return color(d, i); });
8814
8815             var paths = ae.append('path').each(function(d) {
8816                 this._current = d;
8817             });
8818
8819             slices.select('path')
8820                 .transition()
8821                 .attr('d', arc)
8822                 .attrTween('d', arcTween);
8823
8824             if (showLabels) {
8825                 // This does the normal label
8826                 var labelsArc = d3.svg.arc().innerRadius(0);
8827
8828                 if (pieLabelsOutside){
8829                     var labelsArc = arc;
8830                 }
8831
8832                 if (donutLabelsOutside) {
8833                     labelsArc = d3.svg.arc().outerRadius(arc.outerRadius());
8834                 }
8835
8836                 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
8837                     var group = d3.select(this);
8838
8839                     group.attr('transform', function(d) {
8840                         if (labelSunbeamLayout) {
8841                             d.outerRadius = arcRadius + 10; // Set Outer Coordinate
8842                             d.innerRadius = arcRadius + 15; // Set Inner Coordinate
8843                             var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
8844                             if ((d.startAngle+d.endAngle)/2 < Math.PI) {
8845                                 rotateAngle -= 90;
8846                             } else {
8847                                 rotateAngle += 90;
8848                             }
8849                             return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
8850                         } else {
8851                             d.outerRadius = radius + 10; // Set Outer Coordinate
8852                             d.innerRadius = radius + 15; // Set Inner Coordinate
8853                             return 'translate(' + labelsArc.centroid(d) + ')'
8854                         }
8855                     });
8856
8857                     group.append('rect')
8858                         .style('stroke', '#fff')
8859                         .style('fill', '#fff')
8860                         .attr("rx", 3)
8861                         .attr("ry", 3);
8862
8863                     group.append('text')
8864                         .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
8865                         .style('fill', '#000')
8866
8867                 });
8868
8869                 var labelLocationHash = {};
8870                 var avgHeight = 14;
8871                 var avgWidth = 140;
8872                 var createHashKey = function(coordinates) {
8873                     return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
8874                 };
8875
8876                 pieLabels.watchTransition(renderWatch,'pie labels').attr('transform', function(d) {
8877                     if (labelSunbeamLayout) {
8878                         d.outerRadius = arcRadius + 10; // Set Outer Coordinate
8879                         d.innerRadius = arcRadius + 15; // Set Inner Coordinate
8880                         var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
8881                         if ((d.startAngle+d.endAngle)/2 < Math.PI) {
8882                             rotateAngle -= 90;
8883                         } else {
8884                             rotateAngle += 90;
8885                         }
8886                         return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
8887                     } else {
8888                         d.outerRadius = radius + 10; // Set Outer Coordinate
8889                         d.innerRadius = radius + 15; // Set Inner Coordinate
8890
8891                         /*
8892                          Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
8893                          Each label location is hashed, and if a hash collision occurs, we assume an overlap.
8894                          Adjust the label's y-position to remove the overlap.
8895                          */
8896                         var center = labelsArc.centroid(d);
8897                         if(d.value){
8898                             var hashKey = createHashKey(center);
8899                             if (labelLocationHash[hashKey]) {
8900                                 center[1] -= avgHeight;
8901                             }
8902                             labelLocationHash[createHashKey(center)] = true;
8903                         }
8904                         return 'translate(' + center + ')'
8905                     }
8906                 });
8907
8908                 pieLabels.select(".nv-label text")
8909                     .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
8910                     .text(function(d, i) {
8911                         var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
8912                         var labelTypes = {
8913                             "key" : getX(d.data),
8914                             "value": getY(d.data),
8915                             "percent": labelFormat(percent)
8916                         };
8917                         return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
8918                     })
8919                 ;
8920             }
8921
8922
8923             // Computes the angle of an arc, converting from radians to degrees.
8924             function angle(d) {
8925                 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
8926                 return a > 90 ? a - 180 : a;
8927             }
8928
8929             function arcTween(a) {
8930                 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
8931                 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
8932                 if (!donut) a.innerRadius = 0;
8933                 var i = d3.interpolate(this._current, a);
8934                 this._current = i(0);
8935                 return function(t) {
8936                     return arc(i(t));
8937                 };
8938             }
8939         });
8940
8941         renderWatch.renderEnd('pie immediate');
8942         return chart;
8943     }
8944
8945     //============================================================
8946     // Expose Public Variables
8947     //------------------------------------------------------------
8948
8949     chart.dispatch = dispatch;
8950     chart.options = nv.utils.optionsFunc.bind(chart);
8951
8952     chart._options = Object.create({}, {
8953         // simple options, just get/set the necessary values
8954         width:      {get: function(){return width;}, set: function(_){width=_;}},
8955         height:     {get: function(){return height;}, set: function(_){height=_;}},
8956         showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
8957         title:      {get: function(){return title;}, set: function(_){title=_;}},
8958         titleOffset:    {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
8959         labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
8960         labelFormat:    {get: function(){return labelFormat;}, set: function(_){labelFormat=_;}},
8961         valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
8962         x:          {get: function(){return getX;}, set: function(_){getX=_;}},
8963         id:         {get: function(){return id;}, set: function(_){id=_;}},
8964         endAngle:   {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
8965         startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
8966         padAngle:   {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
8967         cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
8968         donutRatio:   {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
8969         pieLabelsOutside:   {get: function(){return pieLabelsOutside;}, set: function(_){pieLabelsOutside=_;}},
8970         donutLabelsOutside: {get: function(){return donutLabelsOutside;}, set: function(_){donutLabelsOutside=_;}},
8971         labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
8972         donut:              {get: function(){return donut;}, set: function(_){donut=_;}},
8973         growOnHover:        {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
8974
8975         // options that require extra logic in the setter
8976         margin: {get: function(){return margin;}, set: function(_){
8977             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8978             margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8979             margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8980             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8981         }},
8982         y: {get: function(){return getY;}, set: function(_){
8983             getY=d3.functor(_);
8984         }},
8985         color: {get: function(){return color;}, set: function(_){
8986             color=nv.utils.getColor(_);
8987         }},
8988         labelType:          {get: function(){return labelType;}, set: function(_){
8989             labelType= _ || 'key';
8990         }}
8991     });
8992
8993     nv.utils.initOptions(chart);
8994     return chart;
8995 };
8996 nv.models.pieChart = function() {
8997     "use strict";
8998
8999     //============================================================
9000     // Public Variables with Default Settings
9001     //------------------------------------------------------------
9002
9003     var pie = nv.models.pie();
9004     var legend = nv.models.legend();
9005
9006     var margin = {top: 30, right: 20, bottom: 20, left: 20}
9007         , width = null
9008         , height = null
9009         , showLegend = true
9010         , color = nv.utils.defaultColor()
9011         , tooltips = true
9012         , tooltip = function(key, y, e, graph) {
9013             return '<h3 style="background-color: '
9014                 + e.color + '">' + key + '</h3>'
9015                 + '<p>' +  y + '</p>';
9016         }
9017         , state = nv.utils.state()
9018         , defaultState = null
9019         , noData = "No Data Available."
9020         , duration = 250
9021         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
9022         ;
9023
9024     //============================================================
9025     // Private Variables
9026     //------------------------------------------------------------
9027
9028     var showTooltip = function(e, offsetElement) {
9029         var tooltipLabel = pie.x()(e.point);
9030         var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
9031             top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
9032             y = pie.valueFormat()(pie.y()(e.point)),
9033             content = tooltip(tooltipLabel, y, e, chart)
9034             ;
9035         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
9036     };
9037
9038     var renderWatch = nv.utils.renderWatch(dispatch);
9039
9040     var stateGetter = function(data) {
9041         return function(){
9042             return {
9043                 active: data.map(function(d) { return !d.disabled })
9044             };
9045         }
9046     };
9047
9048     var stateSetter = function(data) {
9049         return function(state) {
9050             if (state.active !== undefined) {
9051                 data.forEach(function (series, i) {
9052                     series.disabled = !state.active[i];
9053                 });
9054             }
9055         }
9056     };
9057
9058     //============================================================
9059     // Chart function
9060     //------------------------------------------------------------
9061
9062     function chart(selection) {
9063         renderWatch.reset();
9064         renderWatch.models(pie);
9065
9066         selection.each(function(data) {
9067             var container = d3.select(this);
9068             nv.utils.initSVG(container);
9069
9070             var that = this;
9071             var availableWidth = (width || parseInt(container.style('width'), 10) || 960)
9072                     - margin.left - margin.right,
9073                 availableHeight = (height || parseInt(container.style('height'), 10) || 400)
9074                     - margin.top - margin.bottom
9075                 ;
9076
9077             chart.update = function() { container.transition().call(chart); };
9078             chart.container = this;
9079
9080             state.setter(stateSetter(data), chart.update)
9081                 .getter(stateGetter(data))
9082                 .update();
9083
9084             //set state.disabled
9085             state.disabled = data.map(function(d) { return !!d.disabled });
9086
9087             if (!defaultState) {
9088                 var key;
9089                 defaultState = {};
9090                 for (key in state) {
9091                     if (state[key] instanceof Array)
9092                         defaultState[key] = state[key].slice(0);
9093                     else
9094                         defaultState[key] = state[key];
9095                 }
9096             }
9097
9098             // Display No Data message if there's nothing to show.
9099             if (!data || !data.length) {
9100                 var noDataText = container.selectAll('.nv-noData').data([noData]);
9101
9102                 noDataText.enter().append('text')
9103                     .attr('class', 'nvd3 nv-noData')
9104                     .attr('dy', '-.7em')
9105                     .style('text-anchor', 'middle');
9106
9107                 noDataText
9108                     .attr('x', margin.left + availableWidth / 2)
9109                     .attr('y', margin.top + availableHeight / 2)
9110                     .text(function(d) { return d });
9111
9112                 return chart;
9113             } else {
9114                 container.selectAll('.nv-noData').remove();
9115             }
9116
9117             // Setup containers and skeleton of chart
9118             var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
9119             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
9120             var g = wrap.select('g');
9121
9122             gEnter.append('g').attr('class', 'nv-pieWrap');
9123             gEnter.append('g').attr('class', 'nv-legendWrap');
9124
9125             // Legend
9126             if (showLegend) {
9127                 legend.width( availableWidth ).key(pie.x());
9128
9129                 wrap.select('.nv-legendWrap')
9130                     .datum(data)
9131                     .call(legend);
9132
9133                 if ( margin.top != legend.height()) {
9134                     margin.top = legend.height();
9135                     availableHeight = (height || parseInt(container.style('height')) || 400)
9136                         - margin.top - margin.bottom;
9137                 }
9138
9139                 wrap.select('.nv-legendWrap')
9140                     .attr('transform', 'translate(0,' + (-margin.top) +')');
9141             }
9142             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9143
9144             // Main Chart Component(s)
9145             pie.width(availableWidth).height(availableHeight);
9146             var pieWrap = g.select('.nv-pieWrap').datum([data]);
9147             d3.transition(pieWrap).call(pie);
9148
9149             // Event Handling/Dispatching (in chart's scope)
9150             legend.dispatch.on('stateChange', function(newState) {
9151                 for (var key in newState) {
9152                     state[key] = newState[key];
9153                 }
9154                 dispatch.stateChange(state);
9155                 chart.update();
9156             });
9157
9158             pie.dispatch.on('elementMouseout.tooltip', function(e) {
9159                 dispatch.tooltipHide(e);
9160             });
9161
9162             // Update chart from a state object passed to event handler
9163             dispatch.on('changeState', function(e) {
9164                 if (typeof e.disabled !== 'undefined') {
9165                     data.forEach(function(series,i) {
9166                         series.disabled = e.disabled[i];
9167                     });
9168                     state.disabled = e.disabled;
9169                 }
9170                 chart.update();
9171             });
9172
9173         });
9174
9175         renderWatch.renderEnd('pieChart immediate');
9176         return chart;
9177     }
9178
9179     //============================================================
9180     // Event Handling/Dispatching (out of chart's scope)
9181     //------------------------------------------------------------
9182
9183     pie.dispatch.on('elementMouseover.tooltip', function(e) {
9184         e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9185         dispatch.tooltipShow(e);
9186     });
9187
9188     dispatch.on('tooltipShow', function(e) {
9189         if (tooltips) showTooltip(e);
9190     });
9191
9192     dispatch.on('tooltipHide', function() {
9193         if (tooltips) nv.tooltip.cleanup();
9194     });
9195
9196     //============================================================
9197     // Expose Public Variables
9198     //------------------------------------------------------------
9199
9200     // expose chart's sub-components
9201     chart.legend = legend;
9202     chart.dispatch = dispatch;
9203     chart.pie = pie;
9204     chart.options = nv.utils.optionsFunc.bind(chart);
9205
9206     // use Object get/set functionality to map between vars and chart functions
9207     chart._options = Object.create({}, {
9208         // simple options, just get/set the necessary values
9209         noData:         {get: function(){return noData;},         set: function(_){noData=_;}},
9210         tooltipContent: {get: function(){return tooltip;},        set: function(_){tooltip=_;}},
9211         tooltips:       {get: function(){return tooltips;},       set: function(_){tooltips=_;}},
9212         showLegend:     {get: function(){return showLegend;},     set: function(_){showLegend=_;}},
9213         defaultState:   {get: function(){return defaultState;},   set: function(_){defaultState=_;}},
9214         // options that require extra logic in the setter
9215         color: {get: function(){return color;}, set: function(_){
9216             color = _;
9217             legend.color(color);
9218             pie.color(color);
9219         }},
9220         duration: {get: function(){return duration;}, set: function(_){
9221             duration = _;
9222             renderWatch.reset(duration);
9223         }}
9224     });
9225     nv.utils.inheritOptions(chart, pie);
9226     nv.utils.initOptions(chart);
9227     return chart;
9228 };
9229
9230 nv.models.scatter = function() {
9231     "use strict";
9232
9233     //============================================================
9234     // Public Variables with Default Settings
9235     //------------------------------------------------------------
9236
9237     var margin       = {top: 0, right: 0, bottom: 0, left: 0}
9238         , width        = null
9239         , height       = null
9240         , color        = nv.utils.defaultColor() // chooses color
9241         , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
9242         , x            = d3.scale.linear()
9243         , y            = d3.scale.linear()
9244         , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
9245         , getX         = function(d) { return d.x } // accessor to get the x value
9246         , getY         = function(d) { return d.y } // accessor to get the y value
9247         , getSize      = function(d) { return d.size || 1} // accessor to get the point size
9248         , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
9249         , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
9250         , forceY       = [] // List of numbers to Force into the Y scale
9251         , forceSize    = [] // List of numbers to Force into the Size scale
9252         , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
9253         , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
9254         , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9255         , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
9256         , clipEdge     = false // if true, masks points within x and y scale
9257         , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
9258         , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
9259         , xDomain      = null // Override x domain (skips the calculation from data)
9260         , yDomain      = null // Override y domain
9261         , xRange       = null // Override x range
9262         , yRange       = null // Override y range
9263         , sizeDomain   = null // Override point size domain
9264         , sizeRange    = null
9265         , singlePoint  = false
9266         , dispatch     = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
9267         , useVoronoi   = true
9268         , duration     = 250
9269         ;
9270
9271
9272     //============================================================
9273     // Private Variables
9274     //------------------------------------------------------------
9275
9276     var x0, y0, z0 // used to store previous scales
9277         , timeoutID
9278         , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
9279         , renderWatch = nv.utils.renderWatch(dispatch, duration)
9280         ;
9281
9282     function chart(selection) {
9283         renderWatch.reset();
9284         selection.each(function(data) {
9285             var container = d3.select(this);
9286             var availableWidth = (width  || parseInt(container.style('width')) || 960)
9287                 - margin.left - margin.right;
9288             var availableHeight = (height || parseInt(container.style('height')) || 400)
9289                 - margin.top - margin.bottom;
9290
9291             nv.utils.initSVG(container);
9292
9293             //add series index to each data point for reference
9294             data.forEach(function(series, i) {
9295                 series.values.forEach(function(point) {
9296                     point.series = i;
9297                 });
9298             });
9299
9300             // Setup Scales
9301             // remap and flatten the data for use in calculating the scales' domains
9302             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
9303                 d3.merge(
9304                     data.map(function(d) {
9305                         return d.values.map(function(d,i) {
9306                             return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
9307                         })
9308                     })
9309                 );
9310
9311             x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
9312
9313             if (padData && data[0])
9314                 x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
9315             //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
9316             else
9317                 x.range(xRange || [0, availableWidth]);
9318
9319             y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
9320                 .range(yRange || [availableHeight, 0]);
9321
9322             z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
9323                 .range(sizeRange || [16, 256]);
9324
9325             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9326             if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
9327             if (x.domain()[0] === x.domain()[1])
9328                 x.domain()[0] ?
9329                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9330                     : x.domain([-1,1]);
9331
9332             if (y.domain()[0] === y.domain()[1])
9333                 y.domain()[0] ?
9334                     y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
9335                     : y.domain([-1,1]);
9336
9337             if ( isNaN(x.domain()[0])) {
9338                 x.domain([-1,1]);
9339             }
9340
9341             if ( isNaN(y.domain()[0])) {
9342                 y.domain([-1,1]);
9343             }
9344
9345             x0 = x0 || x;
9346             y0 = y0 || y;
9347             z0 = z0 || z;
9348
9349             // Setup containers and skeleton of chart
9350             var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
9351             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
9352             var defsEnter = wrapEnter.append('defs');
9353             var gEnter = wrapEnter.append('g');
9354             var g = wrap.select('g');
9355
9356             gEnter.append('g').attr('class', 'nv-groups');
9357             gEnter.append('g').attr('class', 'nv-point-paths');
9358
9359             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9360
9361             defsEnter.append('clipPath')
9362                 .attr('id', 'nv-edge-clip-' + id)
9363                 .append('rect');
9364
9365             wrap.select('#nv-edge-clip-' + id + ' rect')
9366                 .attr('width', availableWidth)
9367                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
9368
9369             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
9370
9371             function updateInteractiveLayer() {
9372
9373                 if (!interactive) return false;
9374
9375                 var eventElements;
9376
9377                 var vertices = d3.merge(data.map(function(group, groupIndex) {
9378                         return group.values
9379                             .map(function(point, pointIndex) {
9380                                 // *Adding noise to make duplicates very unlikely
9381                                 // *Injecting series and point index for reference
9382                                 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
9383                                  */
9384                                 var pX = getX(point,pointIndex);
9385                                 var pY = getY(point,pointIndex);
9386
9387                                 return [x(pX)+ Math.random() * 1e-7,
9388                                         y(pY)+ Math.random() * 1e-7,
9389                                     groupIndex,
9390                                     pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
9391                             })
9392                             .filter(function(pointArray, pointIndex) {
9393                                 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
9394                             })
9395                     })
9396                 );
9397
9398                 //inject series and point index for reference into voronoi
9399                 if (useVoronoi === true) {
9400
9401                     if(vertices.length < 3) {
9402                         // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
9403                         vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
9404                         vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
9405                         vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
9406                         vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
9407                     }
9408
9409                     // keep voronoi sections from going more than 10 outside of graph
9410                     // to avoid overlap with other things like legend etc
9411                     var bounds = d3.geom.polygon([
9412                         [-10,-10],
9413                         [-10,height + 10],
9414                         [width + 10,height + 10],
9415                         [width + 10,-10]
9416                     ]);
9417
9418                     var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
9419                         return {
9420                             'data': bounds.clip(d),
9421                             'series': vertices[i][2],
9422                             'point': vertices[i][3]
9423                         }
9424                     });
9425
9426                     // nuke all voronoi paths on reload and recreate them
9427                     wrap.select('.nv-point-paths').selectAll('path').remove();
9428                     var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
9429                     pointPaths
9430                         .enter().append("svg:path")
9431                         .attr("d", function(d) {
9432                             if (!d || !d.data || d.data.length === 0)
9433                                 return 'M 0 0';
9434                             else
9435                                 return "M" + d.data.join(",") + "Z";
9436                         })
9437                         .attr("id", function(d,i) {
9438                             return "nv-path-"+i; })
9439                         .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
9440                         ;
9441                         // chain these to above to see the voronoi elements (good for debugging)
9442                         //.style("fill", d3.rgb(230, 230, 230))
9443                         //.style('fill-opacity', 0.4)
9444                         //.style('stroke-opacity', 1)
9445                         //.style("stroke", d3.rgb(200,200,200));
9446
9447                     if (clipVoronoi) {
9448                         // voronoi sections are already set to clip,
9449                         // just create the circles with the IDs they expect
9450                         var clips = wrap.append("svg:g").attr("id", "nv-point-clips");
9451                         clips.selectAll("clipPath")
9452                             .data(vertices)
9453                             .enter().append("svg:clipPath")
9454                             .attr("id", function(d, i) { return "nv-clip-"+i;})
9455                             .append("svg:circle")
9456                             .attr('cx', function(d) { return d[0]; })
9457                             .attr('cy', function(d) { return d[1]; })
9458                             .attr('r', clipRadius);
9459                     }
9460
9461                     var mouseEventCallback = function(d,mDispatch) {
9462                         if (needsUpdate) return 0;
9463                         var series = data[d.series];
9464                         if (typeof series === 'undefined') return;
9465                         var point  = series.values[d.point];
9466
9467                         mDispatch({
9468                             point: point,
9469                             series: series,
9470                             pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
9471                             seriesIndex: d.series,
9472                             pointIndex: d.point
9473                         });
9474                     };
9475
9476                     pointPaths
9477                         .on('click', function(d) {
9478                             mouseEventCallback(d, dispatch.elementClick);
9479                         })
9480                         .on('dblclick', function(d) {
9481                             mouseEventCallback(d, dispatch.elementDblClick);
9482                         })
9483                         .on('mouseover', function(d) {
9484                             mouseEventCallback(d, dispatch.elementMouseover);
9485                         })
9486                         .on('mouseout', function(d, i) {
9487                             mouseEventCallback(d, dispatch.elementMouseout);
9488                         });
9489
9490                 } else {
9491                     /*
9492                      // bring data in form needed for click handlers
9493                      var dataWithPoints = vertices.map(function(d, i) {
9494                      return {
9495                      'data': d,
9496                      'series': vertices[i][2],
9497                      'point': vertices[i][3]
9498                      }
9499                      });
9500                      */
9501
9502                     // add event handlers to points instead voronoi paths
9503                     wrap.select('.nv-groups').selectAll('.nv-group')
9504                         .selectAll('.nv-point')
9505                         //.data(dataWithPoints)
9506                         //.style('pointer-events', 'auto') // recativate events, disabled by css
9507                         .on('click', function(d,i) {
9508                             //nv.log('test', d, i);
9509                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9510                             var series = data[d.series],
9511                                 point  = series.values[i];
9512
9513                             dispatch.elementClick({
9514                                 point: point,
9515                                 series: series,
9516                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
9517                                 seriesIndex: d.series,
9518                                 pointIndex: i
9519                             });
9520                         })
9521                         .on('mouseover', function(d,i) {
9522                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9523                             var series = data[d.series],
9524                                 point  = series.values[i];
9525
9526                             dispatch.elementMouseover({
9527                                 point: point,
9528                                 series: series,
9529                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
9530                                 seriesIndex: d.series,
9531                                 pointIndex: i
9532                             });
9533                         })
9534                         .on('mouseout', function(d,i) {
9535                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9536                             var series = data[d.series],
9537                                 point  = series.values[i];
9538
9539                             dispatch.elementMouseout({
9540                                 point: point,
9541                                 series: series,
9542                                 seriesIndex: d.series,
9543                                 pointIndex: i
9544                             });
9545                         });
9546                 }
9547
9548                 needsUpdate = false;
9549             }
9550
9551             needsUpdate = true;
9552             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
9553                 .data(function(d) { return d }, function(d) { return d.key });
9554             groups.enter().append('g')
9555                 .style('stroke-opacity', 1e-6)
9556                 .style('fill-opacity', 1e-6);
9557             groups.exit()
9558                 .remove();
9559             groups
9560                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
9561                 .classed('hover', function(d) { return d.hover });
9562             groups.watchTransition(renderWatch, 'scatter: groups')
9563                 .style('fill', function(d,i) { return color(d, i) })
9564                 .style('stroke', function(d,i) { return color(d, i) })
9565                 .style('stroke-opacity', 1)
9566                 .style('fill-opacity', .5);
9567
9568             // create the points
9569             var points = groups.selectAll('path.nv-point')
9570                 .data(function(d) { return d.values });
9571             points.enter().append('path')
9572                 .style('fill', function (d,i) { return d.color })
9573                 .style('stroke', function (d,i) { return d.color })
9574                 .attr('transform', function(d,i) {
9575                     return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
9576                 })
9577                 .attr('d',
9578                     nv.utils.symbol()
9579                     .type(getShape)
9580                     .size(function(d,i) { return z(getSize(d,i)) })
9581             );
9582             points.exit().remove();
9583             groups.exit().selectAll('path.nv-point')
9584                 .watchTransition(renderWatch, 'scatter exit')
9585                 .attr('transform', function(d,i) {
9586                     return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
9587                 })
9588                 .remove();
9589             points.each(function(d,i) {
9590                 d3.select(this)
9591                     .classed('nv-point', true)
9592                     .classed('nv-point-' + i, true)
9593                     .classed('hover',false)
9594                 ;
9595             });
9596             points
9597                 .watchTransition(renderWatch, 'scatter points')
9598                 .attr('transform', function(d,i) {
9599                     //nv.log(d,i,getX(d,i), x(getX(d,i)));
9600                     return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
9601                 })
9602                 .attr('d',
9603                     nv.utils.symbol()
9604                     .type(getShape)
9605                     .size(function(d,i) { return z(getSize(d,i)) })
9606             );
9607
9608             // Delay updating the invisible interactive layer for smoother animation
9609             clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
9610             timeoutID = setTimeout(updateInteractiveLayer, 300);
9611             //updateInteractiveLayer();
9612
9613             //store old scales for use in transitions on update
9614             x0 = x.copy();
9615             y0 = y.copy();
9616             z0 = z.copy();
9617
9618         });
9619         renderWatch.renderEnd('scatter immediate');
9620         return chart;
9621     }
9622
9623     //============================================================
9624     // Expose Public Variables
9625     //------------------------------------------------------------
9626
9627     chart.dispatch = dispatch;
9628     chart.options = nv.utils.optionsFunc.bind(chart);
9629
9630     // utility function calls provided by this chart
9631     chart._calls = new function() {
9632         this.clearHighlights = function () {
9633             d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover", false);
9634             return null;
9635         };
9636         this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
9637             d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
9638                 .classed("hover", isHoverOver);
9639         };
9640     };
9641
9642     // trigger calls from events too
9643     dispatch.on('elementMouseover.point', function(d) {
9644         if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
9645     });
9646
9647     dispatch.on('elementMouseout.point', function(d) {
9648         if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
9649     });
9650
9651     chart._options = Object.create({}, {
9652         // simple options, just get/set the necessary values
9653         width:        {get: function(){return width;}, set: function(_){width=_;}},
9654         height:       {get: function(){return height;}, set: function(_){height=_;}},
9655         xScale:       {get: function(){return x;}, set: function(_){x=_;}},
9656         yScale:       {get: function(){return y;}, set: function(_){y=_;}},
9657         pointScale:   {get: function(){return z;}, set: function(_){z=_;}},
9658         xDomain:      {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
9659         yDomain:      {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
9660         pointDomain:  {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
9661         xRange:       {get: function(){return xRange;}, set: function(_){xRange=_;}},
9662         yRange:       {get: function(){return yRange;}, set: function(_){yRange=_;}},
9663         pointRange:   {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
9664         forceX:       {get: function(){return forceX;}, set: function(_){forceX=_;}},
9665         forceY:       {get: function(){return forceY;}, set: function(_){forceY=_;}},
9666         forcePoint:   {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
9667         interactive:  {get: function(){return interactive;}, set: function(_){interactive=_;}},
9668         pointActive:  {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
9669         padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
9670         padData:      {get: function(){return padData;}, set: function(_){padData=_;}},
9671         clipEdge:     {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
9672         clipVoronoi:  {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
9673         clipRadius:   {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
9674         id:           {get: function(){return id;}, set: function(_){id=_;}},
9675
9676
9677         // simple functor options
9678         x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
9679         y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
9680         pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
9681         pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
9682
9683         // options that require extra logic in the setter
9684         margin: {get: function(){return margin;}, set: function(_){
9685             margin.top    = _.top    !== undefined ? _.top    : margin.top;
9686             margin.right  = _.right  !== undefined ? _.right  : margin.right;
9687             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9688             margin.left   = _.left   !== undefined ? _.left   : margin.left;
9689         }},
9690         duration: {get: function(){return duration;}, set: function(_){
9691             duration = _;
9692             renderWatch.reset(duration);
9693         }},
9694         color: {get: function(){return color;}, set: function(_){
9695             color = nv.utils.getColor(_);
9696         }},
9697         useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
9698             useVoronoi = _;
9699             if (useVoronoi === false) {
9700                 clipVoronoi = false;
9701             }
9702         }}
9703     });
9704
9705     nv.utils.initOptions(chart);
9706     return chart;
9707 };
9708
9709 nv.models.scatterChart = function() {
9710     "use strict";
9711
9712     //============================================================
9713     // Public Variables with Default Settings
9714     //------------------------------------------------------------
9715
9716     var scatter      = nv.models.scatter()
9717         , xAxis        = nv.models.axis()
9718         , yAxis        = nv.models.axis()
9719         , legend       = nv.models.legend()
9720         , distX        = nv.models.distribution()
9721         , distY        = nv.models.distribution()
9722         ;
9723
9724     var margin       = {top: 30, right: 20, bottom: 50, left: 75}
9725         , width        = null
9726         , height       = null
9727         , color        = nv.utils.defaultColor()
9728         , x            = scatter.xScale()
9729         , y            = scatter.yScale()
9730         , showDistX    = false
9731         , showDistY    = false
9732         , showLegend   = true
9733         , showXAxis    = true
9734         , showYAxis    = true
9735         , rightAlignYAxis = false
9736         , tooltips     = true
9737         , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
9738         , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
9739         , tooltip      = function(key, x, y, date) { return '<h3>' + key + '</h3>'
9740             + '<p>' + date + '</p>' }
9741         , state = nv.utils.state()
9742         , defaultState = null
9743         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
9744         , noData       = "No Data Available."
9745         , duration = 250
9746         ;
9747
9748     scatter
9749         .xScale(x)
9750         .yScale(y)
9751     ;
9752     xAxis
9753         .orient('bottom')
9754         .tickPadding(10)
9755     ;
9756     yAxis
9757         .orient((rightAlignYAxis) ? 'right' : 'left')
9758         .tickPadding(10)
9759     ;
9760     distX
9761         .axis('x')
9762     ;
9763     distY
9764         .axis('y')
9765     ;
9766
9767     //============================================================
9768     // Private Variables
9769     //------------------------------------------------------------
9770
9771     var x0, y0
9772         , renderWatch = nv.utils.renderWatch(dispatch, duration);
9773
9774     var showTooltip = function(e, offsetElement) {
9775         //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?
9776         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9777             top = e.pos[1] + ( offsetElement.offsetTop || 0),
9778             leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9779             topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
9780             leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
9781             topY = e.pos[1] + ( offsetElement.offsetTop || 0),
9782             xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
9783             yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
9784
9785         if( tooltipX != null )
9786             nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
9787         if( tooltipY != null )
9788             nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
9789         if( tooltip != null )
9790             nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
9791     };
9792
9793     var stateGetter = function(data) {
9794         return function(){
9795             return {
9796                 active: data.map(function(d) { return !d.disabled })
9797             };
9798         }
9799     };
9800
9801     var stateSetter = function(data) {
9802         return function(state) {
9803             if (state.active !== undefined)
9804                 data.forEach(function(series,i) {
9805                     series.disabled = !state.active[i];
9806                 });
9807         }
9808     };
9809
9810     function chart(selection) {
9811         renderWatch.reset();
9812         renderWatch.models(scatter);
9813         if (showXAxis) renderWatch.models(xAxis);
9814         if (showYAxis) renderWatch.models(yAxis);
9815         if (showDistX) renderWatch.models(distX);
9816         if (showDistY) renderWatch.models(distY);
9817
9818         selection.each(function(data) {
9819             var container = d3.select(this),
9820                 that = this;
9821             nv.utils.initSVG(container);
9822
9823             var availableWidth = (width  || parseInt(container.style('width')) || 960)
9824                     - margin.left - margin.right,
9825                 availableHeight = (height || parseInt(container.style('height')) || 400)
9826                     - margin.top - margin.bottom;
9827
9828             chart.update = function() {
9829                 if (duration === 0)
9830                     container.call(chart);
9831                 else
9832                     container.transition().duration(duration).call(chart);
9833             };
9834             chart.container = this;
9835
9836             state
9837                 .setter(stateSetter(data), chart.update)
9838                 .getter(stateGetter(data))
9839                 .update();
9840
9841             // DEPRECATED set state.disableddisabled
9842             state.disabled = data.map(function(d) { return !!d.disabled });
9843
9844             if (!defaultState) {
9845                 var key;
9846                 defaultState = {};
9847                 for (key in state) {
9848                     if (state[key] instanceof Array)
9849                         defaultState[key] = state[key].slice(0);
9850                     else
9851                         defaultState[key] = state[key];
9852                 }
9853             }
9854
9855             // Display noData message if there's nothing to show.
9856             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9857                 var noDataText = container.selectAll('.nv-noData').data([noData]);
9858
9859                 noDataText.enter().append('text')
9860                     .attr('class', 'nvd3 nv-noData')
9861                     .attr('dy', '-.7em')
9862                     .style('text-anchor', 'middle');
9863
9864                 noDataText
9865                     .attr('x', margin.left + availableWidth / 2)
9866                     .attr('y', margin.top + availableHeight / 2)
9867                     .text(function(d) { return d });
9868
9869                 renderWatch.renderEnd('scatter immediate');
9870
9871                 return chart;
9872             } else {
9873                 container.selectAll('.nv-noData').remove();
9874             }
9875
9876             // Setup Scales
9877             x = scatter.xScale();
9878             y = scatter.yScale();
9879
9880             // Setup containers and skeleton of chart
9881             var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
9882             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
9883             var gEnter = wrapEnter.append('g');
9884             var g = wrap.select('g');
9885
9886             // background for pointer events
9887             gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
9888
9889             gEnter.append('g').attr('class', 'nv-x nv-axis');
9890             gEnter.append('g').attr('class', 'nv-y nv-axis');
9891             gEnter.append('g').attr('class', 'nv-scatterWrap');
9892             gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
9893             gEnter.append('g').attr('class', 'nv-distWrap');
9894             gEnter.append('g').attr('class', 'nv-legendWrap');
9895
9896             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9897
9898             if (rightAlignYAxis) {
9899                 g.select(".nv-y.nv-axis")
9900                     .attr("transform", "translate(" + availableWidth + ",0)");
9901             }
9902
9903             // Legend
9904             if (showLegend) {
9905                 legend.width( availableWidth / 2 );
9906
9907                 wrap.select('.nv-legendWrap')
9908                     .datum(data)
9909                     .call(legend);
9910
9911                 if ( margin.top != legend.height()) {
9912                     margin.top = legend.height();
9913                     availableHeight = (height || parseInt(container.style('height')) || 400)
9914                         - margin.top - margin.bottom;
9915                 }
9916
9917                 wrap.select('.nv-legendWrap')
9918                     .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
9919             }
9920
9921             // Main Chart Component(s)
9922             scatter
9923                 .width(availableWidth)
9924                 .height(availableHeight)
9925                 .color(data.map(function(d,i) {
9926                     return d.color || color(d, i);
9927                 }).filter(function(d,i) { return !data[i].disabled }));
9928
9929             wrap.select('.nv-scatterWrap')
9930                 .datum(data.filter(function(d) { return !d.disabled }))
9931                 .call(scatter);
9932
9933
9934             wrap.select('.nv-regressionLinesWrap')
9935                 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
9936
9937             var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
9938                 .data(function (d) {
9939                     return d;
9940                 });
9941
9942             regWrap.enter().append('g').attr('class', 'nv-regLines');
9943
9944             var regLine = regWrap.selectAll('.nv-regLine')
9945                 .data(function (d) {
9946                     return [d]
9947                 });
9948
9949             regLine.enter()
9950                 .append('line').attr('class', 'nv-regLine')
9951                 .style('stroke-opacity', 0);
9952
9953             // don't add lines unless we have slope and intercept to use
9954             regLine.filter(function(d) {
9955                 return d.intercept && d.slope;
9956             })
9957                 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
9958                 .attr('x1', x.range()[0])
9959                 .attr('x2', x.range()[1])
9960                 .attr('y1', function (d, i) {
9961                     return y(x.domain()[0] * d.slope + d.intercept)
9962                 })
9963                 .attr('y2', function (d, i) {
9964                     return y(x.domain()[1] * d.slope + d.intercept)
9965                 })
9966                 .style('stroke', function (d, i, j) {
9967                     return color(d, j)
9968                 })
9969                 .style('stroke-opacity', function (d, i) {
9970                     return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
9971                 });
9972
9973             // Setup Axes
9974             if (showXAxis) {
9975                 xAxis
9976                     .scale(x)
9977                     .ticks( xAxis.ticks() ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
9978                     .tickSize( -availableHeight , 0);
9979
9980                 g.select('.nv-x.nv-axis')
9981                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
9982                     .call(xAxis);
9983             }
9984
9985             if (showYAxis) {
9986                 yAxis
9987                     .scale(y)
9988                     .ticks( yAxis.ticks() ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
9989                     .tickSize( -availableWidth, 0);
9990
9991                 g.select('.nv-y.nv-axis')
9992                     .call(yAxis);
9993             }
9994
9995
9996             if (showDistX) {
9997                 distX
9998                     .getData(scatter.x())
9999                     .scale(x)
10000                     .width(availableWidth)
10001                     .color(data.map(function(d,i) {
10002                         return d.color || color(d, i);
10003                     }).filter(function(d,i) { return !data[i].disabled }));
10004                 gEnter.select('.nv-distWrap').append('g')
10005                     .attr('class', 'nv-distributionX');
10006                 g.select('.nv-distributionX')
10007                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
10008                     .datum(data.filter(function(d) { return !d.disabled }))
10009                     .call(distX);
10010             }
10011
10012             if (showDistY) {
10013                 distY
10014                     .getData(scatter.y())
10015                     .scale(y)
10016                     .width(availableHeight)
10017                     .color(data.map(function(d,i) {
10018                         return d.color || color(d, i);
10019                     }).filter(function(d,i) { return !data[i].disabled }));
10020                 gEnter.select('.nv-distWrap').append('g')
10021                     .attr('class', 'nv-distributionY');
10022                 g.select('.nv-distributionY')
10023                     .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
10024                     .datum(data.filter(function(d) { return !d.disabled }))
10025                     .call(distY);
10026             }
10027
10028             //============================================================
10029             // Event Handling/Dispatching (in chart's scope)
10030             //------------------------------------------------------------
10031
10032             legend.dispatch.on('stateChange', function(newState) {
10033                 for (var key in newState)
10034                     state[key] = newState[key];
10035                 dispatch.stateChange(state);
10036                 chart.update();
10037             });
10038
10039
10040             scatter.dispatch.on('elementMouseover.tooltip', function(e) {
10041                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
10042                     .attr('y1', e.pos[1] - availableHeight);
10043                 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
10044                     .attr('x2', e.pos[0] + distX.size());
10045
10046                 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10047                 dispatch.tooltipShow(e);
10048             });
10049
10050             dispatch.on('tooltipShow', function(e) {
10051                 if (tooltips) showTooltip(e, that.parentNode);
10052             });
10053
10054             // Update chart from a state object passed to event handler
10055             dispatch.on('changeState', function(e) {
10056
10057                 if (typeof e.disabled !== 'undefined') {
10058                     data.forEach(function(series,i) {
10059                         series.disabled = e.disabled[i];
10060                     });
10061
10062                     state.disabled = e.disabled;
10063                 }
10064
10065                 chart.update();
10066             });
10067
10068             //store old scales for use in transitions on update
10069             x0 = x.copy();
10070             y0 = y.copy();
10071
10072         });
10073
10074         renderWatch.renderEnd('scatter with line immediate');
10075         return chart;
10076     }
10077
10078     //============================================================
10079     // Event Handling/Dispatching (out of chart's scope)
10080     //------------------------------------------------------------
10081
10082     scatter.dispatch.on('elementMouseout.tooltip', function(e) {
10083         dispatch.tooltipHide(e);
10084
10085         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
10086             .attr('y1', 0);
10087         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
10088             .attr('x2', distY.size());
10089     });
10090     dispatch.on('tooltipHide', function() {
10091         if (tooltips) nv.tooltip.cleanup();
10092     });
10093
10094     //============================================================
10095     // Expose Public Variables
10096     //------------------------------------------------------------
10097
10098     // expose chart's sub-components
10099     chart.dispatch = dispatch;
10100     chart.scatter = scatter;
10101     chart.legend = legend;
10102     chart.xAxis = xAxis;
10103     chart.yAxis = yAxis;
10104     chart.distX = distX;
10105     chart.distY = distY;
10106
10107     chart._options = Object.create({}, {
10108         // simple options, just get/set the necessary values
10109         width:      {get: function(){return width;}, set: function(_){width=_;}},
10110         height:     {get: function(){return height;}, set: function(_){height=_;}},
10111         showDistX:  {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
10112         showDistY:  {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
10113         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
10114         showXAxis:  {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
10115         showYAxis:  {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
10116         tooltips:   {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
10117         tooltipContent:   {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
10118         tooltipXContent:  {get: function(){return tooltipX;}, set: function(_){tooltipX=_;}},
10119         tooltipYContent:  {get: function(){return tooltipY;}, set: function(_){tooltipY=_;}},
10120         defaultState:     {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
10121         noData:     {get: function(){return noData;}, set: function(_){noData=_;}},
10122         duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
10123
10124         // options that require extra logic in the setter
10125         margin: {get: function(){return margin;}, set: function(_){
10126             margin.top    = _.top    !== undefined ? _.top    : margin.top;
10127             margin.right  = _.right  !== undefined ? _.right  : margin.right;
10128             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10129             margin.left   = _.left   !== undefined ? _.left   : margin.left;
10130         }},
10131         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
10132             rightAlignYAxis = _;
10133             yAxis.orient( (_) ? 'right' : 'left');
10134         }},
10135         color: {get: function(){return color;}, set: function(_){
10136             color = nv.utils.getColor(_);
10137             legend.color(color);
10138             distX.color(color);
10139             distY.color(color);
10140         }}
10141     });
10142
10143     nv.utils.inheritOptions(chart, scatter);
10144     nv.utils.initOptions(chart);
10145     return chart;
10146 };
10147
10148 nv.models.sparkline = function() {
10149     "use strict";
10150
10151     //============================================================
10152     // Public Variables with Default Settings
10153     //------------------------------------------------------------
10154
10155     var margin = {top: 2, right: 0, bottom: 2, left: 0}
10156         , width = 400
10157         , height = 32
10158         , animate = true
10159         , x = d3.scale.linear()
10160         , y = d3.scale.linear()
10161         , getX = function(d) { return d.x }
10162         , getY = function(d) { return d.y }
10163         , color = nv.utils.getColor(['#000'])
10164         , xDomain
10165         , yDomain
10166         , xRange
10167         , yRange
10168         ;
10169
10170     function chart(selection) {
10171         selection.each(function(data) {
10172             var availableWidth = width - margin.left - margin.right,
10173                 availableHeight = height - margin.top - margin.bottom,
10174                 container = d3.select(this);
10175             nv.utils.initSVG(container);
10176
10177             // Setup Scales
10178             x   .domain(xDomain || d3.extent(data, getX ))
10179                 .range(xRange || [0, availableWidth]);
10180
10181             y   .domain(yDomain || d3.extent(data, getY ))
10182                 .range(yRange || [availableHeight, 0]);
10183
10184             // Setup containers and skeleton of chart
10185             var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
10186             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
10187             var gEnter = wrapEnter.append('g');
10188             var g = wrap.select('g');
10189
10190             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
10191
10192             var paths = wrap.selectAll('path')
10193                 .data(function(d) { return [d] });
10194             paths.enter().append('path');
10195             paths.exit().remove();
10196             paths
10197                 .style('stroke', function(d,i) { return d.color || color(d, i) })
10198                 .attr('d', d3.svg.line()
10199                     .x(function(d,i) { return x(getX(d,i)) })
10200                     .y(function(d,i) { return y(getY(d,i)) })
10201             );
10202
10203             // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
10204             var points = wrap.selectAll('circle.nv-point')
10205                 .data(function(data) {
10206                     var yValues = data.map(function(d, i) { return getY(d,i); });
10207                     function pointIndex(index) {
10208                         if (index != -1) {
10209                             var result = data[index];
10210                             result.pointIndex = index;
10211                             return result;
10212                         } else {
10213                             return null;
10214                         }
10215                     }
10216                     var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
10217                         minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
10218                         currentPoint = pointIndex(yValues.length - 1);
10219                     return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
10220                 });
10221             points.enter().append('circle');
10222             points.exit().remove();
10223             points
10224                 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
10225                 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
10226                 .attr('r', 2)
10227                 .attr('class', function(d,i) {
10228                     return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
10229                             getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
10230                 });
10231         });
10232
10233         return chart;
10234     }
10235
10236     //============================================================
10237     // Expose Public Variables
10238     //------------------------------------------------------------
10239
10240     chart.options = nv.utils.optionsFunc.bind(chart);
10241
10242     chart._options = Object.create({}, {
10243         // simple options, just get/set the necessary values
10244         width:     {get: function(){return width;}, set: function(_){width=_;}},
10245         height:    {get: function(){return height;}, set: function(_){height=_;}},
10246         xDomain:   {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
10247         yDomain:   {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
10248         xRange:    {get: function(){return xRange;}, set: function(_){xRange=_;}},
10249         yRange:    {get: function(){return yRange;}, set: function(_){yRange=_;}},
10250         xScale:    {get: function(){return x;}, set: function(_){x=_;}},
10251         yScale:    {get: function(){return y;}, set: function(_){y=_;}},
10252         animate:   {get: function(){return animate;}, set: function(_){animate=_;}},
10253
10254         //functor options
10255         x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
10256         y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
10257
10258         // options that require extra logic in the setter
10259         margin: {get: function(){return margin;}, set: function(_){
10260             margin.top    = _.top    !== undefined ? _.top    : margin.top;
10261             margin.right  = _.right  !== undefined ? _.right  : margin.right;
10262             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10263             margin.left   = _.left   !== undefined ? _.left   : margin.left;
10264         }},
10265         color:  {get: function(){return color;}, set: function(_){
10266             color = nv.utils.getColor(_);
10267         }}
10268     });
10269
10270     nv.utils.initOptions(chart);
10271     return chart;
10272 };
10273
10274 nv.models.sparklinePlus = function() {
10275     "use strict";
10276
10277     //============================================================
10278     // Public Variables with Default Settings
10279     //------------------------------------------------------------
10280
10281     var sparkline = nv.models.sparkline();
10282
10283     var margin = {top: 15, right: 100, bottom: 10, left: 50}
10284         , width = null
10285         , height = null
10286         , x
10287         , y
10288         , index = []
10289         , paused = false
10290         , xTickFormat = d3.format(',r')
10291         , yTickFormat = d3.format(',.2f')
10292         , showValue = true
10293         , alignValue = true
10294         , rightAlignValue = false
10295         , noData = "No Data Available."
10296         ;
10297
10298     function chart(selection) {
10299         selection.each(function(data) {
10300             var container = d3.select(this);
10301             nv.utils.initSVG(container);
10302
10303             var availableWidth = (width  || parseInt(container.style('width')) || 960)
10304                     - margin.left - margin.right,
10305                 availableHeight = (height || parseInt(container.style('height')) || 400)
10306                     - margin.top - margin.bottom;
10307
10308             chart.update = function() { chart(selection) };
10309             chart.container = this;
10310
10311             // Display No Data message if there's nothing to show.
10312             if (!data || !data.length) {
10313                 var noDataText = container.selectAll('.nv-noData').data([noData]);
10314
10315                 noDataText.enter().append('text')
10316                     .attr('class', 'nvd3 nv-noData')
10317                     .attr('dy', '-.7em')
10318                     .style('text-anchor', 'middle');
10319
10320                 noDataText
10321                     .attr('x', margin.left + availableWidth / 2)
10322                     .attr('y', margin.top + availableHeight / 2)
10323                     .text(function(d) { return d });
10324
10325                 return chart;
10326             } else {
10327                 container.selectAll('.nv-noData').remove();
10328             }
10329
10330             var currentValue = sparkline.y()(data[data.length-1], data.length-1);
10331
10332             // Setup Scales
10333             x = sparkline.xScale();
10334             y = sparkline.yScale();
10335
10336             // Setup containers and skeleton of chart
10337             var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
10338             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
10339             var gEnter = wrapEnter.append('g');
10340             var g = wrap.select('g');
10341
10342             gEnter.append('g').attr('class', 'nv-sparklineWrap');
10343             gEnter.append('g').attr('class', 'nv-valueWrap');
10344             gEnter.append('g').attr('class', 'nv-hoverArea');
10345
10346             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10347
10348             // Main Chart Component(s)
10349             var sparklineWrap = g.select('.nv-sparklineWrap');
10350
10351             sparkline.width(availableWidth).height(availableHeight);
10352             sparklineWrap.call(sparkline);
10353
10354             var valueWrap = g.select('.nv-valueWrap');
10355             var value = valueWrap.selectAll('.nv-currentValue')
10356                 .data([currentValue]);
10357
10358             value.enter().append('text').attr('class', 'nv-currentValue')
10359                 .attr('dx', rightAlignValue ? -8 : 8)
10360                 .attr('dy', '.9em')
10361                 .style('text-anchor', rightAlignValue ? 'end' : 'start');
10362
10363             value
10364                 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
10365                 .attr('y', alignValue ? function(d) { return y(d) } : 0)
10366                 .style('fill', sparkline.color()(data[data.length-1], data.length-1))
10367                 .text(yTickFormat(currentValue));
10368
10369             gEnter.select('.nv-hoverArea').append('rect')
10370                 .on('mousemove', sparklineHover)
10371                 .on('click', function() { paused = !paused })
10372                 .on('mouseout', function() { index = []; updateValueLine(); });
10373
10374             g.select('.nv-hoverArea rect')
10375                 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
10376                 .attr('width', availableWidth + margin.left + margin.right)
10377                 .attr('height', availableHeight + margin.top);
10378
10379             //index is currently global (within the chart), may or may not keep it that way
10380             function updateValueLine() {
10381                 if (paused) return;
10382
10383                 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
10384
10385                 var hoverEnter = hoverValue.enter()
10386                     .append('g').attr('class', 'nv-hoverValue')
10387                     .style('stroke-opacity', 0)
10388                     .style('fill-opacity', 0);
10389
10390                 hoverValue.exit()
10391                     .transition().duration(250)
10392                     .style('stroke-opacity', 0)
10393                     .style('fill-opacity', 0)
10394                     .remove();
10395
10396                 hoverValue
10397                     .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
10398                     .transition().duration(250)
10399                     .style('stroke-opacity', 1)
10400                     .style('fill-opacity', 1);
10401
10402                 if (!index.length) return;
10403
10404                 hoverEnter.append('line')
10405                     .attr('x1', 0)
10406                     .attr('y1', -margin.top)
10407                     .attr('x2', 0)
10408                     .attr('y2', availableHeight);
10409
10410                 hoverEnter.append('text').attr('class', 'nv-xValue')
10411                     .attr('x', -6)
10412                     .attr('y', -margin.top)
10413                     .attr('text-anchor', 'end')
10414                     .attr('dy', '.9em')
10415
10416                 g.select('.nv-hoverValue .nv-xValue')
10417                     .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
10418
10419                 hoverEnter.append('text').attr('class', 'nv-yValue')
10420                     .attr('x', 6)
10421                     .attr('y', -margin.top)
10422                     .attr('text-anchor', 'start')
10423                     .attr('dy', '.9em')
10424
10425                 g.select('.nv-hoverValue .nv-yValue')
10426                     .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
10427             }
10428
10429             function sparklineHover() {
10430                 if (paused) return;
10431
10432                 var pos = d3.mouse(this)[0] - margin.left;
10433
10434                 function getClosestIndex(data, x) {
10435                     var distance = Math.abs(sparkline.x()(data[0], 0) - x);
10436                     var closestIndex = 0;
10437                     for (var i = 0; i < data.length; i++){
10438                         if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
10439                             distance = Math.abs(sparkline.x()(data[i], i) - x);
10440                             closestIndex = i;
10441                         }
10442                     }
10443                     return closestIndex;
10444                 }
10445
10446                 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
10447                 updateValueLine();
10448             }
10449
10450         });
10451
10452         return chart;
10453     }
10454
10455     //============================================================
10456     // Expose Public Variables
10457     //------------------------------------------------------------
10458
10459     // expose chart's sub-components
10460     chart.sparkline = sparkline;
10461
10462     chart.options = nv.utils.optionsFunc.bind(chart);
10463
10464     chart._options = Object.create({}, {
10465         // simple options, just get/set the necessary values
10466         width:           {get: function(){return width;}, set: function(_){width=_;}},
10467         height:          {get: function(){return height;}, set: function(_){height=_;}},
10468         xTickFormat:     {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
10469         yTickFormat:     {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
10470         showValue:       {get: function(){return showValue;}, set: function(_){showValue=_;}},
10471         alignValue:      {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
10472         rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
10473         noData:          {get: function(){return noData;}, set: function(_){noData=_;}},
10474
10475         // options that require extra logic in the setter
10476         margin: {get: function(){return margin;}, set: function(_){
10477             margin.top    = _.top    !== undefined ? _.top    : margin.top;
10478             margin.right  = _.right  !== undefined ? _.right  : margin.right;
10479             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10480             margin.left   = _.left   !== undefined ? _.left   : margin.left;
10481         }}
10482     });
10483
10484     nv.utils.inheritOptions(chart, sparkline);
10485     nv.utils.initOptions(chart);
10486
10487     return chart;
10488 };
10489
10490 nv.models.stackedArea = function() {
10491     "use strict";
10492
10493     //============================================================
10494     // Public Variables with Default Settings
10495     //------------------------------------------------------------
10496
10497     var margin = {top: 0, right: 0, bottom: 0, left: 0}
10498         , width = 960
10499         , height = 500
10500         , color = nv.utils.defaultColor() // a function that computes the color
10501         , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
10502         , getX = function(d) { return d.x } // accessor to get the x value from a data point
10503         , getY = function(d) { return d.y } // accessor to get the y value from a data point
10504         , style = 'stack'
10505         , offset = 'zero'
10506         , order = 'default'
10507         , interpolate = 'linear'  // controls the line interpolation
10508         , clipEdge = false // if true, masks lines within x and y scale
10509         , x //can be accessed via chart.xScale()
10510         , y //can be accessed via chart.yScale()
10511         , scatter = nv.models.scatter()
10512         , duration = 250
10513         , dispatch =  d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout','renderEnd')
10514         ;
10515
10516     // scatter is interactive by default, but this chart isn't so must disable
10517     scatter.interactive(false);
10518
10519     scatter
10520         .pointSize(2.2) // default size
10521         .pointDomain([2.2, 2.2]) // all the same size by default
10522     ;
10523
10524     /************************************
10525      * offset:
10526      *   'wiggle' (stream)
10527      *   'zero' (stacked)
10528      *   'expand' (normalize to 100%)
10529      *   'silhouette' (simple centered)
10530      *
10531      * order:
10532      *   'inside-out' (stream)
10533      *   'default' (input order)
10534      ************************************/
10535
10536     var renderWatch = nv.utils.renderWatch(dispatch, duration);
10537
10538     function chart(selection) {
10539         renderWatch.reset();
10540         renderWatch.models(scatter);
10541         selection.each(function(data) {
10542             var availableWidth = width - margin.left - margin.right,
10543                 availableHeight = height - margin.top - margin.bottom,
10544                 container = d3.select(this);
10545             nv.utils.initSVG(container);
10546
10547             // Setup Scales
10548             x = scatter.xScale();
10549             y = scatter.yScale();
10550
10551             var dataRaw = data;
10552             // Injecting point index into each point because d3.layout.stack().out does not give index
10553             data.forEach(function(aseries, i) {
10554                 aseries.seriesIndex = i;
10555                 aseries.values = aseries.values.map(function(d, j) {
10556                     d.index = j;
10557                     d.seriesIndex = i;
10558                     return d;
10559                 });
10560             });
10561
10562             var dataFiltered = data.filter(function(series) {
10563                 return !series.disabled;
10564             });
10565
10566             data = d3.layout.stack()
10567                 .order(order)
10568                 .offset(offset)
10569                 .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
10570                 .x(getX)
10571                 .y(getY)
10572                 .out(function(d, y0, y) {
10573                     var yHeight = (getY(d) === 0) ? 0 : y;
10574                     d.display = {
10575                         y: yHeight,
10576                         y0: y0
10577                     };
10578                 })
10579             (dataFiltered);
10580
10581             // Setup containers and skeleton of chart
10582             var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
10583             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
10584             var defsEnter = wrapEnter.append('defs');
10585             var gEnter = wrapEnter.append('g');
10586             var g = wrap.select('g');
10587
10588             gEnter.append('g').attr('class', 'nv-areaWrap');
10589             gEnter.append('g').attr('class', 'nv-scatterWrap');
10590
10591             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10592
10593             scatter
10594                 .width(availableWidth)
10595                 .height(availableHeight)
10596                 .x(getX)
10597                 .y(function(d) { return d.display.y + d.display.y0 })
10598                 .forceY([0])
10599                 .color(data.map(function(d,i) {
10600                     return d.color || color(d, d.seriesIndex);
10601                 }));
10602
10603             var scatterWrap = g.select('.nv-scatterWrap')
10604                 .datum(data);
10605
10606             scatterWrap.call(scatter);
10607
10608             defsEnter.append('clipPath')
10609                 .attr('id', 'nv-edge-clip-' + id)
10610                 .append('rect');
10611
10612             wrap.select('#nv-edge-clip-' + id + ' rect')
10613                 .attr('width', availableWidth)
10614                 .attr('height', availableHeight);
10615
10616             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10617
10618             var area = d3.svg.area()
10619                 .x(function(d,i)  { return x(getX(d,i)) })
10620                 .y0(function(d) {
10621                     return y(d.display.y0)
10622                 })
10623                 .y1(function(d) {
10624                     return y(d.display.y + d.display.y0)
10625                 })
10626                 .interpolate(interpolate);
10627
10628             var zeroArea = d3.svg.area()
10629                 .x(function(d,i)  { return x(getX(d,i)) })
10630                 .y0(function(d) { return y(d.display.y0) })
10631                 .y1(function(d) { return y(d.display.y0) });
10632
10633             var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
10634                 .data(function(d) { return d });
10635
10636             path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
10637                 .attr('d', function(d,i){
10638                     return zeroArea(d.values, d.seriesIndex);
10639                 })
10640                 .on('mouseover', function(d,i) {
10641                     d3.select(this).classed('hover', true);
10642                     dispatch.areaMouseover({
10643                         point: d,
10644                         series: d.key,
10645                         pos: [d3.event.pageX, d3.event.pageY],
10646                         seriesIndex: d.seriesIndex
10647                     });
10648                 })
10649                 .on('mouseout', function(d,i) {
10650                     d3.select(this).classed('hover', false);
10651                     dispatch.areaMouseout({
10652                         point: d,
10653                         series: d.key,
10654                         pos: [d3.event.pageX, d3.event.pageY],
10655                         seriesIndex: d.seriesIndex
10656                     });
10657                 })
10658                 .on('click', function(d,i) {
10659                     d3.select(this).classed('hover', false);
10660                     dispatch.areaClick({
10661                         point: d,
10662                         series: d.key,
10663                         pos: [d3.event.pageX, d3.event.pageY],
10664                         seriesIndex: d.seriesIndex
10665                     });
10666                 });
10667
10668             path.exit().remove();
10669             path.style('fill', function(d,i){
10670                     return d.color || color(d, d.seriesIndex)
10671                 })
10672                 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
10673             path.watchTransition(renderWatch,'stackedArea path')
10674                 .attr('d', function(d,i) {
10675                     return area(d.values,i)
10676                 });
10677
10678             //============================================================
10679             // Event Handling/Dispatching (in chart's scope)
10680             //------------------------------------------------------------
10681
10682             scatter.dispatch.on('elementMouseover.area', function(e) {
10683                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
10684             });
10685             scatter.dispatch.on('elementMouseout.area', function(e) {
10686                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
10687             });
10688
10689             //Special offset functions
10690             chart.d3_stackedOffset_stackPercent = function(stackData) {
10691                 var n = stackData.length,    //How many series
10692                     m = stackData[0].length,     //how many points per series
10693                     k = 1 / n,
10694                     i,
10695                     j,
10696                     o,
10697                     y0 = [];
10698
10699                 for (j = 0; j < m; ++j) { //Looping through all points
10700                     for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through series'
10701                         o += getY(dataRaw[i].values[j]);   //total value of all points at a certian point in time.
10702                     }
10703
10704                     if (o) for (i = 0; i < n; i++) {
10705                         stackData[i][j][1] /= o;
10706                     } else {
10707                         for (i = 0; i < n; i++) {
10708                             stackData[i][j][1] = k;
10709                         }
10710                     }
10711                 }
10712                 for (j = 0; j < m; ++j) y0[j] = 0;
10713                 return y0;
10714             };
10715
10716         });
10717
10718         renderWatch.renderEnd('stackedArea immediate');
10719         return chart;
10720     }
10721
10722
10723     //============================================================
10724     // Event Handling/Dispatching (out of chart's scope)
10725     //------------------------------------------------------------
10726
10727     scatter.dispatch.on('elementClick.area', function(e) {
10728         dispatch.areaClick(e);
10729     });
10730     scatter.dispatch.on('elementMouseover.tooltip', function(e) {
10731         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10732             dispatch.tooltipShow(e);
10733     });
10734     scatter.dispatch.on('elementMouseout.tooltip', function(e) {
10735         dispatch.tooltipHide(e);
10736     });
10737
10738     //============================================================
10739     // Global getters and setters
10740     //------------------------------------------------------------
10741
10742     chart.dispatch = dispatch;
10743     chart.scatter = scatter;
10744
10745     chart.interpolate = function(_) {
10746         if (!arguments.length) return interpolate;
10747         interpolate = _;
10748         return chart;
10749     };
10750
10751     chart.duration = function(_) {
10752         if (!arguments.length) return duration;
10753         duration = _;
10754         renderWatch.reset(duration);
10755         scatter.duration(duration);
10756         return chart;
10757     };
10758
10759     chart.dispatch = dispatch;
10760     chart.options = nv.utils.optionsFunc.bind(chart);
10761
10762     chart._options = Object.create({}, {
10763         // simple options, just get/set the necessary values
10764         width:      {get: function(){return width;}, set: function(_){width=_;}},
10765         height:     {get: function(){return height;}, set: function(_){height=_;}},
10766         clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
10767         offset:      {get: function(){return offset;}, set: function(_){offset=_;}},
10768         order:    {get: function(){return order;}, set: function(_){order=_;}},
10769         interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
10770
10771         // simple functor options
10772         x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
10773         y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
10774
10775         // options that require extra logic in the setter
10776         margin: {get: function(){return margin;}, set: function(_){
10777             margin.top    = _.top    !== undefined ? _.top    : margin.top;
10778             margin.right  = _.right  !== undefined ? _.right  : margin.right;
10779             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10780             margin.left   = _.left   !== undefined ? _.left   : margin.left;
10781         }},
10782         color:  {get: function(){return color;}, set: function(_){
10783             color = nv.utils.getColor(_);
10784         }},
10785         style: {get: function(){return style;}, set: function(_){
10786             style = _;
10787             switch (style) {
10788                 case 'stack':
10789                     chart.offset('zero');
10790                     chart.order('default');
10791                     break;
10792                 case 'stream':
10793                     chart.offset('wiggle');
10794                     chart.order('inside-out');
10795                     break;
10796                 case 'stream-center':
10797                     chart.offset('silhouette');
10798                     chart.order('inside-out');
10799                     break;
10800                 case 'expand':
10801                     chart.offset('expand');
10802                     chart.order('default');
10803                     break;
10804                 case 'stack_percent':
10805                     chart.offset(chart.d3_stackedOffset_stackPercent);
10806                     chart.order('default');
10807                     break;
10808             }
10809         }},
10810         duration: {get: function(){return duration;}, set: function(_){
10811             duration = _;
10812             renderWatch.reset(duration);
10813             scatter.duration(duration);
10814         }}
10815     });
10816
10817     nv.utils.inheritOptions(chart, scatter);
10818     nv.utils.initOptions(chart);
10819
10820     return chart;
10821 };
10822
10823 nv.models.stackedAreaChart = function() {
10824     "use strict";
10825
10826     //============================================================
10827     // Public Variables with Default Settings
10828     //------------------------------------------------------------
10829
10830     var stacked = nv.models.stackedArea()
10831         , xAxis = nv.models.axis()
10832         , yAxis = nv.models.axis()
10833         , legend = nv.models.legend()
10834         , controls = nv.models.legend()
10835         , interactiveLayer = nv.interactiveGuideline()
10836         ;
10837
10838     var margin = {top: 30, right: 25, bottom: 50, left: 60}
10839         , width = null
10840         , height = null
10841         , color = nv.utils.defaultColor()
10842         , showControls = true
10843         , showLegend = true
10844         , showXAxis = true
10845         , showYAxis = true
10846         , rightAlignYAxis = false
10847         , useInteractiveGuideline = false
10848         , tooltips = true
10849         , tooltip = function(key, x, y, e, graph) {
10850             return '<h3>' + key + '</h3>' +
10851                 '<p>' +  y + ' on ' + x + '</p>'
10852         }
10853         , x //can be accessed via chart.xScale()
10854         , y //can be accessed via chart.yScale()
10855         , yAxisTickFormat = d3.format(',.2f')
10856         , state = nv.utils.state()
10857         , defaultState = null
10858         , noData = 'No Data Available.'
10859         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
10860         , controlWidth = 250
10861         , cData = ['Stacked','Stream','Expanded']
10862         , controlLabels = {}
10863         , duration = 250
10864         ;
10865
10866     state.style = stacked.style();
10867     xAxis.orient('bottom').tickPadding(7);
10868     yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
10869
10870     controls.updateState(false);
10871
10872     //============================================================
10873     // Private Variables
10874     //------------------------------------------------------------
10875
10876     var renderWatch = nv.utils.renderWatch(dispatch);
10877     var style = stacked.style();
10878
10879     var showTooltip = function(e, offsetElement) {
10880         var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10881             top = e.pos[1] + ( offsetElement.offsetTop || 0),
10882             x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
10883             y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
10884             content = tooltip(e.series.key, x, y, e, chart);
10885
10886         nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10887     };
10888
10889     var stateGetter = function(data) {
10890         return function(){
10891             return {
10892                 active: data.map(function(d) { return !d.disabled }),
10893                 style: stacked.style()
10894             };
10895         }
10896     };
10897
10898     var stateSetter = function(data) {
10899         return function(state) {
10900             if (state.style !== undefined)
10901                 style = state.style;
10902             if (state.active !== undefined)
10903                 data.forEach(function(series,i) {
10904                     series.disabled = !state.active[i];
10905                 });
10906         }
10907     };
10908
10909     function chart(selection) {
10910         renderWatch.reset();
10911         renderWatch.models(stacked);
10912         if (showXAxis) renderWatch.models(xAxis);
10913         if (showYAxis) renderWatch.models(yAxis);
10914
10915         selection.each(function(data) {
10916             var container = d3.select(this),
10917                 that = this;
10918             nv.utils.initSVG(container);
10919
10920             var availableWidth = (width  || parseInt(container.style('width')) || 960)
10921                     - margin.left - margin.right,
10922                 availableHeight = (height || parseInt(container.style('height')) || 400)
10923                     - margin.top - margin.bottom;
10924
10925             chart.update = function() { container.transition().duration(duration).call(chart); };
10926             chart.container = this;
10927
10928             state
10929                 .setter(stateSetter(data), chart.update)
10930                 .getter(stateGetter(data))
10931                 .update();
10932
10933             // DEPRECATED set state.disabled
10934             state.disabled = data.map(function(d) { return !!d.disabled });
10935
10936             if (!defaultState) {
10937                 var key;
10938                 defaultState = {};
10939                 for (key in state) {
10940                     if (state[key] instanceof Array)
10941                         defaultState[key] = state[key].slice(0);
10942                     else
10943                         defaultState[key] = state[key];
10944                 }
10945             }
10946
10947             // Display No Data message if there's nothing to show.
10948             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
10949                 var noDataText = container.selectAll('.nv-noData').data([noData]);
10950
10951                 noDataText.enter().append('text')
10952                     .attr('class', 'nvd3 nv-noData')
10953                     .attr('dy', '-.7em')
10954                     .style('text-anchor', 'middle');
10955
10956                 noDataText
10957                     .attr('x', margin.left + availableWidth / 2)
10958                     .attr('y', margin.top + availableHeight / 2)
10959                     .text(function(d) { return d });
10960
10961                 return chart;
10962             } else {
10963                 container.selectAll('.nv-noData').remove();
10964             }
10965
10966             // Setup Scales
10967             x = stacked.xScale();
10968             y = stacked.yScale();
10969
10970             // Setup containers and skeleton of chart
10971             var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
10972             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
10973             var g = wrap.select('g');
10974
10975             gEnter.append("rect").style("opacity",0);
10976             gEnter.append('g').attr('class', 'nv-x nv-axis');
10977             gEnter.append('g').attr('class', 'nv-y nv-axis');
10978             gEnter.append('g').attr('class', 'nv-stackedWrap');
10979             gEnter.append('g').attr('class', 'nv-legendWrap');
10980             gEnter.append('g').attr('class', 'nv-controlsWrap');
10981             gEnter.append('g').attr('class', 'nv-interactive');
10982
10983             g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
10984
10985             // Legend
10986             if (showLegend) {
10987                 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
10988
10989                 legend.width(legendWidth);
10990                 g.select('.nv-legendWrap').datum(data).call(legend);
10991
10992                 if ( margin.top != legend.height()) {
10993                     margin.top = legend.height();
10994                     availableHeight = (height || parseInt(container.style('height')) || 400)
10995                         - margin.top - margin.bottom;
10996                 }
10997
10998                 g.select('.nv-legendWrap')
10999                     .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
11000             }
11001
11002             // Controls
11003             if (showControls) {
11004                 var controlsData = [
11005                     {
11006                         key: controlLabels.stacked || 'Stacked',
11007                         metaKey: 'Stacked',
11008                         disabled: stacked.style() != 'stack',
11009                         style: 'stack'
11010                     },
11011                     {
11012                         key: controlLabels.stream || 'Stream',
11013                         metaKey: 'Stream',
11014                         disabled: stacked.style() != 'stream',
11015                         style: 'stream'
11016                     },
11017                     {
11018                         key: controlLabels.expanded || 'Expanded',
11019                         metaKey: 'Expanded',
11020                         disabled: stacked.style() != 'expand',
11021                         style: 'expand'
11022                     },
11023                     {
11024                         key: controlLabels.stack_percent || 'Stack %',
11025                         metaKey: 'Stack_Percent',
11026                         disabled: stacked.style() != 'stack_percent',
11027                         style: 'stack_percent'
11028                     }
11029                 ];
11030
11031                 controlWidth = (cData.length/3) * 260;
11032                 controlsData = controlsData.filter(function(d) {
11033                     return cData.indexOf(d.metaKey) !== -1;
11034                 });
11035
11036                 controls
11037                     .width( controlWidth )
11038                     .color(['#444', '#444', '#444']);
11039
11040                 g.select('.nv-controlsWrap')
11041                     .datum(controlsData)
11042                     .call(controls);
11043
11044                 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
11045                     margin.top = Math.max(controls.height(), legend.height());
11046                     availableHeight = (height || parseInt(container.style('height')) || 400)
11047                         - margin.top - margin.bottom;
11048                 }
11049
11050                 g.select('.nv-controlsWrap')
11051                     .attr('transform', 'translate(0,' + (-margin.top) +')');
11052             }
11053
11054             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11055
11056             if (rightAlignYAxis) {
11057                 g.select(".nv-y.nv-axis")
11058                     .attr("transform", "translate(" + availableWidth + ",0)");
11059             }
11060
11061             //Set up interactive layer
11062             if (useInteractiveGuideline) {
11063                 interactiveLayer
11064                     .width(availableWidth)
11065                     .height(availableHeight)
11066                     .margin({left: margin.left, top: margin.top})
11067                     .svgContainer(container)
11068                     .xScale(x);
11069                 wrap.select(".nv-interactive").call(interactiveLayer);
11070             }
11071
11072             stacked
11073                 .width(availableWidth)
11074                 .height(availableHeight);
11075
11076             var stackedWrap = g.select('.nv-stackedWrap')
11077                 .datum(data);
11078
11079             stackedWrap.transition().call(stacked);
11080
11081             // Setup Axes
11082             if (showXAxis) {
11083                 xAxis.scale(x)
11084                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
11085                     .tickSize( -availableHeight, 0);
11086
11087                 g.select('.nv-x.nv-axis')
11088                     .attr('transform', 'translate(0,' + availableHeight + ')');
11089
11090                 g.select('.nv-x.nv-axis')
11091                     .transition().duration(0)
11092                     .call(xAxis);
11093             }
11094
11095             if (showYAxis) {
11096                 yAxis.scale(y)
11097                     .ticks(stacked.offset() == 'wiggle' ? 0 : nv.utils.calcTicksY(availableHeight/36, data) )
11098                     .tickSize(-availableWidth, 0)
11099                     .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
11100                         ? d3.format('%') : yAxisTickFormat);
11101
11102                 g.select('.nv-y.nv-axis')
11103                     .transition().duration(0)
11104                     .call(yAxis);
11105             }
11106
11107             //============================================================
11108             // Event Handling/Dispatching (in chart's scope)
11109             //------------------------------------------------------------
11110
11111             stacked.dispatch.on('areaClick.toggle', function(e) {
11112                 if (data.filter(function(d) { return !d.disabled }).length === 1)
11113                     data.forEach(function(d) {
11114                         d.disabled = false;
11115                     });
11116                 else
11117                     data.forEach(function(d,i) {
11118                         d.disabled = (i != e.seriesIndex);
11119                     });
11120
11121                 state.disabled = data.map(function(d) { return !!d.disabled });
11122                 dispatch.stateChange(state);
11123
11124                 chart.update();
11125             });
11126
11127             legend.dispatch.on('stateChange', function(newState) {
11128                 for (var key in newState)
11129                     state[key] = newState[key];
11130                 dispatch.stateChange(state);
11131                 chart.update();
11132             });
11133
11134             controls.dispatch.on('legendClick', function(d,i) {
11135                 if (!d.disabled) return;
11136
11137                 controlsData = controlsData.map(function(s) {
11138                     s.disabled = true;
11139                     return s;
11140                 });
11141                 d.disabled = false;
11142
11143                 stacked.style(d.style);
11144
11145
11146                 state.style = stacked.style();
11147                 dispatch.stateChange(state);
11148
11149                 chart.update();
11150             });
11151
11152             interactiveLayer.dispatch.on('elementMousemove', function(e) {
11153                 stacked.clearHighlights();
11154                 var singlePoint, pointIndex, pointXLocation, allData = [];
11155                 data
11156                     .filter(function(series, i) {
11157                         series.seriesIndex = i;
11158                         return !series.disabled;
11159                     })
11160                     .forEach(function(series,i) {
11161                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
11162                         stacked.highlightPoint(i, pointIndex, true);
11163                         var point = series.values[pointIndex];
11164                         if (typeof point === 'undefined') return;
11165                         if (typeof singlePoint === 'undefined') singlePoint = point;
11166                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
11167
11168                         //If we are in 'expand' mode, use the stacked percent value instead of raw value.
11169                         var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
11170                         allData.push({
11171                             key: series.key,
11172                             value: tooltipValue,
11173                             color: color(series,series.seriesIndex),
11174                             stackedValue: point.display
11175                         });
11176                     });
11177
11178                 allData.reverse();
11179
11180                 //Highlight the tooltip entry based on which stack the mouse is closest to.
11181                 if (allData.length > 2) {
11182                     var yValue = chart.yScale().invert(e.mouseY);
11183                     var yDistMax = Infinity, indexToHighlight = null;
11184                     allData.forEach(function(series,i) {
11185
11186                         //To handle situation where the stacked area chart is negative, we need to use absolute values
11187                         //when checking if the mouse Y value is within the stack area.
11188                         yValue = Math.abs(yValue);
11189                         var stackedY0 = Math.abs(series.stackedValue.y0);
11190                         var stackedY = Math.abs(series.stackedValue.y);
11191                         if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
11192                         {
11193                             indexToHighlight = i;
11194                             return;
11195                         }
11196                     });
11197                     if (indexToHighlight != null)
11198                         allData[indexToHighlight].highlight = true;
11199                 }
11200
11201                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
11202
11203                 //If we are in 'expand' mode, force the format to be a percentage.
11204                 var valueFormatter = (stacked.style() == 'expand') ?
11205                     function(d,i) {return d3.format(".1%")(d);} :
11206                     function(d,i) {return yAxis.tickFormat()(d); };
11207                 interactiveLayer.tooltip
11208                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
11209                     .chartContainer(that.parentNode)
11210                     .enabled(tooltips)
11211                     .valueFormatter(valueFormatter)
11212                     .data(
11213                     {
11214                         value: xValue,
11215                         series: allData
11216                     }
11217                 )();
11218
11219                 interactiveLayer.renderGuideLine(pointXLocation);
11220
11221             });
11222
11223             interactiveLayer.dispatch.on("elementMouseout",function(e) {
11224                 dispatch.tooltipHide();
11225                 stacked.clearHighlights();
11226             });
11227
11228
11229             dispatch.on('tooltipShow', function(e) {
11230                 if (tooltips) showTooltip(e, that.parentNode);
11231             });
11232
11233             // Update chart from a state object passed to event handler
11234             dispatch.on('changeState', function(e) {
11235
11236                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
11237                     data.forEach(function(series,i) {
11238                         series.disabled = e.disabled[i];
11239                     });
11240
11241                     state.disabled = e.disabled;
11242                 }
11243
11244                 if (typeof e.style !== 'undefined') {
11245                     stacked.style(e.style);
11246                     style = e.style;
11247                 }
11248
11249                 chart.update();
11250             });
11251
11252         });
11253
11254         renderWatch.renderEnd('stacked Area chart immediate');
11255         return chart;
11256     }
11257
11258     //============================================================
11259     // Event Handling/Dispatching (out of chart's scope)
11260     //------------------------------------------------------------
11261
11262     stacked.dispatch.on('tooltipShow', function(e) {
11263         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11264         dispatch.tooltipShow(e);
11265     });
11266
11267     stacked.dispatch.on('tooltipHide', function(e) {
11268         dispatch.tooltipHide(e);
11269     });
11270
11271     dispatch.on('tooltipHide', function() {
11272         if (tooltips) nv.tooltip.cleanup();
11273     });
11274
11275     //============================================================
11276     // Expose Public Variables
11277     //------------------------------------------------------------
11278
11279     // expose chart's sub-components
11280     chart.dispatch = dispatch;
11281     chart.stacked = stacked;
11282     chart.legend = legend;
11283     chart.controls = controls;
11284     chart.xAxis = xAxis;
11285     chart.yAxis = yAxis;
11286     chart.interactiveLayer = interactiveLayer;
11287
11288     yAxis.setTickFormat = yAxis.tickFormat;
11289
11290     chart.dispatch = dispatch;
11291     chart.options = nv.utils.optionsFunc.bind(chart);
11292
11293     chart._options = Object.create({}, {
11294         // simple options, just get/set the necessary values
11295         width:      {get: function(){return width;}, set: function(_){width=_;}},
11296         height:     {get: function(){return height;}, set: function(_){height=_;}},
11297         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11298         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
11299         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
11300         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
11301         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
11302         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
11303         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
11304         showControls:    {get: function(){return showControls;}, set: function(_){showControls=_;}},
11305         controlLabels:    {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
11306         yAxisTickFormat:    {get: function(){return yAxisTickFormat;}, set: function(_){yAxisTickFormat=_;}},
11307
11308         // options that require extra logic in the setter
11309         margin: {get: function(){return margin;}, set: function(_){
11310             margin.top    = _.top    !== undefined ? _.top    : margin.top;
11311             margin.right  = _.right  !== undefined ? _.right  : margin.right;
11312             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11313             margin.left   = _.left   !== undefined ? _.left   : margin.left;
11314         }},
11315         duration: {get: function(){return duration;}, set: function(_){
11316             duration = _;
11317             renderWatch.reset(duration);
11318             stacked.duration(duration);
11319             xAxis.duration(duration);
11320             yAxis.duration(duration);
11321         }},
11322         color:  {get: function(){return color;}, set: function(_){
11323             color = nv.utils.getColor(_);
11324             legend.color(color);
11325             stacked.color(color);
11326         }},
11327         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
11328             rightAlignYAxis = _;
11329             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
11330         }},
11331         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
11332             useInteractiveGuideline = !!_;
11333             if (_) {
11334                 chart.interactive(false);
11335                 chart.useVoronoi(false);
11336             }
11337         }}
11338     });
11339
11340     nv.utils.inheritOptions(chart, stacked);
11341     nv.utils.initOptions(chart);
11342
11343     return chart;
11344 };
11345
11346 nv.version = "1.7.0";
11347 })();