Built motion from commit b598105.|2.0.7
[motion2.git] / public / bower_components / nvd3 / build / nv.d3.js
1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
2 (function(){
3
4 // set up main nv object
5 var nv = {};
6
7 // the major global objects under the nv namespace
8 nv.dev = false; //set false when in production
9 nv.tooltip = nv.tooltip || {}; // For the tooltip system
10 nv.utils = nv.utils || {}; // Utility subsystem
11 nv.models = nv.models || {}; //stores all the possible models/components
12 nv.charts = {}; //stores all the ready to use charts
13 nv.logs = {}; //stores some statistics and potential error messages
14 nv.dom = {}; //DOM manipulation functions
15
16 nv.dispatch = d3.dispatch('render_start', 'render_end');
17
18 // Function bind polyfill
19 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
20 // https://github.com/ariya/phantomjs/issues/10522
21 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
22 // phantomJS is used for running the test suite
23 if (!Function.prototype.bind) {
24     Function.prototype.bind = function (oThis) {
25         if (typeof this !== "function") {
26             // closest thing possible to the ECMAScript 5 internal IsCallable function
27             throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
28         }
29
30         var aArgs = Array.prototype.slice.call(arguments, 1),
31             fToBind = this,
32             fNOP = function () {},
33             fBound = function () {
34                 return fToBind.apply(this instanceof fNOP && oThis
35                         ? this
36                         : oThis,
37                     aArgs.concat(Array.prototype.slice.call(arguments)));
38             };
39
40         fNOP.prototype = this.prototype;
41         fBound.prototype = new fNOP();
42         return fBound;
43     };
44 }
45
46 //  Development render timers - disabled if dev = false
47 if (nv.dev) {
48     nv.dispatch.on('render_start', function(e) {
49         nv.logs.startTime = +new Date();
50     });
51
52     nv.dispatch.on('render_end', function(e) {
53         nv.logs.endTime = +new Date();
54         nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
55         nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
56     });
57 }
58
59 // Logs all arguments, and returns the last so you can test things in place
60 // Note: in IE8 console.log is an object not a function, and if modernizr is used
61 // then calling Function.prototype.bind with with anything other than a function
62 // causes a TypeError to be thrown.
63 nv.log = function() {
64     if (nv.dev && window.console && console.log && console.log.apply)
65         console.log.apply(console, arguments);
66     else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
67         var log = Function.prototype.bind.call(console.log, console);
68         log.apply(console, arguments);
69     }
70     return arguments[arguments.length - 1];
71 };
72
73 // print console warning, should be used by deprecated functions
74 nv.deprecated = function(name, info) {
75     if (console && console.warn) {
76         console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
77     }
78 };
79
80 // The nv.render function is used to queue up chart rendering
81 // in non-blocking async functions.
82 // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
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     var renderLoop = 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         }
97
98         nv.render.queue.splice(0, i);
99
100         if (nv.render.queue.length) {
101             setTimeout(renderLoop);
102         }
103         else {
104             nv.dispatch.render_end();
105             nv.render.active = false;
106         }
107     };
108
109     setTimeout(renderLoop);
110 };
111
112 nv.render.active = false;
113 nv.render.queue = [];
114
115 /*
116 Adds a chart to the async rendering queue. This method can take arguments in two forms:
117 nv.addGraph({
118     generate: <Function>
119     callback: <Function>
120 })
121
122 or
123
124 nv.addGraph(<generate Function>, <callback Function>)
125
126 The generate function should contain code that creates the NVD3 model, sets options
127 on it, adds data to an SVG element, and invokes the chart model. The generate function
128 should return the chart model.  See examples/lineChart.html for a usage example.
129
130 The callback function is optional, and it is called when the generate function completes.
131 */
132 nv.addGraph = function(obj) {
133     if (typeof arguments[0] === typeof(Function)) {
134         obj = {generate: arguments[0], callback: arguments[1]};
135     }
136
137     nv.render.queue.push(obj);
138
139     if (!nv.render.active) {
140         nv.render();
141     }
142 };
143
144 // Node/CommonJS exports
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
146   module.exports = nv;
147 }
148
149 if (typeof(window) !== 'undefined') {
150   window.nv = nv;
151 }
152 /* Facade for queueing DOM write operations\r
153  * with Fastdom (https://github.com/wilsonpage/fastdom)\r
154  * if available.\r
155  * This could easily be extended to support alternate\r
156  * implementations in the future.\r
157  */\r
158 nv.dom.write = function(callback) {\r
159         if (window.fastdom !== undefined) {\r
160                 return fastdom.write(callback);\r
161         }\r
162         return callback();\r
163 };\r
164 \r
165 /* Facade for queueing DOM read operations\r
166  * with Fastdom (https://github.com/wilsonpage/fastdom)\r
167  * if available.\r
168  * This could easily be extended to support alternate\r
169  * implementations in the future.\r
170  */\r
171 nv.dom.read = function(callback) {\r
172         if (window.fastdom !== undefined) {\r
173                 return fastdom.read(callback);\r
174         }\r
175         return callback();\r
176 };/* Utility class to handle creation of an interactive layer.
177  This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
178  containing the X-coordinate. It can also render a vertical line where the mouse is located.
179
180  dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
181  the rectangle. The dispatch is given one object which contains the mouseX/Y location.
182  It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
183  */
184 nv.interactiveGuideline = function() {
185     "use strict";
186
187     var tooltip = nv.models.tooltip();
188     tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
189
190     //Public settings
191     var width = null;
192     var height = null;
193
194     //Please pass in the bounding chart's top and left margins
195     //This is important for calculating the correct mouseX/Y positions.
196     var margin = {left: 0, top: 0}
197         , xScale = d3.scale.linear()
198         , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
199         , showGuideLine = true;
200     //Must pass in the bounding chart's <svg> container.
201     //The mousemove event is attached to this container.
202     var svgContainer = null;
203
204     // check if IE by looking for activeX
205     var isMSIE = "ActiveXObject" in window;
206
207
208     function layer(selection) {
209         selection.each(function(data) {
210             var container = d3.select(this);
211             var availableWidth = (width || 960), availableHeight = (height || 400);
212             var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
213                 .data([data]);
214             var wrapEnter = wrap.enter()
215                 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
216             wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
217
218             if (!svgContainer) {
219                 return;
220             }
221
222             function mouseHandler() {
223                 var d3mouse = d3.mouse(this);
224                 var mouseX = d3mouse[0];
225                 var mouseY = d3mouse[1];
226                 var subtractMargin = true;
227                 var mouseOutAnyReason = false;
228                 if (isMSIE) {
229                     /*
230                      D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
231                      d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
232                      over a rect in IE 10.
233                      However, d3.event.offsetX/Y also returns the mouse coordinates
234                      relative to the triggering <rect>. So we use offsetX/Y on IE.
235                      */
236                     mouseX = d3.event.offsetX;
237                     mouseY = d3.event.offsetY;
238
239                     /*
240                      On IE, if you attach a mouse event listener to the <svg> container,
241                      it will actually trigger it for all the child elements (like <path>, <circle>, etc).
242                      When this happens on IE, the offsetX/Y is set to where ever the child element
243                      is located.
244                      As a result, we do NOT need to subtract margins to figure out the mouse X/Y
245                      position under this scenario. Removing the line below *will* cause
246                      the interactive layer to not work right on IE.
247                      */
248                     if(d3.event.target.tagName !== "svg") {
249                         subtractMargin = false;
250                     }
251
252                     if (d3.event.target.className.baseVal.match("nv-legend")) {
253                         mouseOutAnyReason = true;
254                     }
255
256                 }
257
258                 if(subtractMargin) {
259                     mouseX -= margin.left;
260                     mouseY -= margin.top;
261                 }
262
263                 /* If mouseX/Y is outside of the chart's bounds,
264                  trigger a mouseOut event.
265                  */
266                 if (mouseX < 0 || mouseY < 0
267                     || mouseX > availableWidth || mouseY > availableHeight
268                     || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
269                     || mouseOutAnyReason
270                     ) {
271
272                     if (isMSIE) {
273                         if (d3.event.relatedTarget
274                             && d3.event.relatedTarget.ownerSVGElement === undefined
275                             && (d3.event.relatedTarget.className === undefined
276                                 || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
277
278                             return;
279                         }
280                     }
281                     dispatch.elementMouseout({
282                         mouseX: mouseX,
283                         mouseY: mouseY
284                     });
285                     layer.renderGuideLine(null); //hide the guideline
286                     tooltip.hidden(true);
287                     return;
288                 } else {
289                     tooltip.hidden(false);
290                 }
291
292                 var pointXValue = xScale.invert(mouseX);
293                 dispatch.elementMousemove({
294                     mouseX: mouseX,
295                     mouseY: mouseY,
296                     pointXValue: pointXValue
297                 });
298
299                 //If user double clicks the layer, fire a elementDblclick
300                 if (d3.event.type === "dblclick") {
301                     dispatch.elementDblclick({
302                         mouseX: mouseX,
303                         mouseY: mouseY,
304                         pointXValue: pointXValue
305                     });
306                 }
307
308                 // if user single clicks the layer, fire elementClick
309                 if (d3.event.type === 'click') {
310                     dispatch.elementClick({
311                         mouseX: mouseX,
312                         mouseY: mouseY,
313                         pointXValue: pointXValue
314                     });
315                 }
316             }
317
318             svgContainer
319                 .on("touchmove",mouseHandler)
320                 .on("mousemove",mouseHandler, true)
321                 .on("mouseout" ,mouseHandler,true)
322                 .on("dblclick" ,mouseHandler)
323                 .on("click", mouseHandler)
324             ;
325
326             layer.guideLine = null;
327             //Draws a vertical guideline at the given X postion.
328             layer.renderGuideLine = function(x) {
329                 if (!showGuideLine) return;
330                 if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
331                 nv.dom.write(function() {
332                     var line = wrap.select(".nv-interactiveGuideLine")
333                         .selectAll("line")
334                         .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
335                     line.enter()
336                         .append("line")
337                         .attr("class", "nv-guideline")
338                         .attr("x1", function(d) { return d;})
339                         .attr("x2", function(d) { return d;})
340                         .attr("y1", availableHeight)
341                         .attr("y2",0);
342                     line.exit().remove();
343                 });
344             }
345         });
346     }
347
348     layer.dispatch = dispatch;
349     layer.tooltip = tooltip;
350
351     layer.margin = function(_) {
352         if (!arguments.length) return margin;
353         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
354         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
355         return layer;
356     };
357
358     layer.width = function(_) {
359         if (!arguments.length) return width;
360         width = _;
361         return layer;
362     };
363
364     layer.height = function(_) {
365         if (!arguments.length) return height;
366         height = _;
367         return layer;
368     };
369
370     layer.xScale = function(_) {
371         if (!arguments.length) return xScale;
372         xScale = _;
373         return layer;
374     };
375
376     layer.showGuideLine = function(_) {
377         if (!arguments.length) return showGuideLine;
378         showGuideLine = _;
379         return layer;
380     };
381
382     layer.svgContainer = function(_) {
383         if (!arguments.length) return svgContainer;
384         svgContainer = _;
385         return layer;
386     };
387
388     return layer;
389 };
390
391 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
392  This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
393
394  For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
395  Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
396  because 28 is closer to 30 than 10.
397
398  Unit tests can be found in: interactiveBisectTest.html
399
400  Has the following known issues:
401  * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
402  * Won't work if there are duplicate x coordinate values.
403  */
404 nv.interactiveBisect = function (values, searchVal, xAccessor) {
405     "use strict";
406     if (! (values instanceof Array)) {
407         return null;
408     }
409     var _xAccessor;
410     if (typeof xAccessor !== 'function') {
411         _xAccessor = function(d) {
412             return d.x;
413         }
414     } else {
415         _xAccessor = xAccessor;
416     }
417     var _cmp = function(d, v) {
418         // Accessors are no longer passed the index of the element along with
419         // the element itself when invoked by d3.bisector.
420         //
421         // Starting at D3 v3.4.4, d3.bisector() started inspecting the
422         // function passed to determine if it should consider it an accessor
423         // or a comparator. This meant that accessors that take two arguments
424         // (expecting an index as the second parameter) are treated as
425         // comparators where the second argument is the search value against
426         // which the first argument is compared.
427         return _xAccessor(d) - v;
428     };
429
430     var bisect = d3.bisector(_cmp).left;
431     var index = d3.max([0, bisect(values,searchVal) - 1]);
432     var currentValue = _xAccessor(values[index]);
433
434     if (typeof currentValue === 'undefined') {
435         currentValue = index;
436     }
437
438     if (currentValue === searchVal) {
439         return index; //found exact match
440     }
441
442     var nextIndex = d3.min([index+1, values.length - 1]);
443     var nextValue = _xAccessor(values[nextIndex]);
444
445     if (typeof nextValue === 'undefined') {
446         nextValue = nextIndex;
447     }
448
449     if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
450         return index;
451     } else {
452         return nextIndex
453     }
454 };
455
456 /*
457  Returns the index in the array "values" that is closest to searchVal.
458  Only returns an index if searchVal is within some "threshold".
459  Otherwise, returns null.
460  */
461 nv.nearestValueIndex = function (values, searchVal, threshold) {
462     "use strict";
463     var yDistMax = Infinity, indexToHighlight = null;
464     values.forEach(function(d,i) {
465         var delta = Math.abs(searchVal - d);
466         if ( d != null && delta <= yDistMax && delta < threshold) {
467             yDistMax = delta;
468             indexToHighlight = i;
469         }
470     });
471     return indexToHighlight;
472 };
473 /* Tooltip rendering model for nvd3 charts.
474  window.nv.models.tooltip is the updated,new way to render tooltips.
475
476  window.nv.tooltip.show is the old tooltip code.
477  window.nv.tooltip.* also has various helper methods.
478  */
479 (function() {
480     "use strict";
481
482     /* Model which can be instantiated to handle tooltip rendering.
483      Example usage:
484      var tip = nv.models.tooltip().gravity('w').distance(23)
485      .data(myDataObject);
486
487      tip();    //just invoke the returned function to render tooltip.
488      */
489     nv.models.tooltip = function() {
490
491         /*
492         Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
493         Example Format of data:
494         {
495             key: "Date",
496             value: "August 2009",
497             series: [
498                 {key: "Series 1", value: "Value 1", color: "#000"},
499                 {key: "Series 2", value: "Value 2", color: "#00f"}
500             ]
501         }
502         */
503         var data = null;
504         var gravity = 'w'   //Can be 'n','s','e','w'. Determines how tooltip is positioned.
505             ,   distance = 25   //Distance to offset tooltip from the mouse location.
506             ,   snapDistance = 0   //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
507             ,   fixedTop = null //If not null, this fixes the top position of the tooltip.
508             ,   classes = null  //Attaches additional CSS classes to the tooltip DIV that is created.
509             ,   chartContainer = null   //Parent dom element of the SVG that holds the chart.
510             ,   hidden = true  // start off hidden, toggle with hide/show functions below
511             ,   hideDelay = 400  // delay before the tooltip hides after calling hide()
512             ,   tooltip = null // d3 select of tooltipElem below
513             ,   tooltipElem = null  //actual DOM element representing the tooltip.
514             ,   position = {left: null, top: null}   //Relative position of the tooltip inside chartContainer.
515             ,   offset = {left: 0, top: 0}   //Offset of tooltip against the pointer
516             ,   enabled = true  //True -> tooltips are rendered. False -> don't render tooltips.
517             ,   duration = 100 // duration for tooltip movement
518             ,   headerEnabled = true
519         ;
520
521         // set to true by interactive layer to adjust tooltip positions
522         // eventually we should probably fix interactive layer to get the position better.
523         // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken
524         var isInteractiveLayer = false;
525
526         //Generates a unique id when you create a new tooltip() object
527         var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
528
529         //CSS class to specify whether element should not have mouse events.
530         var  nvPointerEventsClass = "nv-pointer-events-none";
531
532         //Format function for the tooltip values column
533         var valueFormatter = function(d,i) {
534             return d;
535         };
536
537         //Format function for the tooltip header value.
538         var headerFormatter = function(d) {
539             return d;
540         };
541
542         var keyFormatter = function(d, i) {
543             return d;
544         };
545
546         //By default, the tooltip model renders a beautiful table inside a DIV.
547         //You can override this function if a custom tooltip is desired.
548         var contentGenerator = function(d) {
549             if (d === null) {
550                 return '';
551             }
552
553             var table = d3.select(document.createElement("table"));
554             if (headerEnabled) {
555                 var theadEnter = table.selectAll("thead")
556                     .data([d])
557                     .enter().append("thead");
558
559                 theadEnter.append("tr")
560                     .append("td")
561                     .attr("colspan", 3)
562                     .append("strong")
563                     .classed("x-value", true)
564                     .html(headerFormatter(d.value));
565             }
566
567             var tbodyEnter = table.selectAll("tbody")
568                 .data([d])
569                 .enter().append("tbody");
570
571             var trowEnter = tbodyEnter.selectAll("tr")
572                     .data(function(p) { return p.series})
573                     .enter()
574                     .append("tr")
575                     .classed("highlight", function(p) { return p.highlight});
576
577             trowEnter.append("td")
578                 .classed("legend-color-guide",true)
579                 .append("div")
580                 .style("background-color", function(p) { return p.color});
581
582             trowEnter.append("td")
583                 .classed("key",true)
584                 .html(function(p, i) {return keyFormatter(p.key, i)});
585
586             trowEnter.append("td")
587                 .classed("value",true)
588                 .html(function(p, i) { return valueFormatter(p.value, i) });
589
590
591             trowEnter.selectAll("td").each(function(p) {
592                 if (p.highlight) {
593                     var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
594                     var opacity = 0.6;
595                     d3.select(this)
596                         .style("border-bottom-color", opacityScale(opacity))
597                         .style("border-top-color", opacityScale(opacity))
598                     ;
599                 }
600             });
601
602             var html = table.node().outerHTML;
603             if (d.footer !== undefined)
604                 html += "<div class='footer'>" + d.footer + "</div>";
605             return html;
606
607         };
608
609         var dataSeriesExists = function(d) {
610             if (d && d.series) {
611                 if (d.series instanceof Array) {
612                     return !!d.series.length;
613                 }
614                 // if object, it's okay just convert to array of the object
615                 if (d.series instanceof Object) {
616                     d.series = [d.series];
617                     return true;
618                 }
619             }
620             return false;
621         };
622
623         var calcTooltipPosition = function(pos) {
624             if (!tooltipElem) return;
625
626             nv.dom.read(function() {
627                 var height = parseInt(tooltipElem.offsetHeight, 10),
628                     width = parseInt(tooltipElem.offsetWidth, 10),
629                     windowWidth = nv.utils.windowSize().width,
630                     windowHeight = nv.utils.windowSize().height,
631                     scrollTop = window.pageYOffset,
632                     scrollLeft = window.pageXOffset,
633                     left, top;
634
635                 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
636                 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
637
638
639                 //Helper functions to find the total offsets of a given DOM element.
640                 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
641                 var tooltipTop = function ( Elem ) {
642                     var offsetTop = top;
643                     do {
644                         if( !isNaN( Elem.offsetTop ) ) {
645                             offsetTop += (Elem.offsetTop);
646                         }
647                         Elem = Elem.offsetParent;
648                     } while( Elem );
649                     return offsetTop;
650                 };
651                 var tooltipLeft = function ( Elem ) {
652                     var offsetLeft = left;
653                     do {
654                         if( !isNaN( Elem.offsetLeft ) ) {
655                             offsetLeft += (Elem.offsetLeft);
656                         }
657                         Elem = Elem.offsetParent;
658                     } while( Elem );
659                     return offsetLeft;
660                 };
661
662                 // calculate position based on gravity
663                 var tLeft, tTop;
664                 switch (gravity) {
665                     case 'e':
666                         left = pos[0] - width - distance;
667                         top = pos[1] - (height / 2);
668                         tLeft = tooltipLeft(tooltipElem);
669                         tTop = tooltipTop(tooltipElem);
670                         if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left;
671                         if (tTop < scrollTop) top = scrollTop - tTop + top;
672                         if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
673                         break;
674                     case 'w':
675                         left = pos[0] + distance;
676                         top = pos[1] - (height / 2);
677                         tLeft = tooltipLeft(tooltipElem);
678                         tTop = tooltipTop(tooltipElem);
679                         if (tLeft + width > windowWidth) left = pos[0] - width - distance;
680                         if (tTop < scrollTop) top = scrollTop + 5;
681                         if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
682                         break;
683                     case 'n':
684                         left = pos[0] - (width / 2) - 5;
685                         top = pos[1] + distance;
686                         tLeft = tooltipLeft(tooltipElem);
687                         tTop = tooltipTop(tooltipElem);
688                         if (tLeft < scrollLeft) left = scrollLeft + 5;
689                         if (tLeft + width > windowWidth) left = left - width/2 + 5;
690                         if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
691                         break;
692                     case 's':
693                         left = pos[0] - (width / 2);
694                         top = pos[1] - height - distance;
695                         tLeft = tooltipLeft(tooltipElem);
696                         tTop = tooltipTop(tooltipElem);
697                         if (tLeft < scrollLeft) left = scrollLeft + 5;
698                         if (tLeft + width > windowWidth) left = left - width/2 + 5;
699                         if (scrollTop > tTop) top = scrollTop;
700                         break;
701                     case 'none':
702                         left = pos[0];
703                         top = pos[1] - distance;
704                         tLeft = tooltipLeft(tooltipElem);
705                         tTop = tooltipTop(tooltipElem);
706                         break;
707                 }
708                 
709                 // adjust tooltip offsets
710                 left -= offset.left;
711                 top -= offset.top;
712
713                 // using tooltip.style('transform') returns values un-usable for tween
714                 var box = tooltipElem.getBoundingClientRect();
715                 var scrollTop  = window.pageYOffset || document.documentElement.scrollTop;
716                 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
717                 var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)';
718                 var new_translate = 'translate(' + left + 'px, ' + top + 'px)';
719                 var translateInterpolator = d3.interpolateString(old_translate, new_translate);
720
721                 var is_hidden = tooltip.style('opacity') < 0.1;
722
723                 // delay hiding a bit to avoid flickering
724                 if (hidden) {
725                     tooltip
726                         .transition()
727                         .delay(hideDelay)
728                         .duration(0)
729                         .style('opacity', 0);
730                 } else {
731                     tooltip
732                         .interrupt() // cancel running transitions
733                         .transition()
734                         .duration(is_hidden ? 0 : duration)
735                         // using tween since some versions of d3 can't auto-tween a translate on a div
736                         .styleTween('transform', function (d) {
737                             return translateInterpolator;
738                         }, 'important')
739                         // Safari has its own `-webkit-transform` and does not support `transform` 
740                         // transform tooltip without transition only in Safari
741                         .style('-webkit-transform', new_translate)
742                         .style('opacity', 1);
743                 }
744
745
746
747             });
748         };
749
750         //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
751         function convertViewBoxRatio() {
752             if (chartContainer) {
753                 var svg = d3.select(chartContainer);
754                 if (svg.node().tagName !== "svg") {
755                     svg = svg.select("svg");
756                 }
757                 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
758                 if (viewBox) {
759                     viewBox = viewBox.split(' ');
760                     var ratio = parseInt(svg.style('width'), 10) / viewBox[2];
761
762                     position.left = position.left * ratio;
763                     position.top  = position.top * ratio;
764                 }
765             }
766         }
767
768         //Creates new tooltip container, or uses existing one on DOM.
769         function initTooltip() {
770             if (!tooltip) {
771                 var body;
772                 if (chartContainer) {
773                     body = chartContainer;
774                 } else {
775                     body = document.body;
776                 }
777                 //Create new tooltip div if it doesn't exist on DOM.
778                 tooltip = d3.select(body).append("div")
779                     .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
780                     .attr("id", id);
781                 tooltip.style("top", 0).style("left", 0);
782                 tooltip.style('opacity', 0);
783                 tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true);
784                 tooltip.classed(nvPointerEventsClass, true);
785                 tooltipElem = tooltip.node();
786             }
787         }
788
789         //Draw the tooltip onto the DOM.
790         function nvtooltip() {
791             if (!enabled) return;
792             if (!dataSeriesExists(data)) return;
793
794             convertViewBoxRatio();
795
796             var left = position.left;
797             var top = (fixedTop !== null) ? fixedTop : position.top;
798
799             nv.dom.write(function () {
800                 initTooltip();
801                 // generate data and set it into tooltip
802                 // Bonus - If you override contentGenerator and return falsey you can use something like
803                 //         React or Knockout to bind the data for your tooltip
804                 var newContent = contentGenerator(data);
805                 if (newContent) {
806                     tooltipElem.innerHTML = newContent;
807                 }
808
809                 if (chartContainer && isInteractiveLayer) {
810                     nv.dom.read(function() {
811                         var svgComp = chartContainer.getElementsByTagName("svg")[0];
812                         var svgOffset = {left:0,top:0};
813                         if (svgComp) {
814                             var svgBound = svgComp.getBoundingClientRect();
815                             var chartBound = chartContainer.getBoundingClientRect();
816                             var svgBoundTop = svgBound.top;
817
818                             //Defensive code. Sometimes, svgBoundTop can be a really negative
819                             //  number, like -134254. That's a bug.
820                             //  If such a number is found, use zero instead. FireFox bug only
821                             if (svgBoundTop < 0) {
822                                 var containerBound = chartContainer.getBoundingClientRect();
823                                 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
824                             }
825                             svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
826                             svgOffset.left = Math.abs(svgBound.left - chartBound.left);
827                         }
828                         //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
829                         //You need to also add any offset between the <svg> element and its containing <div>
830                         //Finally, add any offset of the containing <div> on the whole page.
831                         left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
832                         top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
833
834                         if (snapDistance && snapDistance > 0) {
835                             top = Math.floor(top/snapDistance) * snapDistance;
836                         }
837                         calcTooltipPosition([left,top]);
838                     });
839                 } else {
840                     calcTooltipPosition([left,top]);
841                 }
842             });
843
844             return nvtooltip;
845         }
846
847         nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
848         nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
849
850         nvtooltip._options = Object.create({}, {
851             // simple read/write options
852             duration: {get: function(){return duration;}, set: function(_){duration=_;}},
853             gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
854             distance: {get: function(){return distance;}, set: function(_){distance=_;}},
855             snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
856             classes: {get: function(){return classes;}, set: function(_){classes=_;}},
857             chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}},
858             fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}},
859             enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
860             hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
861             contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
862             valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
863             headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
864             keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
865             headerEnabled:   {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
866
867             // internal use only, set by interactive layer to adjust position.
868             _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}},
869
870             // options with extra logic
871             position: {get: function(){return position;}, set: function(_){
872                 position.left = _.left !== undefined ? _.left : position.left;
873                 position.top  = _.top  !== undefined ? _.top  : position.top;
874             }},
875             offset: {get: function(){return offset;}, set: function(_){
876                 offset.left = _.left !== undefined ? _.left : offset.left;
877                 offset.top  = _.top  !== undefined ? _.top  : offset.top;
878             }},
879             hidden: {get: function(){return hidden;}, set: function(_){
880                 if (hidden != _) {
881                     hidden = !!_;
882                     nvtooltip();
883                 }
884             }},
885             data: {get: function(){return data;}, set: function(_){
886                 // if showing a single data point, adjust data format with that
887                 if (_.point) {
888                     _.value = _.point.x;
889                     _.series = _.series || {};
890                     _.series.value = _.point.y;
891                     _.series.color = _.point.color || _.series.color;
892                 }
893                 data = _;
894             }},
895
896             // read only properties
897             tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
898             id: {get: function(){return id;}, set: function(_){}}
899         });
900
901         nv.utils.initOptions(nvtooltip);
902         return nvtooltip;
903     };
904
905 })();
906
907
908 /*
909 Gets the browser window size
910
911 Returns object with height and width properties
912  */
913 nv.utils.windowSize = function() {
914     // Sane defaults
915     var size = {width: 640, height: 480};
916
917     // Most recent browsers use
918     if (window.innerWidth && window.innerHeight) {
919         size.width = window.innerWidth;
920         size.height = window.innerHeight;
921         return (size);
922     }
923
924     // IE can use depending on mode it is in
925     if (document.compatMode=='CSS1Compat' &&
926         document.documentElement &&
927         document.documentElement.offsetWidth ) {
928
929         size.width = document.documentElement.offsetWidth;
930         size.height = document.documentElement.offsetHeight;
931         return (size);
932     }
933
934     // Earlier IE uses Doc.body
935     if (document.body && document.body.offsetWidth) {
936         size.width = document.body.offsetWidth;
937         size.height = document.body.offsetHeight;
938         return (size);
939     }
940
941     return (size);
942 };
943
944 /*
945 Binds callback function to run when window is resized
946  */
947 nv.utils.windowResize = function(handler) {
948     if (window.addEventListener) {
949         window.addEventListener('resize', handler);
950     } else {
951         nv.log("ERROR: Failed to bind to window.resize with: ", handler);
952     }
953     // return object with clear function to remove the single added callback.
954     return {
955         callback: handler,
956         clear: function() {
957             window.removeEventListener('resize', handler);
958         }
959     }
960 };
961
962
963 /*
964 Backwards compatible way to implement more d3-like coloring of graphs.
965 Can take in nothing, an array, or a function/scale
966 To use a normal scale, get the range and pass that because we must be able
967 to take two arguments and use the index to keep backward compatibility
968 */
969 nv.utils.getColor = function(color) {
970     //if you pass in nothing, get default colors back
971     if (color === undefined) {
972         return nv.utils.defaultColor();
973
974     //if passed an array, turn it into a color scale
975     // use isArray, instanceof fails if d3 range is created in an iframe
976     } else if(Array.isArray(color)) {
977         var color_scale = d3.scale.ordinal().range(color);
978         return function(d, i) {
979             var key = i === undefined ? d : i;
980             return d.color || color_scale(key);
981         };
982
983     //if passed a function or scale, return it, or whatever it may be
984     //external libs, such as angularjs-nvd3-directives use this
985     } else {
986         //can't really help it if someone passes rubbish as color
987         return color;
988     }
989 };
990
991
992 /*
993 Default color chooser uses a color scale of 20 colors from D3
994  https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
995  */
996 nv.utils.defaultColor = function() {
997     // get range of the scale so we'll turn it into our own function.
998     return nv.utils.getColor(d3.scale.category20().range());
999 };
1000
1001
1002 /*
1003 Returns a color function that takes the result of 'getKey' for each series and
1004 looks for a corresponding color from the dictionary
1005 */
1006 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
1007     // use default series.key if getKey is undefined
1008     getKey = getKey || function(series) { return series.key };
1009     defaultColors = defaultColors || d3.scale.category20().range();
1010
1011     // start at end of default color list and walk back to index 0
1012     var defIndex = defaultColors.length;
1013
1014     return function(series, index) {
1015         var key = getKey(series);
1016         if (typeof dictionary[key] === 'function') {
1017             return dictionary[key]();
1018         } else if (dictionary[key] !== undefined) {
1019             return dictionary[key];
1020         } else {
1021             // no match in dictionary, use a default color
1022             if (!defIndex) {
1023                 // used all the default colors, start over
1024                 defIndex = defaultColors.length;
1025             }
1026             defIndex = defIndex - 1;
1027             return defaultColors[defIndex];
1028         }
1029     };
1030 };
1031
1032
1033 /*
1034 From the PJAX example on d3js.org, while this is not really directly needed
1035 it's a very cool method for doing pjax, I may expand upon it a little bit,
1036 open to suggestions on anything that may be useful
1037 */
1038 nv.utils.pjax = function(links, content) {
1039
1040     var load = function(href) {
1041         d3.html(href, function(fragment) {
1042             var target = d3.select(content).node();
1043             target.parentNode.replaceChild(
1044                 d3.select(fragment).select(content).node(),
1045                 target);
1046             nv.utils.pjax(links, content);
1047         });
1048     };
1049
1050     d3.selectAll(links).on("click", function() {
1051         history.pushState(this.href, this.textContent, this.href);
1052         load(this.href);
1053         d3.event.preventDefault();
1054     });
1055
1056     d3.select(window).on("popstate", function() {
1057         if (d3.event.state) {
1058             load(d3.event.state);
1059         }
1060     });
1061 };
1062
1063
1064 /*
1065 For when we want to approximate the width in pixels for an SVG:text element.
1066 Most common instance is when the element is in a display:none; container.
1067 Forumla is : text.length * font-size * constant_factor
1068 */
1069 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1070     if (typeof svgTextElem.style === 'function'
1071         && typeof svgTextElem.text === 'function') {
1072
1073         var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
1074         var textLength = svgTextElem.text().length;
1075         return textLength * fontSize * 0.5;
1076     }
1077     return 0;
1078 };
1079
1080
1081 /*
1082 Numbers that are undefined, null or NaN, convert them to zeros.
1083 */
1084 nv.utils.NaNtoZero = function(n) {
1085     if (typeof n !== 'number'
1086         || isNaN(n)
1087         || n === null
1088         || n === Infinity
1089         || n === -Infinity) {
1090
1091         return 0;
1092     }
1093     return n;
1094 };
1095
1096 /*
1097 Add a way to watch for d3 transition ends to d3
1098 */
1099 d3.selection.prototype.watchTransition = function(renderWatch){
1100     var args = [this].concat([].slice.call(arguments, 1));
1101     return renderWatch.transition.apply(renderWatch, args);
1102 };
1103
1104
1105 /*
1106 Helper object to watch when d3 has rendered something
1107 */
1108 nv.utils.renderWatch = function(dispatch, duration) {
1109     if (!(this instanceof nv.utils.renderWatch)) {
1110         return new nv.utils.renderWatch(dispatch, duration);
1111     }
1112
1113     var _duration = duration !== undefined ? duration : 250;
1114     var renderStack = [];
1115     var self = this;
1116
1117     this.models = function(models) {
1118         models = [].slice.call(arguments, 0);
1119         models.forEach(function(model){
1120             model.__rendered = false;
1121             (function(m){
1122                 m.dispatch.on('renderEnd', function(arg){
1123                     m.__rendered = true;
1124                     self.renderEnd('model');
1125                 });
1126             })(model);
1127
1128             if (renderStack.indexOf(model) < 0) {
1129                 renderStack.push(model);
1130             }
1131         });
1132     return this;
1133     };
1134
1135     this.reset = function(duration) {
1136         if (duration !== undefined) {
1137             _duration = duration;
1138         }
1139         renderStack = [];
1140     };
1141
1142     this.transition = function(selection, args, duration) {
1143         args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1144
1145         if (args.length > 1) {
1146             duration = args.pop();
1147         } else {
1148             duration = _duration !== undefined ? _duration : 250;
1149         }
1150         selection.__rendered = false;
1151
1152         if (renderStack.indexOf(selection) < 0) {
1153             renderStack.push(selection);
1154         }
1155
1156         if (duration === 0) {
1157             selection.__rendered = true;
1158             selection.delay = function() { return this; };
1159             selection.duration = function() { return this; };
1160             return selection;
1161         } else {
1162             if (selection.length === 0) {
1163                 selection.__rendered = true;
1164             } else if (selection.every( function(d){ return !d.length; } )) {
1165                 selection.__rendered = true;
1166             } else {
1167                 selection.__rendered = false;
1168             }
1169
1170             var n = 0;
1171             return selection
1172                 .transition()
1173                 .duration(duration)
1174                 .each(function(){ ++n; })
1175                 .each('end', function(d, i) {
1176                     if (--n === 0) {
1177                         selection.__rendered = true;
1178                         self.renderEnd.apply(this, args);
1179                     }
1180                 });
1181         }
1182     };
1183
1184     this.renderEnd = function() {
1185         if (renderStack.every( function(d){ return d.__rendered; } )) {
1186             renderStack.forEach( function(d){ d.__rendered = false; });
1187             dispatch.renderEnd.apply(this, arguments);
1188         }
1189     }
1190
1191 };
1192
1193
1194 /*
1195 Takes multiple objects and combines them into the first one (dst)
1196 example:  nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1197 gives:  {a: 2, b: 3, c: 4}
1198 */
1199 nv.utils.deepExtend = function(dst){
1200     var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1201     sources.forEach(function(source) {
1202         for (var key in source) {
1203             var isArray = dst[key] instanceof Array;
1204             var isObject = typeof dst[key] === 'object';
1205             var srcObj = typeof source[key] === 'object';
1206
1207             if (isObject && !isArray && srcObj) {
1208                 nv.utils.deepExtend(dst[key], source[key]);
1209             } else {
1210                 dst[key] = source[key];
1211             }
1212         }
1213     });
1214 };
1215
1216
1217 /*
1218 state utility object, used to track d3 states in the models
1219 */
1220 nv.utils.state = function(){
1221     if (!(this instanceof nv.utils.state)) {
1222         return new nv.utils.state();
1223     }
1224     var state = {};
1225     var _self = this;
1226     var _setState = function(){};
1227     var _getState = function(){ return {}; };
1228     var init = null;
1229     var changed = null;
1230
1231     this.dispatch = d3.dispatch('change', 'set');
1232
1233     this.dispatch.on('set', function(state){
1234         _setState(state, true);
1235     });
1236
1237     this.getter = function(fn){
1238         _getState = fn;
1239         return this;
1240     };
1241
1242     this.setter = function(fn, callback) {
1243         if (!callback) {
1244             callback = function(){};
1245         }
1246         _setState = function(state, update){
1247             fn(state);
1248             if (update) {
1249                 callback();
1250             }
1251         };
1252         return this;
1253     };
1254
1255     this.init = function(state){
1256         init = init || {};
1257         nv.utils.deepExtend(init, state);
1258     };
1259
1260     var _set = function(){
1261         var settings = _getState();
1262
1263         if (JSON.stringify(settings) === JSON.stringify(state)) {
1264             return false;
1265         }
1266
1267         for (var key in settings) {
1268             if (state[key] === undefined) {
1269                 state[key] = {};
1270             }
1271             state[key] = settings[key];
1272             changed = true;
1273         }
1274         return true;
1275     };
1276
1277     this.update = function(){
1278         if (init) {
1279             _setState(init, false);
1280             init = null;
1281         }
1282         if (_set.call(this)) {
1283             this.dispatch.change(state);
1284         }
1285     };
1286
1287 };
1288
1289
1290 /*
1291 Snippet of code you can insert into each nv.models.* to give you the ability to
1292 do things like:
1293 chart.options({
1294   showXAxis: true,
1295   tooltips: true
1296 });
1297
1298 To enable in the chart:
1299 chart.options = nv.utils.optionsFunc.bind(chart);
1300 */
1301 nv.utils.optionsFunc = function(args) {
1302     if (args) {
1303         d3.map(args).forEach((function(key,value) {
1304             if (typeof this[key] === "function") {
1305                 this[key](value);
1306             }
1307         }).bind(this));
1308     }
1309     return this;
1310 };
1311
1312
1313 /*
1314 numTicks:  requested number of ticks
1315 data:  the chart data
1316
1317 returns the number of ticks to actually use on X axis, based on chart data
1318 to avoid duplicate ticks with the same value
1319 */
1320 nv.utils.calcTicksX = function(numTicks, data) {
1321     // find max number of values from all data streams
1322     var numValues = 1;
1323     var i = 0;
1324     for (i; i < data.length; i += 1) {
1325         var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1326         numValues = stream_len > numValues ? stream_len : numValues;
1327     }
1328     nv.log("Requested number of ticks: ", numTicks);
1329     nv.log("Calculated max values to be: ", numValues);
1330     // make sure we don't have more ticks than values to avoid duplicates
1331     numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1332     // make sure we have at least one tick
1333     numTicks = numTicks < 1 ? 1 : numTicks;
1334     // make sure it's an integer
1335     numTicks = Math.floor(numTicks);
1336     nv.log("Calculating tick count as: ", numTicks);
1337     return numTicks;
1338 };
1339
1340
1341 /*
1342 returns number of ticks to actually use on Y axis, based on chart data
1343 */
1344 nv.utils.calcTicksY = function(numTicks, data) {
1345     // currently uses the same logic but we can adjust here if needed later
1346     return nv.utils.calcTicksX(numTicks, data);
1347 };
1348
1349
1350 /*
1351 Add a particular option from an options object onto chart
1352 Options exposed on a chart are a getter/setter function that returns chart
1353 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1354
1355 option objects should be generated via Object.create() to provide
1356 the option of manipulating data via get/set functions.
1357 */
1358 nv.utils.initOption = function(chart, name) {
1359     // if it's a call option, just call it directly, otherwise do get/set
1360     if (chart._calls && chart._calls[name]) {
1361         chart[name] = chart._calls[name];
1362     } else {
1363         chart[name] = function (_) {
1364             if (!arguments.length) return chart._options[name];
1365             chart._overrides[name] = true;
1366             chart._options[name] = _;
1367             return chart;
1368         };
1369         // calling the option as _option will ignore if set by option already
1370         // so nvd3 can set options internally but the stop if set manually
1371         chart['_' + name] = function(_) {
1372             if (!arguments.length) return chart._options[name];
1373             if (!chart._overrides[name]) {
1374                 chart._options[name] = _;
1375             }
1376             return chart;
1377         }
1378     }
1379 };
1380
1381
1382 /*
1383 Add all options in an options object to the chart
1384 */
1385 nv.utils.initOptions = function(chart) {
1386     chart._overrides = chart._overrides || {};
1387     var ops = Object.getOwnPropertyNames(chart._options || {});
1388     var calls = Object.getOwnPropertyNames(chart._calls || {});
1389     ops = ops.concat(calls);
1390     for (var i in ops) {
1391         nv.utils.initOption(chart, ops[i]);
1392     }
1393 };
1394
1395
1396 /*
1397 Inherit options from a D3 object
1398 d3.rebind makes calling the function on target actually call it on source
1399 Also use _d3options so we can track what we inherit for documentation and chained inheritance
1400 */
1401 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
1402     target._d3options = oplist.concat(target._d3options || []);
1403     oplist.unshift(d3_source);
1404     oplist.unshift(target);
1405     d3.rebind.apply(this, oplist);
1406 };
1407
1408
1409 /*
1410 Remove duplicates from an array
1411 */
1412 nv.utils.arrayUnique = function(a) {
1413     return a.sort().filter(function(item, pos) {
1414         return !pos || item != a[pos - 1];
1415     });
1416 };
1417
1418
1419 /*
1420 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
1421 Necessary since d3 doesn't let you extend its list -_-
1422 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
1423 */
1424 nv.utils.symbolMap = d3.map();
1425
1426
1427 /*
1428 Replaces d3.svg.symbol so that we can look both there and our own map
1429  */
1430 nv.utils.symbol = function() {
1431     var type,
1432         size = 64;
1433     function symbol(d,i) {
1434         var t = type.call(this,d,i);
1435         var s = size.call(this,d,i);
1436         if (d3.svg.symbolTypes.indexOf(t) !== -1) {
1437             return d3.svg.symbol().type(t).size(s)();
1438         } else {
1439             return nv.utils.symbolMap.get(t)(s);
1440         }
1441     }
1442     symbol.type = function(_) {
1443         if (!arguments.length) return type;
1444         type = d3.functor(_);
1445         return symbol;
1446     };
1447     symbol.size = function(_) {
1448         if (!arguments.length) return size;
1449         size = d3.functor(_);
1450         return symbol;
1451     };
1452     return symbol;
1453 };
1454
1455
1456 /*
1457 Inherit option getter/setter functions from source to target
1458 d3.rebind makes calling the function on target actually call it on source
1459 Also track via _inherited and _d3options so we can track what we inherit
1460 for documentation generation purposes and chained inheritance
1461 */
1462 nv.utils.inheritOptions = function(target, source) {
1463     // inherit all the things
1464     var ops = Object.getOwnPropertyNames(source._options || {});
1465     var calls = Object.getOwnPropertyNames(source._calls || {});
1466     var inherited = source._inherited || [];
1467     var d3ops = source._d3options || [];
1468     var args = ops.concat(calls).concat(inherited).concat(d3ops);
1469     args.unshift(source);
1470     args.unshift(target);
1471     d3.rebind.apply(this, args);
1472     // pass along the lists to keep track of them, don't allow duplicates
1473     target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
1474     target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
1475 };
1476
1477
1478 /*
1479 Runs common initialize code on the svg before the chart builds
1480 */
1481 nv.utils.initSVG = function(svg) {
1482     svg.classed({'nvd3-svg':true});
1483 };
1484
1485
1486 /*
1487 Sanitize and provide default for the container height.
1488 */
1489 nv.utils.sanitizeHeight = function(height, container) {
1490     return (height || parseInt(container.style('height'), 10) || 400);
1491 };
1492
1493
1494 /*
1495 Sanitize and provide default for the container width.
1496 */
1497 nv.utils.sanitizeWidth = function(width, container) {
1498     return (width || parseInt(container.style('width'), 10) || 960);
1499 };
1500
1501
1502 /*
1503 Calculate the available height for a chart.
1504 */
1505 nv.utils.availableHeight = function(height, container, margin) {
1506     return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom;
1507 };
1508
1509 /*
1510 Calculate the available width for a chart.
1511 */
1512 nv.utils.availableWidth = function(width, container, margin) {
1513     return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right;
1514 };
1515
1516 /*
1517 Clear any rendered chart components and display a chart's 'noData' message
1518 */
1519 nv.utils.noData = function(chart, container) {
1520     var opt = chart.options(),
1521         margin = opt.margin(),
1522         noData = opt.noData(),
1523         data = (noData == null) ? ["No Data Available."] : [noData],
1524         height = nv.utils.availableHeight(opt.height(), container, margin),
1525         width = nv.utils.availableWidth(opt.width(), container, margin),
1526         x = margin.left + width/2,
1527         y = margin.top + height/2;
1528
1529     //Remove any previously created chart components
1530     container.selectAll('g').remove();
1531
1532     var noDataText = container.selectAll('.nv-noData').data(data);
1533
1534     noDataText.enter().append('text')
1535         .attr('class', 'nvd3 nv-noData')
1536         .attr('dy', '-.7em')
1537         .style('text-anchor', 'middle');
1538
1539     noDataText
1540         .attr('x', x)
1541         .attr('y', y)
1542         .text(function(t){ return t; });
1543 };
1544
1545 nv.models.axis = function() {
1546     "use strict";
1547
1548     //============================================================
1549     // Public Variables with Default Settings
1550     //------------------------------------------------------------
1551
1552     var axis = d3.svg.axis();
1553     var scale = d3.scale.linear();
1554
1555     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1556         , width = 75 //only used for tickLabel currently
1557         , height = 60 //only used for tickLabel currently
1558         , axisLabelText = null
1559         , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1560         , rotateLabels = 0
1561         , rotateYLabel = true
1562         , staggerLabels = false
1563         , isOrdinal = false
1564         , ticks = null
1565         , axisLabelDistance = 0
1566         , duration = 250
1567         , dispatch = d3.dispatch('renderEnd')
1568         ;
1569     axis
1570         .scale(scale)
1571         .orient('bottom')
1572         .tickFormat(function(d) { return d })
1573     ;
1574
1575     //============================================================
1576     // Private Variables
1577     //------------------------------------------------------------
1578
1579     var scale0;
1580     var renderWatch = nv.utils.renderWatch(dispatch, duration);
1581
1582     function chart(selection) {
1583         renderWatch.reset();
1584         selection.each(function(data) {
1585             var container = d3.select(this);
1586             nv.utils.initSVG(container);
1587
1588             // Setup containers and skeleton of chart
1589             var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1590             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1591             var gEnter = wrapEnter.append('g');
1592             var g = wrap.select('g');
1593
1594             if (ticks !== null)
1595                 axis.ticks(ticks);
1596             else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1597                 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1598
1599             //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1600             g.watchTransition(renderWatch, 'axis').call(axis);
1601
1602             scale0 = scale0 || axis.scale();
1603
1604             var fmt = axis.tickFormat();
1605             if (fmt == null) {
1606                 fmt = scale0.tickFormat();
1607             }
1608
1609             var axisLabel = g.selectAll('text.nv-axislabel')
1610                 .data([axisLabelText || null]);
1611             axisLabel.exit().remove();
1612
1613             var xLabelMargin;
1614             var axisMaxMin;
1615             var w;
1616             switch (axis.orient()) {
1617                 case 'top':
1618                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1619                     if (scale.range().length < 2) {
1620                         w = 0;
1621                     } else if (scale.range().length === 2) {
1622                         w = scale.range()[1];
1623                     } else {
1624                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1625                     }
1626                     axisLabel
1627                         .attr('text-anchor', 'middle')
1628                         .attr('y', 0)
1629                         .attr('x', w/2);
1630                     if (showMaxMin) {
1631                         axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1632                             .data(scale.domain());
1633                         axisMaxMin.enter().append('g').attr('class',function(d,i){
1634                                 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
1635                         }).append('text');
1636                         axisMaxMin.exit().remove();
1637                         axisMaxMin
1638                             .attr('transform', function(d,i) {
1639                                 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
1640                             })
1641                             .select('text')
1642                             .attr('dy', '-0.5em')
1643                             .attr('y', -axis.tickPadding())
1644                             .attr('text-anchor', 'middle')
1645                             .text(function(d,i) {
1646                                 var v = fmt(d);
1647                                 return ('' + v).match('NaN') ? '' : v;
1648                             });
1649                         axisMaxMin.watchTransition(renderWatch, 'min-max top')
1650                             .attr('transform', function(d,i) {
1651                                 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
1652                             });
1653                     }
1654                     break;
1655                 case 'bottom':
1656                     xLabelMargin = axisLabelDistance + 36;
1657                     var maxTextWidth = 30;
1658                     var textHeight = 0;
1659                     var xTicks = g.selectAll('g').select("text");
1660                     var rotateLabelsRule = '';
1661                     if (rotateLabels%360) {
1662                         //Calculate the longest xTick width
1663                         xTicks.each(function(d,i){
1664                             var box = this.getBoundingClientRect();
1665                             var width = box.width;
1666                             textHeight = box.height;
1667                             if(width > maxTextWidth) maxTextWidth = width;
1668                         });
1669                         rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
1670                         //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1671                         var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1672                         xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1673                         //Rotate all xTicks
1674                         xTicks
1675                             .attr('transform', rotateLabelsRule)
1676                             .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1677                     }
1678                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1679                     if (scale.range().length < 2) {
1680                         w = 0;
1681                     } else if (scale.range().length === 2) {
1682                         w = scale.range()[1];
1683                     } else {
1684                         w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1685                     }
1686                     axisLabel
1687                         .attr('text-anchor', 'middle')
1688                         .attr('y', xLabelMargin)
1689                         .attr('x', w/2);
1690                     if (showMaxMin) {
1691                         //if (showMaxMin && !isOrdinal) {
1692                         axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1693                             //.data(scale.domain())
1694                             .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1695                         axisMaxMin.enter().append('g').attr('class',function(d,i){
1696                                 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
1697                         }).append('text');
1698                         axisMaxMin.exit().remove();
1699                         axisMaxMin
1700                             .attr('transform', function(d,i) {
1701                                 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1702                             })
1703                             .select('text')
1704                             .attr('dy', '.71em')
1705                             .attr('y', axis.tickPadding())
1706                             .attr('transform', rotateLabelsRule)
1707                             .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1708                             .text(function(d,i) {
1709                                 var v = fmt(d);
1710                                 return ('' + v).match('NaN') ? '' : v;
1711                             });
1712                         axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1713                             .attr('transform', function(d,i) {
1714                                 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1715                             });
1716                     }
1717                     if (staggerLabels)
1718                         xTicks
1719                             .attr('transform', function(d,i) {
1720                                 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
1721                             });
1722
1723                     break;
1724                 case 'right':
1725                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1726                     axisLabel
1727                         .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1728                         .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1729                         .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
1730                         .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
1731                     if (showMaxMin) {
1732                         axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1733                             .data(scale.domain());
1734                         axisMaxMin.enter().append('g').attr('class',function(d,i){
1735                                 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
1736                         }).append('text')
1737                             .style('opacity', 0);
1738                         axisMaxMin.exit().remove();
1739                         axisMaxMin
1740                             .attr('transform', function(d,i) {
1741                                 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
1742                             })
1743                             .select('text')
1744                             .attr('dy', '.32em')
1745                             .attr('y', 0)
1746                             .attr('x', axis.tickPadding())
1747                             .style('text-anchor', 'start')
1748                             .text(function(d, i) {
1749                                 var v = fmt(d);
1750                                 return ('' + v).match('NaN') ? '' : v;
1751                             });
1752                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1753                             .attr('transform', function(d,i) {
1754                                 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1755                             })
1756                             .select('text')
1757                             .style('opacity', 1);
1758                     }
1759                     break;
1760                 case 'left':
1761                     /*
1762                      //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1763                      var yTicks = g.selectAll('g').select("text");
1764                      yTicks.each(function(d,i){
1765                      var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1766                      if(labelPadding > width) width = labelPadding;
1767                      });
1768                      */
1769                     axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1770                     axisLabel
1771                         .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1772                         .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1773                         .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
1774                         .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
1775                     if (showMaxMin) {
1776                         axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1777                             .data(scale.domain());
1778                         axisMaxMin.enter().append('g').attr('class',function(d,i){
1779                                 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
1780                         }).append('text')
1781                             .style('opacity', 0);
1782                         axisMaxMin.exit().remove();
1783                         axisMaxMin
1784                             .attr('transform', function(d,i) {
1785                                 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
1786                             })
1787                             .select('text')
1788                             .attr('dy', '.32em')
1789                             .attr('y', 0)
1790                             .attr('x', -axis.tickPadding())
1791                             .attr('text-anchor', 'end')
1792                             .text(function(d,i) {
1793                                 var v = fmt(d);
1794                                 return ('' + v).match('NaN') ? '' : v;
1795                             });
1796                         axisMaxMin.watchTransition(renderWatch, 'min-max right')
1797                             .attr('transform', function(d,i) {
1798                                 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1799                             })
1800                             .select('text')
1801                             .style('opacity', 1);
1802                     }
1803                     break;
1804             }
1805             axisLabel.text(function(d) { return d });
1806
1807             if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1808                 //check if max and min overlap other values, if so, hide the values that overlap
1809                 g.selectAll('g') // the g's wrapping each tick
1810                     .each(function(d,i) {
1811                         d3.select(this).select('text').attr('opacity', 1);
1812                         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!
1813                             if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1814                                 d3.select(this).attr('opacity', 0);
1815
1816                             d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1817                         }
1818                     });
1819
1820                 //if Max and Min = 0 only show min, Issue #281
1821                 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
1822                     wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
1823                         return !i ? 1 : 0
1824                     });
1825                 }
1826             }
1827
1828             if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1829                 var maxMinRange = [];
1830                 wrap.selectAll('g.nv-axisMaxMin')
1831                     .each(function(d,i) {
1832                         try {
1833                             if (i) // i== 1, max position
1834                                 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)
1835                             else // i==0, min position
1836                                 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1837                         }catch (err) {
1838                             if (i) // i== 1, max position
1839                                 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)
1840                             else // i==0, min position
1841                                 maxMinRange.push(scale(d) + 4);
1842                         }
1843                     });
1844                 // the g's wrapping each tick
1845                 g.selectAll('g').each(function(d, i) {
1846                     if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1847                         if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1848                             d3.select(this).remove();
1849                         else
1850                             d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1851                     }
1852                 });
1853             }
1854
1855             //Highlight zero tick line
1856             g.selectAll('.tick')
1857                 .filter(function (d) {
1858                     /*
1859                     The filter needs to return only ticks at or near zero.
1860                     Numbers like 0.00001 need to count as zero as well,
1861                     and the arithmetic trick below solves that.
1862                     */
1863                     return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
1864                 }) 
1865                 .classed('zero', true);
1866             
1867             //store old scales for use in transitions on update
1868             scale0 = scale.copy();
1869
1870         });
1871
1872         renderWatch.renderEnd('axis immediate');
1873         return chart;
1874     }
1875
1876     //============================================================
1877     // Expose Public Variables
1878     //------------------------------------------------------------
1879
1880     // expose chart's sub-components
1881     chart.axis = axis;
1882     chart.dispatch = dispatch;
1883
1884     chart.options = nv.utils.optionsFunc.bind(chart);
1885     chart._options = Object.create({}, {
1886         // simple options, just get/set the necessary values
1887         axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
1888         staggerLabels:     {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
1889         rotateLabels:      {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
1890         rotateYLabel:      {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
1891         showMaxMin:        {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
1892         axisLabel:         {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
1893         height:            {get: function(){return height;}, set: function(_){height=_;}},
1894         ticks:             {get: function(){return ticks;}, set: function(_){ticks=_;}},
1895         width:             {get: function(){return width;}, set: function(_){width=_;}},
1896
1897         // options that require extra logic in the setter
1898         margin: {get: function(){return margin;}, set: function(_){
1899             margin.top    = _.top !== undefined    ? _.top    : margin.top;
1900             margin.right  = _.right !== undefined  ? _.right  : margin.right;
1901             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
1902             margin.left   = _.left !== undefined   ? _.left   : margin.left;
1903         }},
1904         duration: {get: function(){return duration;}, set: function(_){
1905             duration=_;
1906             renderWatch.reset(duration);
1907         }},
1908         scale: {get: function(){return scale;}, set: function(_){
1909             scale = _;
1910             axis.scale(scale);
1911             isOrdinal = typeof scale.rangeBands === 'function';
1912             nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1913         }}
1914     });
1915
1916     nv.utils.initOptions(chart);
1917     nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
1918     nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1919
1920     return chart;
1921 };
1922 nv.models.boxPlot = function() {
1923     "use strict";
1924
1925     //============================================================
1926     // Public Variables with Default Settings
1927     //------------------------------------------------------------
1928
1929     var margin = {top: 0, right: 0, bottom: 0, left: 0}
1930         , width = 960
1931         , height = 500
1932         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
1933         , x = d3.scale.ordinal()
1934         , y = d3.scale.linear()
1935         , getX = function(d) { return d.x }
1936         , getY = function(d) { return d.y }
1937         , color = nv.utils.defaultColor()
1938         , container = null
1939         , xDomain
1940         , yDomain
1941         , xRange
1942         , yRange
1943         , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
1944         , duration = 250
1945         , maxBoxWidth = null
1946         ;
1947
1948     //============================================================
1949     // Private Variables
1950     //------------------------------------------------------------
1951
1952     var x0, y0;
1953     var renderWatch = nv.utils.renderWatch(dispatch, duration);
1954
1955     function chart(selection) {
1956         renderWatch.reset();
1957         selection.each(function(data) {
1958             var availableWidth = width - margin.left - margin.right,
1959                 availableHeight = height - margin.top - margin.bottom;
1960
1961             container = d3.select(this);
1962             nv.utils.initSVG(container);
1963
1964             // Setup Scales
1965             x   .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
1966                 .rangeBands(xRange || [0, availableWidth], .1);
1967
1968             // if we know yDomain, no need to calculate
1969             var yData = []
1970             if (!yDomain) {
1971                 // (y-range is based on quartiles, whiskers and outliers)
1972
1973                 // lower values
1974                 var yMin = d3.min(data.map(function(d) {
1975                     var min_arr = [];
1976
1977                     min_arr.push(d.values.Q1);
1978                     if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); }
1979                     if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); }
1980
1981                     return d3.min(min_arr);
1982                 }));
1983
1984                 // upper values
1985                 var yMax = d3.max(data.map(function(d) {
1986                     var max_arr = [];
1987
1988                     max_arr.push(d.values.Q3);
1989                     if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); }
1990                     if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); }
1991
1992                     return d3.max(max_arr);
1993                 }));
1994
1995                 yData = [ yMin, yMax ] ;
1996             }
1997
1998             y.domain(yDomain || yData);
1999             y.range(yRange || [availableHeight, 0]);
2000
2001             //store old scales if they exist
2002             x0 = x0 || x;
2003             y0 = y0 || y.copy().range([y(0),y(0)]);
2004
2005             // Setup containers and skeleton of chart
2006             var wrap = container.selectAll('g.nv-wrap').data([data]);
2007             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
2008             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2009
2010             var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
2011             var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
2012             boxplots
2013                 .attr('class', 'nv-boxplot')
2014                 .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; })
2015                 .classed('hover', function(d) { return d.hover });
2016             boxplots
2017                 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2018                 .style('stroke-opacity', 1)
2019                 .style('fill-opacity', .75)
2020                 .delay(function(d,i) { return i * duration / data.length })
2021                 .attr('transform', function(d,i) {
2022                     return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)';
2023                 });
2024             boxplots.exit().remove();
2025
2026             // ----- add the SVG elements for each boxPlot -----
2027
2028             // conditionally append whisker lines
2029             boxEnter.each(function(d,i) {
2030               var box = d3.select(this);
2031
2032               ['low', 'high'].forEach(function(key) {
2033                 if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) {
2034                   box.append('line')
2035                     .style('stroke', (d.color) ? d.color : color(d,i))
2036                     .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
2037
2038                   box.append('line')
2039                     .style('stroke', (d.color) ? d.color : color(d,i))
2040                     .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2041                 }
2042               });
2043             });
2044
2045             // outliers
2046             // TODO: support custom colors here
2047             var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
2048                 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; }
2049                 else { return []; }
2050             });
2051             outliers.enter().append('circle')
2052                 .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) })
2053                 .on('mouseover', function(d,i,j) {
2054                     d3.select(this).classed('hover', true);
2055                     dispatch.elementMouseover({
2056                         series: { key: d, color: color(d,j) },
2057                         e: d3.event
2058                     });
2059                 })
2060                 .on('mouseout', function(d,i,j) {
2061                     d3.select(this).classed('hover', false);
2062                     dispatch.elementMouseout({
2063                         series: { key: d, color: color(d,j) },
2064                         e: d3.event
2065                     });
2066                 })
2067                 .on('mousemove', function(d,i) {
2068                     dispatch.elementMousemove({e: d3.event});
2069                 });
2070
2071             outliers.attr('class', 'nv-boxplot-outlier');
2072             outliers
2073               .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
2074                 .attr('cx', x.rangeBand() * .45)
2075                 .attr('cy', function(d,i,j) { return y(d); })
2076                 .attr('r', '3');
2077             outliers.exit().remove();
2078
2079             var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); };
2080             var box_left  = function() { return x.rangeBand() * .45 - box_width()/2; };
2081             var box_right = function() { return x.rangeBand() * .45 + box_width()/2; };
2082
2083             // update whisker lines and ticks
2084             ['low', 'high'].forEach(function(key) {
2085               var endpoint = (key === 'low') ? 'Q1' : 'Q3';
2086
2087               boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
2088                 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2089                   .attr('x1', x.rangeBand() * .45 )
2090                   .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2091                   .attr('x2', x.rangeBand() * .45 )
2092                   .attr('y2', function(d,i) { return y(d.values[endpoint]); });
2093
2094               boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
2095                 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2096                   .attr('x1', box_left )
2097                   .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2098                   .attr('x2', box_right )
2099                   .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); });
2100             });
2101
2102             ['low', 'high'].forEach(function(key) {
2103               boxEnter.selectAll('.nv-boxplot-' + key)
2104                 .on('mouseover', function(d,i,j) {
2105                     d3.select(this).classed('hover', true);
2106                     dispatch.elementMouseover({
2107                         series: { key: d.values['whisker_' + key], color: color(d,j) },
2108                         e: d3.event
2109                     });
2110                 })
2111                 .on('mouseout', function(d,i,j) {
2112                     d3.select(this).classed('hover', false);
2113                     dispatch.elementMouseout({
2114                         series: { key: d.values['whisker_' + key], color: color(d,j) },
2115                         e: d3.event
2116                     });
2117                 })
2118                 .on('mousemove', function(d,i) {
2119                     dispatch.elementMousemove({e: d3.event});
2120                 });
2121             });
2122
2123             // boxes
2124             boxEnter.append('rect')
2125                 .attr('class', 'nv-boxplot-box')
2126                 // tooltip events
2127                 .on('mouseover', function(d,i) {
2128                     d3.select(this).classed('hover', true);
2129                     dispatch.elementMouseover({
2130                         key: d.label,
2131                         value: d.label,
2132                         series: [
2133                             { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2134                             { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2135                             { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2136                         ],
2137                         data: d,
2138                         index: i,
2139                         e: d3.event
2140                     });
2141                 })
2142                 .on('mouseout', function(d,i) {
2143                     d3.select(this).classed('hover', false);
2144                     dispatch.elementMouseout({
2145                         key: d.label,
2146                         value: d.label,
2147                         series: [
2148                             { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2149                             { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2150                             { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2151                         ],
2152                         data: d,
2153                         index: i,
2154                         e: d3.event
2155                     });
2156                 })
2157                 .on('mousemove', function(d,i) {
2158                     dispatch.elementMousemove({e: d3.event});
2159                 });
2160
2161             // box transitions
2162             boxplots.select('rect.nv-boxplot-box')
2163               .watchTransition(renderWatch, 'nv-boxplot: boxes')
2164                 .attr('y', function(d,i) { return y(d.values.Q3); })
2165                 .attr('width', box_width)
2166                 .attr('x', box_left )
2167
2168                 .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 })
2169                 .style('fill', function(d,i) { return d.color || color(d,i) })
2170                 .style('stroke', function(d,i) { return d.color || color(d,i) });
2171
2172             // median line
2173             boxEnter.append('line').attr('class', 'nv-boxplot-median');
2174
2175             boxplots.select('line.nv-boxplot-median')
2176               .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
2177                 .attr('x1', box_left)
2178                 .attr('y1', function(d,i) { return y(d.values.Q2); })
2179                 .attr('x2', box_right)
2180                 .attr('y2', function(d,i) { return y(d.values.Q2); });
2181
2182             //store old scales for use in transitions on update
2183             x0 = x.copy();
2184             y0 = y.copy();
2185         });
2186
2187         renderWatch.renderEnd('nv-boxplot immediate');
2188         return chart;
2189     }
2190
2191     //============================================================
2192     // Expose Public Variables
2193     //------------------------------------------------------------
2194
2195     chart.dispatch = dispatch;
2196     chart.options = nv.utils.optionsFunc.bind(chart);
2197
2198     chart._options = Object.create({}, {
2199         // simple options, just get/set the necessary values
2200         width:   {get: function(){return width;}, set: function(_){width=_;}},
2201         height:  {get: function(){return height;}, set: function(_){height=_;}},
2202         maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
2203         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
2204         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
2205         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
2206         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
2207         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
2208         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
2209         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
2210         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
2211         id:          {get: function(){return id;}, set: function(_){id=_;}},
2212         // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
2213
2214         // options that require extra logic in the setter
2215         margin: {get: function(){return margin;}, set: function(_){
2216             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2217             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2218             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2219             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2220         }},
2221         color:  {get: function(){return color;}, set: function(_){
2222             color = nv.utils.getColor(_);
2223         }},
2224         duration: {get: function(){return duration;}, set: function(_){
2225             duration = _;
2226             renderWatch.reset(duration);
2227         }}
2228     });
2229
2230     nv.utils.initOptions(chart);
2231
2232     return chart;
2233 };
2234 nv.models.boxPlotChart = function() {
2235     "use strict";
2236
2237     //============================================================
2238     // Public Variables with Default Settings
2239     //------------------------------------------------------------
2240
2241     var boxplot = nv.models.boxPlot()
2242         , xAxis = nv.models.axis()
2243         , yAxis = nv.models.axis()
2244         ;
2245
2246     var margin = {top: 15, right: 10, bottom: 50, left: 60}
2247         , width = null
2248         , height = null
2249         , color = nv.utils.getColor()
2250         , showXAxis = true
2251         , showYAxis = true
2252         , rightAlignYAxis = false
2253         , staggerLabels = false
2254         , tooltip = nv.models.tooltip()
2255         , x
2256         , y
2257         , noData = "No Data Available."
2258         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd')
2259         , duration = 250
2260         ;
2261
2262     xAxis
2263         .orient('bottom')
2264         .showMaxMin(false)
2265         .tickFormat(function(d) { return d })
2266     ;
2267     yAxis
2268         .orient((rightAlignYAxis) ? 'right' : 'left')
2269         .tickFormat(d3.format(',.1f'))
2270     ;
2271     
2272     tooltip.duration(0);
2273
2274     //============================================================
2275     // Private Variables
2276     //------------------------------------------------------------
2277
2278     var renderWatch = nv.utils.renderWatch(dispatch, duration);
2279
2280     function chart(selection) {
2281         renderWatch.reset();
2282         renderWatch.models(boxplot);
2283         if (showXAxis) renderWatch.models(xAxis);
2284         if (showYAxis) renderWatch.models(yAxis);
2285
2286         selection.each(function(data) {
2287             var container = d3.select(this),
2288                 that = this;
2289             nv.utils.initSVG(container);
2290             var availableWidth = (width  || parseInt(container.style('width')) || 960)
2291                     - margin.left - margin.right,
2292                 availableHeight = (height || parseInt(container.style('height')) || 400)
2293                     - margin.top - margin.bottom;
2294
2295             chart.update = function() {
2296                 dispatch.beforeUpdate();
2297                 container.transition().duration(duration).call(chart);
2298             };
2299             chart.container = this;
2300
2301             // Display No Data message if there's nothing to show. (quartiles required at minimum)
2302             if (!data || !data.length || 
2303                     !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) {
2304                 var noDataText = container.selectAll('.nv-noData').data([noData]);
2305
2306                 noDataText.enter().append('text')
2307                     .attr('class', 'nvd3 nv-noData')
2308                     .attr('dy', '-.7em')
2309                     .style('text-anchor', 'middle');
2310
2311                 noDataText
2312                     .attr('x', margin.left + availableWidth / 2)
2313                     .attr('y', margin.top + availableHeight / 2)
2314                     .text(function(d) { return d });
2315
2316                 return chart;
2317             } else {
2318                 container.selectAll('.nv-noData').remove();
2319             }
2320
2321             // Setup Scales
2322             x = boxplot.xScale();
2323             y = boxplot.yScale().clamp(true);
2324
2325             // Setup containers and skeleton of chart
2326             var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
2327             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
2328             var defsEnter = gEnter.append('defs');
2329             var g = wrap.select('g');
2330
2331             gEnter.append('g').attr('class', 'nv-x nv-axis');
2332             gEnter.append('g').attr('class', 'nv-y nv-axis')
2333                 .append('g').attr('class', 'nv-zeroLine')
2334                 .append('line');
2335
2336             gEnter.append('g').attr('class', 'nv-barsWrap');
2337
2338             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2339
2340             if (rightAlignYAxis) {
2341                 g.select(".nv-y.nv-axis")
2342                     .attr("transform", "translate(" + availableWidth + ",0)");
2343             }
2344
2345             // Main Chart Component(s)
2346             boxplot
2347                 .width(availableWidth)
2348                 .height(availableHeight);
2349
2350             var barsWrap = g.select('.nv-barsWrap')
2351                 .datum(data.filter(function(d) { return !d.disabled }))
2352
2353             barsWrap.transition().call(boxplot);
2354
2355
2356             defsEnter.append('clipPath')
2357                 .attr('id', 'nv-x-label-clip-' + boxplot.id())
2358                 .append('rect');
2359
2360             g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
2361                 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
2362                 .attr('height', 16)
2363                 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
2364
2365             // Setup Axes
2366             if (showXAxis) {
2367                 xAxis
2368                     .scale(x)
2369                     .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
2370                     .tickSize(-availableHeight, 0);
2371
2372                 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
2373                 g.select('.nv-x.nv-axis').call(xAxis);
2374
2375                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
2376                 if (staggerLabels) {
2377                     xTicks
2378                         .selectAll('text')
2379                         .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2380                 }
2381             }
2382
2383             if (showYAxis) {
2384                 yAxis
2385                     .scale(y)
2386                     .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
2387                     .tickSize( -availableWidth, 0);
2388
2389                 g.select('.nv-y.nv-axis').call(yAxis);
2390             }
2391
2392             // Zero line
2393             g.select(".nv-zeroLine line")
2394                 .attr("x1",0)
2395                 .attr("x2",availableWidth)
2396                 .attr("y1", y(0))
2397                 .attr("y2", y(0))
2398             ;
2399
2400             //============================================================
2401             // Event Handling/Dispatching (in chart's scope)
2402             //------------------------------------------------------------
2403         });
2404
2405         renderWatch.renderEnd('nv-boxplot chart immediate');
2406         return chart;
2407     }
2408
2409     //============================================================
2410     // Event Handling/Dispatching (out of chart's scope)
2411     //------------------------------------------------------------
2412
2413     boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
2414         tooltip.data(evt).hidden(false);
2415     });
2416
2417     boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
2418         tooltip.data(evt).hidden(true);
2419     });
2420
2421     boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
2422         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2423     });
2424
2425     //============================================================
2426     // Expose Public Variables
2427     //------------------------------------------------------------
2428
2429     chart.dispatch = dispatch;
2430     chart.boxplot = boxplot;
2431     chart.xAxis = xAxis;
2432     chart.yAxis = yAxis;
2433     chart.tooltip = tooltip;
2434
2435     chart.options = nv.utils.optionsFunc.bind(chart);
2436
2437     chart._options = Object.create({}, {
2438         // simple options, just get/set the necessary values
2439         width:      {get: function(){return width;}, set: function(_){width=_;}},
2440         height:     {get: function(){return height;}, set: function(_){height=_;}},
2441         staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
2442         showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
2443         showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
2444         tooltips:    {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2445         tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2446         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
2447
2448         // options that require extra logic in the setter
2449         margin: {get: function(){return margin;}, set: function(_){
2450             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2451             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2452             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2453             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2454         }},
2455         duration: {get: function(){return duration;}, set: function(_){
2456             duration = _;
2457             renderWatch.reset(duration);
2458             boxplot.duration(duration);
2459             xAxis.duration(duration);
2460             yAxis.duration(duration);
2461         }},
2462         color:  {get: function(){return color;}, set: function(_){
2463             color = nv.utils.getColor(_);
2464             boxplot.color(color);
2465         }},
2466         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
2467             rightAlignYAxis = _;
2468             yAxis.orient( (_) ? 'right' : 'left');
2469         }}
2470     });
2471
2472     nv.utils.inheritOptions(chart, boxplot);
2473     nv.utils.initOptions(chart);
2474
2475     return chart;
2476 }
2477 // Chart design based on the recommendations of Stephen Few. Implementation
2478 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2479 // http://projects.instantcognition.com/protovis/bulletchart/
2480
2481 nv.models.bullet = function() {
2482     "use strict";
2483
2484     //============================================================
2485     // Public Variables with Default Settings
2486     //------------------------------------------------------------
2487
2488     var margin = {top: 0, right: 0, bottom: 0, left: 0}
2489         , orient = 'left' // TODO top & bottom
2490         , reverse = false
2491         , ranges = function(d) { return d.ranges }
2492         , markers = function(d) { return d.markers ? d.markers : [0] }
2493         , measures = function(d) { return d.measures }
2494         , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
2495         , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
2496         , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
2497         , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
2498         , width = 380
2499         , height = 30
2500         , container = null
2501         , tickFormat = null
2502         , color = nv.utils.getColor(['#1f77b4'])
2503         , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
2504         ;
2505
2506     function chart(selection) {
2507         selection.each(function(d, i) {
2508             var availableWidth = width - margin.left - margin.right,
2509                 availableHeight = height - margin.top - margin.bottom;
2510
2511             container = d3.select(this);
2512             nv.utils.initSVG(container);
2513
2514             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2515                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2516                 measurez = measures.call(this, d, i).slice().sort(d3.descending),
2517                 rangeLabelz = rangeLabels.call(this, d, i).slice(),
2518                 markerLabelz = markerLabels.call(this, d, i).slice(),
2519                 measureLabelz = measureLabels.call(this, d, i).slice();
2520
2521             // Setup Scales
2522             // Compute the new x-scale.
2523             var x1 = d3.scale.linear()
2524                 .domain( d3.extent(d3.merge([forceX, rangez])) )
2525                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2526
2527             // Retrieve the old x-scale, if this is an update.
2528             var x0 = this.__chart__ || d3.scale.linear()
2529                 .domain([0, Infinity])
2530                 .range(x1.range());
2531
2532             // Stash the new scale.
2533             this.__chart__ = x1;
2534
2535             var rangeMin = d3.min(rangez), //rangez[2]
2536                 rangeMax = d3.max(rangez), //rangez[0]
2537                 rangeAvg = rangez[1];
2538
2539             // Setup containers and skeleton of chart
2540             var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
2541             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
2542             var gEnter = wrapEnter.append('g');
2543             var g = wrap.select('g');
2544
2545             gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
2546             gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
2547             gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
2548             gEnter.append('rect').attr('class', 'nv-measure');
2549
2550             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2551
2552             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2553                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2554             var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
2555                 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
2556
2557             g.select('rect.nv-rangeMax')
2558                 .attr('height', availableHeight)
2559                 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
2560                 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
2561                 .datum(rangeMax > 0 ? rangeMax : rangeMin)
2562
2563             g.select('rect.nv-rangeAvg')
2564                 .attr('height', availableHeight)
2565                 .attr('width', w1(rangeAvg))
2566                 .attr('x', xp1(rangeAvg))
2567                 .datum(rangeAvg)
2568
2569             g.select('rect.nv-rangeMin')
2570                 .attr('height', availableHeight)
2571                 .attr('width', w1(rangeMax))
2572                 .attr('x', xp1(rangeMax))
2573                 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
2574                 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
2575                 .datum(rangeMax > 0 ? rangeMin : rangeMax)
2576
2577             g.select('rect.nv-measure')
2578                 .style('fill', color)
2579                 .attr('height', availableHeight / 3)
2580                 .attr('y', availableHeight / 3)
2581                 .attr('width', measurez < 0 ?
2582                     x1(0) - x1(measurez[0])
2583                     : x1(measurez[0]) - x1(0))
2584                 .attr('x', xp1(measurez))
2585                 .on('mouseover', function() {
2586                     dispatch.elementMouseover({
2587                         value: measurez[0],
2588                         label: measureLabelz[0] || 'Current',
2589                         color: d3.select(this).style("fill")
2590                     })
2591                 })
2592                 .on('mousemove', function() {
2593                     dispatch.elementMousemove({
2594                         value: measurez[0],
2595                         label: measureLabelz[0] || 'Current',
2596                         color: d3.select(this).style("fill")
2597                     })
2598                 })
2599                 .on('mouseout', function() {
2600                     dispatch.elementMouseout({
2601                         value: measurez[0],
2602                         label: measureLabelz[0] || 'Current',
2603                         color: d3.select(this).style("fill")
2604                     })
2605                 });
2606
2607             var h3 =  availableHeight / 6;
2608
2609             var markerData = markerz.map( function(marker, index) {
2610                 return {value: marker, label: markerLabelz[index]}
2611             });
2612             gEnter
2613               .selectAll("path.nv-markerTriangle")
2614               .data(markerData)
2615               .enter()
2616               .append('path')
2617               .attr('class', 'nv-markerTriangle')
2618               .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' })
2619               .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
2620               .on('mouseover', function(d) {
2621                 dispatch.elementMouseover({
2622                   value: d.value,
2623                   label: d.label || 'Previous',
2624                   color: d3.select(this).style("fill"),
2625                   pos: [x1(d.value), availableHeight/2]
2626                 })
2627
2628               })
2629               .on('mousemove', function(d) {
2630                   dispatch.elementMousemove({
2631                       value: d.value,
2632                       label: d.label || 'Previous',
2633                       color: d3.select(this).style("fill")
2634                   })
2635               })
2636               .on('mouseout', function(d, i) {
2637                   dispatch.elementMouseout({
2638                       value: d.value,
2639                       label: d.label || 'Previous',
2640                       color: d3.select(this).style("fill")
2641                   })
2642               });
2643
2644             wrap.selectAll('.nv-range')
2645                 .on('mouseover', function(d,i) {
2646                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2647                     dispatch.elementMouseover({
2648                         value: d,
2649                         label: label,
2650                         color: d3.select(this).style("fill")
2651                     })
2652                 })
2653                 .on('mousemove', function() {
2654                     dispatch.elementMousemove({
2655                         value: measurez[0],
2656                         label: measureLabelz[0] || 'Previous',
2657                         color: d3.select(this).style("fill")
2658                     })
2659                 })
2660                 .on('mouseout', function(d,i) {
2661                     var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2662                     dispatch.elementMouseout({
2663                         value: d,
2664                         label: label,
2665                         color: d3.select(this).style("fill")
2666                     })
2667                 });
2668         });
2669
2670         return chart;
2671     }
2672
2673     //============================================================
2674     // Expose Public Variables
2675     //------------------------------------------------------------
2676
2677     chart.dispatch = dispatch;
2678     chart.options = nv.utils.optionsFunc.bind(chart);
2679
2680     chart._options = Object.create({}, {
2681         // simple options, just get/set the necessary values
2682         ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2683         markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2684         measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2685         forceX:      {get: function(){return forceX;}, set: function(_){forceX=_;}},
2686         width:    {get: function(){return width;}, set: function(_){width=_;}},
2687         height:    {get: function(){return height;}, set: function(_){height=_;}},
2688         tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2689
2690         // options that require extra logic in the setter
2691         margin: {get: function(){return margin;}, set: function(_){
2692             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2693             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2694             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2695             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2696         }},
2697         orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2698             orient = _;
2699             reverse = orient == 'right' || orient == 'bottom';
2700         }},
2701         color:  {get: function(){return color;}, set: function(_){
2702             color = nv.utils.getColor(_);
2703         }}
2704     });
2705
2706     nv.utils.initOptions(chart);
2707     return chart;
2708 };
2709
2710
2711
2712 // Chart design based on the recommendations of Stephen Few. Implementation
2713 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2714 // http://projects.instantcognition.com/protovis/bulletchart/
2715 nv.models.bulletChart = function() {
2716     "use strict";
2717
2718     //============================================================
2719     // Public Variables with Default Settings
2720     //------------------------------------------------------------
2721
2722     var bullet = nv.models.bullet();
2723     var tooltip = nv.models.tooltip();
2724
2725     var orient = 'left' // TODO top & bottom
2726         , reverse = false
2727         , margin = {top: 5, right: 40, bottom: 20, left: 120}
2728         , ranges = function(d) { return d.ranges }
2729         , markers = function(d) { return d.markers ? d.markers : [0] }
2730         , measures = function(d) { return d.measures }
2731         , width = null
2732         , height = 55
2733         , tickFormat = null
2734         , ticks = null
2735         , noData = null
2736         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2737         ;
2738
2739     tooltip.duration(0).headerEnabled(false);
2740
2741     function chart(selection) {
2742         selection.each(function(d, i) {
2743             var container = d3.select(this);
2744             nv.utils.initSVG(container);
2745
2746             var availableWidth = nv.utils.availableWidth(width, container, margin),
2747                 availableHeight = height - margin.top - margin.bottom,
2748                 that = this;
2749
2750             chart.update = function() { chart(selection) };
2751             chart.container = this;
2752
2753             // Display No Data message if there's nothing to show.
2754             if (!d || !ranges.call(this, d, i)) {
2755                 nv.utils.noData(chart, container)
2756                 return chart;
2757             } else {
2758                 container.selectAll('.nv-noData').remove();
2759             }
2760
2761             var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2762                 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2763                 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2764
2765             // Setup containers and skeleton of chart
2766             var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2767             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2768             var gEnter = wrapEnter.append('g');
2769             var g = wrap.select('g');
2770
2771             gEnter.append('g').attr('class', 'nv-bulletWrap');
2772             gEnter.append('g').attr('class', 'nv-titles');
2773
2774             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2775
2776             // Compute the new x-scale.
2777             var x1 = d3.scale.linear()
2778                 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
2779                 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2780
2781             // Retrieve the old x-scale, if this is an update.
2782             var x0 = this.__chart__ || d3.scale.linear()
2783                 .domain([0, Infinity])
2784                 .range(x1.range());
2785
2786             // Stash the new scale.
2787             this.__chart__ = x1;
2788
2789             var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2790                 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2791
2792             var title = gEnter.select('.nv-titles').append('g')
2793                 .attr('text-anchor', 'end')
2794                 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2795             title.append('text')
2796                 .attr('class', 'nv-title')
2797                 .text(function(d) { return d.title; });
2798
2799             title.append('text')
2800                 .attr('class', 'nv-subtitle')
2801                 .attr('dy', '1em')
2802                 .text(function(d) { return d.subtitle; });
2803
2804             bullet
2805                 .width(availableWidth)
2806                 .height(availableHeight)
2807
2808             var bulletWrap = g.select('.nv-bulletWrap');
2809             d3.transition(bulletWrap).call(bullet);
2810
2811             // Compute the tick format.
2812             var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2813
2814             // Update the tick groups.
2815             var tick = g.selectAll('g.nv-tick')
2816                 .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
2817                     return this.textContent || format(d);
2818                 });
2819
2820             // Initialize the ticks with the old scale, x0.
2821             var tickEnter = tick.enter().append('g')
2822                 .attr('class', 'nv-tick')
2823                 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2824                 .style('opacity', 1e-6);
2825
2826             tickEnter.append('line')
2827                 .attr('y1', availableHeight)
2828                 .attr('y2', availableHeight * 7 / 6);
2829
2830             tickEnter.append('text')
2831                 .attr('text-anchor', 'middle')
2832                 .attr('dy', '1em')
2833                 .attr('y', availableHeight * 7 / 6)
2834                 .text(format);
2835
2836             // Transition the updating ticks to the new scale, x1.
2837             var tickUpdate = d3.transition(tick)
2838                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2839                 .style('opacity', 1);
2840
2841             tickUpdate.select('line')
2842                 .attr('y1', availableHeight)
2843                 .attr('y2', availableHeight * 7 / 6);
2844
2845             tickUpdate.select('text')
2846                 .attr('y', availableHeight * 7 / 6);
2847
2848             // Transition the exiting ticks to the new scale, x1.
2849             d3.transition(tick.exit())
2850                 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2851                 .style('opacity', 1e-6)
2852                 .remove();
2853         });
2854
2855         d3.timer.flush();
2856         return chart;
2857     }
2858
2859     //============================================================
2860     // Event Handling/Dispatching (out of chart's scope)
2861     //------------------------------------------------------------
2862
2863     bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
2864         evt['series'] = {
2865             key: evt.label,
2866             value: evt.value,
2867             color: evt.color
2868         };
2869         tooltip.data(evt).hidden(false);
2870     });
2871
2872     bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
2873         tooltip.hidden(true);
2874     });
2875
2876     bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
2877         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2878     });
2879
2880     //============================================================
2881     // Expose Public Variables
2882     //------------------------------------------------------------
2883
2884     chart.bullet = bullet;
2885     chart.dispatch = dispatch;
2886     chart.tooltip = tooltip;
2887
2888     chart.options = nv.utils.optionsFunc.bind(chart);
2889
2890     chart._options = Object.create({}, {
2891         // simple options, just get/set the necessary values
2892         ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2893         markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2894         measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2895         width:    {get: function(){return width;}, set: function(_){width=_;}},
2896         height:    {get: function(){return height;}, set: function(_){height=_;}},
2897         tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2898         ticks:    {get: function(){return ticks;}, set: function(_){ticks=_;}},
2899         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
2900
2901         // deprecated options
2902         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
2903             // deprecated after 1.7.1
2904             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
2905             tooltip.enabled(!!_);
2906         }},
2907         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
2908             // deprecated after 1.7.1
2909             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
2910             tooltip.contentGenerator(_);
2911         }},
2912
2913         // options that require extra logic in the setter
2914         margin: {get: function(){return margin;}, set: function(_){
2915             margin.top    = _.top    !== undefined ? _.top    : margin.top;
2916             margin.right  = _.right  !== undefined ? _.right  : margin.right;
2917             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2918             margin.left   = _.left   !== undefined ? _.left   : margin.left;
2919         }},
2920         orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2921             orient = _;
2922             reverse = orient == 'right' || orient == 'bottom';
2923         }}
2924     });
2925
2926     nv.utils.inheritOptions(chart, bullet);
2927     nv.utils.initOptions(chart);
2928
2929     return chart;
2930 };
2931
2932
2933
2934 nv.models.candlestickBar = function() {
2935     "use strict";
2936
2937     //============================================================
2938     // Public Variables with Default Settings
2939     //------------------------------------------------------------
2940
2941     var margin = {top: 0, right: 0, bottom: 0, left: 0}
2942         , width = null
2943         , height = null
2944         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2945         , container
2946         , x = d3.scale.linear()
2947         , y = d3.scale.linear()
2948         , getX = function(d) { return d.x }
2949         , getY = function(d) { return d.y }
2950         , getOpen = function(d) { return d.open }
2951         , getClose = function(d) { return d.close }
2952         , getHigh = function(d) { return d.high }
2953         , getLow = function(d) { return d.low }
2954         , forceX = []
2955         , forceY = []
2956         , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
2957         , clipEdge = true
2958         , color = nv.utils.defaultColor()
2959         , interactive = false
2960         , xDomain
2961         , yDomain
2962         , xRange
2963         , yRange
2964         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
2965         ;
2966
2967     //============================================================
2968     // Private Variables
2969     //------------------------------------------------------------
2970
2971     function chart(selection) {
2972         selection.each(function(data) {
2973             container = d3.select(this);
2974             var availableWidth = nv.utils.availableWidth(width, container, margin),
2975                 availableHeight = nv.utils.availableHeight(height, container, margin);
2976
2977             nv.utils.initSVG(container);
2978
2979             // Width of the candlestick bars.
2980             var barWidth = (availableWidth / data[0].values.length) * .45;
2981
2982             // Setup Scales
2983             x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
2984
2985             if (padData)
2986                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
2987             else
2988                 x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
2989
2990             y.domain(yDomain || [
2991                     d3.min(data[0].values.map(getLow).concat(forceY)),
2992                     d3.max(data[0].values.map(getHigh).concat(forceY))
2993                 ]
2994             ).range(yRange || [availableHeight, 0]);
2995
2996             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
2997             if (x.domain()[0] === x.domain()[1])
2998                 x.domain()[0] ?
2999                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3000                     : x.domain([-1,1]);
3001
3002             if (y.domain()[0] === y.domain()[1])
3003                 y.domain()[0] ?
3004                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3005                     : y.domain([-1,1]);
3006
3007             // Setup containers and skeleton of chart
3008             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
3009             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
3010             var defsEnter = wrapEnter.append('defs');
3011             var gEnter = wrapEnter.append('g');
3012             var g = wrap.select('g');
3013
3014             gEnter.append('g').attr('class', 'nv-ticks');
3015
3016             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3017
3018             container
3019                 .on('click', function(d,i) {
3020                     dispatch.chartClick({
3021                         data: d,
3022                         index: i,
3023                         pos: d3.event,
3024                         id: id
3025                     });
3026                 });
3027
3028             defsEnter.append('clipPath')
3029                 .attr('id', 'nv-chart-clip-path-' + id)
3030                 .append('rect');
3031
3032             wrap.select('#nv-chart-clip-path-' + id + ' rect')
3033                 .attr('width', availableWidth)
3034                 .attr('height', availableHeight);
3035
3036             g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3037
3038             var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
3039                 .data(function(d) { return d });
3040             ticks.exit().remove();
3041
3042             // The colors are currently controlled by CSS.
3043             var tickGroups = ticks.enter().append('g')
3044                 .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
3045
3046             var lines = tickGroups.append('line')
3047                 .attr('class', 'nv-candlestick-lines')
3048                 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
3049                 .attr('x1', 0)
3050                 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3051                 .attr('x2', 0)
3052                 .attr('y2', function(d, i) { return y(getLow(d, i)); });
3053
3054             var rects = tickGroups.append('rect')
3055                 .attr('class', 'nv-candlestick-rects nv-bars')
3056                 .attr('transform', function(d, i) {
3057                     return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
3058                     + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
3059                     + ')';
3060                 })
3061                 .attr('x', 0)
3062                 .attr('y', 0)
3063                 .attr('width', barWidth)
3064                 .attr('height', function(d, i) {
3065                     var open = getOpen(d, i);
3066                     var close = getClose(d, i);
3067                     return open > close ? y(close) - y(open) : y(open) - y(close);
3068                 });
3069
3070             container.selectAll('.nv-candlestick-lines').transition()
3071                 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
3072                 .attr('x1', 0)
3073                 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3074                 .attr('x2', 0)
3075                 .attr('y2', function(d, i) { return y(getLow(d, i)); });
3076
3077             container.selectAll('.nv-candlestick-rects').transition()
3078                 .attr('transform', function(d, i) {
3079                     return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
3080                     + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
3081                     + ')';
3082                 })
3083                 .attr('x', 0)
3084                 .attr('y', 0)
3085                 .attr('width', barWidth)
3086                 .attr('height', function(d, i) {
3087                     var open = getOpen(d, i);
3088                     var close = getClose(d, i);
3089                     return open > close ? y(close) - y(open) : y(open) - y(close);
3090                 });
3091         });
3092
3093         return chart;
3094     }
3095
3096
3097     //Create methods to allow outside functions to highlight a specific bar.
3098     chart.highlightPoint = function(pointIndex, isHoverOver) {
3099         chart.clearHighlights();
3100         container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
3101             .classed("hover", isHoverOver)
3102         ;
3103     };
3104
3105     chart.clearHighlights = function() {
3106         container.select(".nv-candlestickBar .nv-tick.hover")
3107             .classed("hover", false)
3108         ;
3109     };
3110
3111     //============================================================
3112     // Expose Public Variables
3113     //------------------------------------------------------------
3114
3115     chart.dispatch = dispatch;
3116     chart.options = nv.utils.optionsFunc.bind(chart);
3117
3118     chart._options = Object.create({}, {
3119         // simple options, just get/set the necessary values
3120         width:    {get: function(){return width;}, set: function(_){width=_;}},
3121         height:   {get: function(){return height;}, set: function(_){height=_;}},
3122         xScale:   {get: function(){return x;}, set: function(_){x=_;}},
3123         yScale:   {get: function(){return y;}, set: function(_){y=_;}},
3124         xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3125         yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3126         xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
3127         yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
3128         forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
3129         forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
3130         padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
3131         clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
3132         id:       {get: function(){return id;}, set: function(_){id=_;}},
3133         interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
3134
3135         x:     {get: function(){return getX;}, set: function(_){getX=_;}},
3136         y:     {get: function(){return getY;}, set: function(_){getY=_;}},
3137         open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
3138         close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
3139         high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
3140         low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},
3141
3142         // options that require extra logic in the setter
3143         margin: {get: function(){return margin;}, set: function(_){
3144             margin.top    = _.top    != undefined ? _.top    : margin.top;
3145             margin.right  = _.right  != undefined ? _.right  : margin.right;
3146             margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
3147             margin.left   = _.left   != undefined ? _.left   : margin.left;
3148         }},
3149         color:  {get: function(){return color;}, set: function(_){
3150             color = nv.utils.getColor(_);
3151         }}
3152     });
3153
3154     nv.utils.initOptions(chart);
3155     return chart;
3156 };
3157
3158 nv.models.cumulativeLineChart = function() {
3159     "use strict";
3160
3161     //============================================================
3162     // Public Variables with Default Settings
3163     //------------------------------------------------------------
3164
3165     var lines = nv.models.line()
3166         , xAxis = nv.models.axis()
3167         , yAxis = nv.models.axis()
3168         , legend = nv.models.legend()
3169         , controls = nv.models.legend()
3170         , interactiveLayer = nv.interactiveGuideline()
3171         , tooltip = nv.models.tooltip()
3172         ;
3173
3174     var margin = {top: 30, right: 30, bottom: 50, left: 60}
3175         , color = nv.utils.defaultColor()
3176         , width = null
3177         , height = null
3178         , showLegend = true
3179         , showXAxis = true
3180         , showYAxis = true
3181         , rightAlignYAxis = false
3182         , showControls = true
3183         , useInteractiveGuideline = false
3184         , rescaleY = true
3185         , x //can be accessed via chart.xScale()
3186         , y //can be accessed via chart.yScale()
3187         , id = lines.id()
3188         , state = nv.utils.state()
3189         , defaultState = null
3190         , noData = null
3191         , average = function(d) { return d.average }
3192         , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
3193         , transitionDuration = 250
3194         , duration = 250
3195         , noErrorCheck = false  //if set to TRUE, will bypass an error check in the indexify function.
3196         ;
3197
3198     state.index = 0;
3199     state.rescaleY = rescaleY;
3200
3201     xAxis.orient('bottom').tickPadding(7);
3202     yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
3203
3204     tooltip.valueFormatter(function(d, i) {
3205         return yAxis.tickFormat()(d, i);
3206     }).headerFormatter(function(d, i) {
3207         return xAxis.tickFormat()(d, i);
3208     });
3209
3210     controls.updateState(false);
3211
3212     //============================================================
3213     // Private Variables
3214     //------------------------------------------------------------
3215
3216     var dx = d3.scale.linear()
3217         , index = {i: 0, x: 0}
3218         , renderWatch = nv.utils.renderWatch(dispatch, duration)
3219         ;
3220
3221     var stateGetter = function(data) {
3222         return function(){
3223             return {
3224                 active: data.map(function(d) { return !d.disabled }),
3225                 index: index.i,
3226                 rescaleY: rescaleY
3227             };
3228         }
3229     };
3230
3231     var stateSetter = function(data) {
3232         return function(state) {
3233             if (state.index !== undefined)
3234                 index.i = state.index;
3235             if (state.rescaleY !== undefined)
3236                 rescaleY = state.rescaleY;
3237             if (state.active !== undefined)
3238                 data.forEach(function(series,i) {
3239                     series.disabled = !state.active[i];
3240                 });
3241         }
3242     };
3243
3244     function chart(selection) {
3245         renderWatch.reset();
3246         renderWatch.models(lines);
3247         if (showXAxis) renderWatch.models(xAxis);
3248         if (showYAxis) renderWatch.models(yAxis);
3249         selection.each(function(data) {
3250             var container = d3.select(this);
3251             nv.utils.initSVG(container);
3252             container.classed('nv-chart-' + id, true);
3253             var that = this;
3254
3255             var availableWidth = nv.utils.availableWidth(width, container, margin),
3256                 availableHeight = nv.utils.availableHeight(height, container, margin);
3257
3258             chart.update = function() {
3259                 if (duration === 0)
3260                     container.call(chart);
3261                 else
3262                     container.transition().duration(duration).call(chart)
3263             };
3264             chart.container = this;
3265
3266             state
3267                 .setter(stateSetter(data), chart.update)
3268                 .getter(stateGetter(data))
3269                 .update();
3270
3271             // DEPRECATED set state.disableddisabled
3272             state.disabled = data.map(function(d) { return !!d.disabled });
3273
3274             if (!defaultState) {
3275                 var key;
3276                 defaultState = {};
3277                 for (key in state) {
3278                     if (state[key] instanceof Array)
3279                         defaultState[key] = state[key].slice(0);
3280                     else
3281                         defaultState[key] = state[key];
3282                 }
3283             }
3284
3285             var indexDrag = d3.behavior.drag()
3286                 .on('dragstart', dragStart)
3287                 .on('drag', dragMove)
3288                 .on('dragend', dragEnd);
3289
3290
3291             function dragStart(d,i) {
3292                 d3.select(chart.container)
3293                     .style('cursor', 'ew-resize');
3294             }
3295
3296             function dragMove(d,i) {
3297                 index.x = d3.event.x;
3298                 index.i = Math.round(dx.invert(index.x));
3299                 updateZero();
3300             }
3301
3302             function dragEnd(d,i) {
3303                 d3.select(chart.container)
3304                     .style('cursor', 'auto');
3305
3306                 // update state and send stateChange with new index
3307                 state.index = index.i;
3308                 dispatch.stateChange(state);
3309             }
3310
3311             // Display No Data message if there's nothing to show.
3312             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3313                 nv.utils.noData(chart, container)
3314                 return chart;
3315             } else {
3316                 container.selectAll('.nv-noData').remove();
3317             }
3318
3319             // Setup Scales
3320             x = lines.xScale();
3321             y = lines.yScale();
3322
3323             if (!rescaleY) {
3324                 var seriesDomains = data
3325                     .filter(function(series) { return !series.disabled })
3326                     .map(function(series,i) {
3327                         var initialDomain = d3.extent(series.values, lines.y());
3328
3329                         //account for series being disabled when losing 95% or more
3330                         if (initialDomain[0] < -.95) initialDomain[0] = -.95;
3331
3332                         return [
3333                                 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
3334                                 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
3335                         ];
3336                     });
3337
3338                 var completeDomain = [
3339                     d3.min(seriesDomains, function(d) { return d[0] }),
3340                     d3.max(seriesDomains, function(d) { return d[1] })
3341                 ];
3342
3343                 lines.yDomain(completeDomain);
3344             } else {
3345                 lines.yDomain(null);
3346             }
3347
3348             dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
3349                 .range([0, availableWidth])
3350                 .clamp(true);
3351
3352             var data = indexify(index.i, data);
3353
3354             // Setup containers and skeleton of chart
3355             var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
3356             var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
3357             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
3358             var g = wrap.select('g');
3359
3360             gEnter.append('g').attr('class', 'nv-interactive');
3361             gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
3362             gEnter.append('g').attr('class', 'nv-y nv-axis');
3363             gEnter.append('g').attr('class', 'nv-background');
3364             gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
3365             gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
3366             gEnter.append('g').attr('class', 'nv-legendWrap');
3367             gEnter.append('g').attr('class', 'nv-controlsWrap');
3368
3369             // Legend
3370             if (showLegend) {
3371                 legend.width(availableWidth);
3372
3373                 g.select('.nv-legendWrap')
3374                     .datum(data)
3375                     .call(legend);
3376
3377                 if ( margin.top != legend.height()) {
3378                     margin.top = legend.height();
3379                     availableHeight = nv.utils.availableHeight(height, container, margin);
3380                 }
3381
3382                 g.select('.nv-legendWrap')
3383                     .attr('transform', 'translate(0,' + (-margin.top) +')')
3384             }
3385
3386             // Controls
3387             if (showControls) {
3388                 var controlsData = [
3389                     { key: 'Re-scale y-axis', disabled: !rescaleY }
3390                 ];
3391
3392                 controls
3393                     .width(140)
3394                     .color(['#444', '#444', '#444'])
3395                     .rightAlign(false)
3396                     .margin({top: 5, right: 0, bottom: 5, left: 20})
3397                 ;
3398
3399                 g.select('.nv-controlsWrap')
3400                     .datum(controlsData)
3401                     .attr('transform', 'translate(0,' + (-margin.top) +')')
3402                     .call(controls);
3403             }
3404
3405             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3406
3407             if (rightAlignYAxis) {
3408                 g.select(".nv-y.nv-axis")
3409                     .attr("transform", "translate(" + availableWidth + ",0)");
3410             }
3411
3412             // Show error if series goes below 100%
3413             var tempDisabled = data.filter(function(d) { return d.tempDisabled });
3414
3415             wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
3416             if (tempDisabled.length) {
3417                 wrap.append('text').attr('class', 'tempDisabled')
3418                     .attr('x', availableWidth / 2)
3419                     .attr('y', '-.71em')
3420                     .style('text-anchor', 'end')
3421                     .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
3422             }
3423
3424             //Set up interactive layer
3425             if (useInteractiveGuideline) {
3426                 interactiveLayer
3427                     .width(availableWidth)
3428                     .height(availableHeight)
3429                     .margin({left:margin.left,top:margin.top})
3430                     .svgContainer(container)
3431                     .xScale(x);
3432                 wrap.select(".nv-interactive").call(interactiveLayer);
3433             }
3434
3435             gEnter.select('.nv-background')
3436                 .append('rect');
3437
3438             g.select('.nv-background rect')
3439                 .attr('width', availableWidth)
3440                 .attr('height', availableHeight);
3441
3442             lines
3443                 //.x(function(d) { return d.x })
3444                 .y(function(d) { return d.display.y })
3445                 .width(availableWidth)
3446                 .height(availableHeight)
3447                 .color(data.map(function(d,i) {
3448                     return d.color || color(d, i);
3449                 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
3450
3451             var linesWrap = g.select('.nv-linesWrap')
3452                 .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
3453
3454             linesWrap.call(lines);
3455
3456             //Store a series index number in the data array.
3457             data.forEach(function(d,i) {
3458                 d.seriesIndex = i;
3459             });
3460
3461             var avgLineData = data.filter(function(d) {
3462                 return !d.disabled && !!average(d);
3463             });
3464
3465             var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
3466                 .data(avgLineData, function(d) { return d.key; });
3467
3468             var getAvgLineY = function(d) {
3469                 //If average lines go off the svg element, clamp them to the svg bounds.
3470                 var yVal = y(average(d));
3471                 if (yVal < 0) return 0;
3472                 if (yVal > availableHeight) return availableHeight;
3473                 return yVal;
3474             };
3475
3476             avgLines.enter()
3477                 .append('line')
3478                 .style('stroke-width',2)
3479                 .style('stroke-dasharray','10,10')
3480                 .style('stroke',function (d,i) {
3481                     return lines.color()(d,d.seriesIndex);
3482                 })
3483                 .attr('x1',0)
3484                 .attr('x2',availableWidth)
3485                 .attr('y1', getAvgLineY)
3486                 .attr('y2', getAvgLineY);
3487
3488             avgLines
3489                 .style('stroke-opacity',function(d){
3490                     //If average lines go offscreen, make them transparent
3491                     var yVal = y(average(d));
3492                     if (yVal < 0 || yVal > availableHeight) return 0;
3493                     return 1;
3494                 })
3495                 .attr('x1',0)
3496                 .attr('x2',availableWidth)
3497                 .attr('y1', getAvgLineY)
3498                 .attr('y2', getAvgLineY);
3499
3500             avgLines.exit().remove();
3501
3502             //Create index line
3503             var indexLine = linesWrap.selectAll('.nv-indexLine')
3504                 .data([index]);
3505             indexLine.enter().append('rect').attr('class', 'nv-indexLine')
3506                 .attr('width', 3)
3507                 .attr('x', -2)
3508                 .attr('fill', 'red')
3509                 .attr('fill-opacity', .5)
3510                 .style("pointer-events","all")
3511                 .call(indexDrag);
3512
3513             indexLine
3514                 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
3515                 .attr('height', availableHeight);
3516
3517             // Setup Axes
3518             if (showXAxis) {
3519                 xAxis
3520                     .scale(x)
3521                     ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
3522                     .tickSize(-availableHeight, 0);
3523
3524                 g.select('.nv-x.nv-axis')
3525                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
3526                 g.select('.nv-x.nv-axis')
3527                     .call(xAxis);
3528             }
3529
3530             if (showYAxis) {
3531                 yAxis
3532                     .scale(y)
3533                     ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3534                     .tickSize( -availableWidth, 0);
3535
3536                 g.select('.nv-y.nv-axis')
3537                     .call(yAxis);
3538             }
3539
3540             //============================================================
3541             // Event Handling/Dispatching (in chart's scope)
3542             //------------------------------------------------------------
3543
3544             function updateZero() {
3545                 indexLine
3546                     .data([index]);
3547
3548                 //When dragging the index line, turn off line transitions.
3549                 // Then turn them back on when done dragging.
3550                 var oldDuration = chart.duration();
3551                 chart.duration(0);
3552                 chart.update();
3553                 chart.duration(oldDuration);
3554             }
3555
3556             g.select('.nv-background rect')
3557                 .on('click', function() {
3558                     index.x = d3.mouse(this)[0];
3559                     index.i = Math.round(dx.invert(index.x));
3560
3561                     // update state and send stateChange with new index
3562                     state.index = index.i;
3563                     dispatch.stateChange(state);
3564
3565                     updateZero();
3566                 });
3567
3568             lines.dispatch.on('elementClick', function(e) {
3569                 index.i = e.pointIndex;
3570                 index.x = dx(index.i);
3571
3572                 // update state and send stateChange with new index
3573                 state.index = index.i;
3574                 dispatch.stateChange(state);
3575
3576                 updateZero();
3577             });
3578
3579             controls.dispatch.on('legendClick', function(d,i) {
3580                 d.disabled = !d.disabled;
3581                 rescaleY = !d.disabled;
3582
3583                 state.rescaleY = rescaleY;
3584                 dispatch.stateChange(state);
3585                 chart.update();
3586             });
3587
3588             legend.dispatch.on('stateChange', function(newState) {
3589                 for (var key in newState)
3590                     state[key] = newState[key];
3591                 dispatch.stateChange(state);
3592                 chart.update();
3593             });
3594
3595             interactiveLayer.dispatch.on('elementMousemove', function(e) {
3596                 lines.clearHighlights();
3597                 var singlePoint, pointIndex, pointXLocation, allData = [];
3598
3599                 data
3600                     .filter(function(series, i) {
3601                         series.seriesIndex = i;
3602                         return !series.disabled;
3603                     })
3604                     .forEach(function(series,i) {
3605                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
3606                         lines.highlightPoint(i, pointIndex, true);
3607                         var point = series.values[pointIndex];
3608                         if (typeof point === 'undefined') return;
3609                         if (typeof singlePoint === 'undefined') singlePoint = point;
3610                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
3611                         allData.push({
3612                             key: series.key,
3613                             value: chart.y()(point, pointIndex),
3614                             color: color(series,series.seriesIndex)
3615                         });
3616                     });
3617
3618                 //Highlight the tooltip entry based on which point the mouse is closest to.
3619                 if (allData.length > 2) {
3620                     var yValue = chart.yScale().invert(e.mouseY);
3621                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
3622                     var threshold = 0.03 * domainExtent;
3623                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
3624                     if (indexToHighlight !== null)
3625                         allData[indexToHighlight].highlight = true;
3626                 }
3627
3628                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
3629                 interactiveLayer.tooltip
3630                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
3631                     .chartContainer(that.parentNode)
3632                     .valueFormatter(function(d,i) {
3633                         return yAxis.tickFormat()(d);
3634                     })
3635                     .data(
3636                     {
3637                         value: xValue,
3638                         series: allData
3639                     }
3640                 )();
3641
3642                 interactiveLayer.renderGuideLine(pointXLocation);
3643             });
3644
3645             interactiveLayer.dispatch.on("elementMouseout",function(e) {
3646                 lines.clearHighlights();
3647             });
3648
3649             // Update chart from a state object passed to event handler
3650             dispatch.on('changeState', function(e) {
3651                 if (typeof e.disabled !== 'undefined') {
3652                     data.forEach(function(series,i) {
3653                         series.disabled = e.disabled[i];
3654                     });
3655
3656                     state.disabled = e.disabled;
3657                 }
3658
3659                 if (typeof e.index !== 'undefined') {
3660                     index.i = e.index;
3661                     index.x = dx(index.i);
3662
3663                     state.index = e.index;
3664
3665                     indexLine
3666                         .data([index]);
3667                 }
3668
3669                 if (typeof e.rescaleY !== 'undefined') {
3670                     rescaleY = e.rescaleY;
3671                 }
3672
3673                 chart.update();
3674             });
3675
3676         });
3677
3678         renderWatch.renderEnd('cumulativeLineChart immediate');
3679
3680         return chart;
3681     }
3682
3683     //============================================================
3684     // Event Handling/Dispatching (out of chart's scope)
3685     //------------------------------------------------------------
3686
3687     lines.dispatch.on('elementMouseover.tooltip', function(evt) {
3688         var point = {
3689             x: chart.x()(evt.point),
3690             y: chart.y()(evt.point),
3691             color: evt.point.color
3692         };
3693         evt.point = point;
3694         tooltip.data(evt).position(evt.pos).hidden(false);
3695     });
3696
3697     lines.dispatch.on('elementMouseout.tooltip', function(evt) {
3698         tooltip.hidden(true)
3699     });
3700
3701     //============================================================
3702     // Functions
3703     //------------------------------------------------------------
3704
3705     var indexifyYGetter = null;
3706     /* Normalize the data according to an index point. */
3707     function indexify(idx, data) {
3708         if (!indexifyYGetter) indexifyYGetter = lines.y();
3709         return data.map(function(line, i) {
3710             if (!line.values) {
3711                 return line;
3712             }
3713             var indexValue = line.values[idx];
3714             if (indexValue == null) {
3715                 return line;
3716             }
3717             var v = indexifyYGetter(indexValue, idx);
3718
3719             //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
3720             if (v < -.95 && !noErrorCheck) {
3721                 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
3722
3723                 line.tempDisabled = true;
3724                 return line;
3725             }
3726
3727             line.tempDisabled = false;
3728
3729             line.values = line.values.map(function(point, pointIndex) {
3730                 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3731                 return point;
3732             });
3733
3734             return line;
3735         })
3736     }
3737
3738     //============================================================
3739     // Expose Public Variables
3740     //------------------------------------------------------------
3741
3742     // expose chart's sub-components
3743     chart.dispatch = dispatch;
3744     chart.lines = lines;
3745     chart.legend = legend;
3746     chart.controls = controls;
3747     chart.xAxis = xAxis;
3748     chart.yAxis = yAxis;
3749     chart.interactiveLayer = interactiveLayer;
3750     chart.state = state;
3751     chart.tooltip = tooltip;
3752
3753     chart.options = nv.utils.optionsFunc.bind(chart);
3754
3755     chart._options = Object.create({}, {
3756         // simple options, just get/set the necessary values
3757         width:      {get: function(){return width;}, set: function(_){width=_;}},
3758         height:     {get: function(){return height;}, set: function(_){height=_;}},
3759         rescaleY:     {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
3760         showControls:     {get: function(){return showControls;}, set: function(_){showControls=_;}},
3761         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
3762         average: {get: function(){return average;}, set: function(_){average=_;}},
3763         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
3764         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
3765         showXAxis:    {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
3766         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
3767         noErrorCheck:    {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
3768
3769         // deprecated options
3770         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
3771             // deprecated after 1.7.1
3772             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
3773             tooltip.enabled(!!_);
3774         }},
3775         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
3776             // deprecated after 1.7.1
3777             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
3778             tooltip.contentGenerator(_);
3779         }},
3780
3781         // options that require extra logic in the setter
3782         margin: {get: function(){return margin;}, set: function(_){
3783             margin.top    = _.top    !== undefined ? _.top    : margin.top;
3784             margin.right  = _.right  !== undefined ? _.right  : margin.right;
3785             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3786             margin.left   = _.left   !== undefined ? _.left   : margin.left;
3787         }},
3788         color:  {get: function(){return color;}, set: function(_){
3789             color = nv.utils.getColor(_);
3790             legend.color(color);
3791         }},
3792         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
3793             useInteractiveGuideline = _;
3794             if (_ === true) {
3795                 chart.interactive(false);
3796                 chart.useVoronoi(false);
3797             }
3798         }},
3799         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
3800             rightAlignYAxis = _;
3801             yAxis.orient( (_) ? 'right' : 'left');
3802         }},
3803         duration:    {get: function(){return duration;}, set: function(_){
3804             duration = _;
3805             lines.duration(duration);
3806             xAxis.duration(duration);
3807             yAxis.duration(duration);
3808             renderWatch.reset(duration);
3809         }}
3810     });
3811
3812     nv.utils.inheritOptions(chart, lines);
3813     nv.utils.initOptions(chart);
3814
3815     return chart;
3816 };
3817 //TODO: consider deprecating by adding necessary features to multiBar model
3818 nv.models.discreteBar = function() {
3819     "use strict";
3820
3821     //============================================================
3822     // Public Variables with Default Settings
3823     //------------------------------------------------------------
3824
3825     var margin = {top: 0, right: 0, bottom: 0, left: 0}
3826         , width = 960
3827         , height = 500
3828         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3829         , container
3830         , x = d3.scale.ordinal()
3831         , y = d3.scale.linear()
3832         , getX = function(d) { return d.x }
3833         , getY = function(d) { return d.y }
3834         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
3835         , color = nv.utils.defaultColor()
3836         , showValues = false
3837         , valueFormat = d3.format(',.2f')
3838         , xDomain
3839         , yDomain
3840         , xRange
3841         , yRange
3842         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
3843         , rectClass = 'discreteBar'
3844         , duration = 250
3845         ;
3846
3847     //============================================================
3848     // Private Variables
3849     //------------------------------------------------------------
3850
3851     var x0, y0;
3852     var renderWatch = nv.utils.renderWatch(dispatch, duration);
3853
3854     function chart(selection) {
3855         renderWatch.reset();
3856         selection.each(function(data) {
3857             var availableWidth = width - margin.left - margin.right,
3858                 availableHeight = height - margin.top - margin.bottom;
3859
3860             container = d3.select(this);
3861             nv.utils.initSVG(container);
3862
3863             //add series index to each data point for reference
3864             data.forEach(function(series, i) {
3865                 series.values.forEach(function(point) {
3866                     point.series = i;
3867                 });
3868             });
3869
3870             // Setup Scales
3871             // remap and flatten the data for use in calculating the scales' domains
3872             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
3873                 data.map(function(d) {
3874                     return d.values.map(function(d,i) {
3875                         return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
3876                     })
3877                 });
3878
3879             x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
3880                 .rangeBands(xRange || [0, availableWidth], .1);
3881             y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3882
3883             // If showValues, pad the Y axis range to account for label height
3884             if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3885             else y.range(yRange || [availableHeight, 0]);
3886
3887             //store old scales if they exist
3888             x0 = x0 || x;
3889             y0 = y0 || y.copy().range([y(0),y(0)]);
3890
3891             // Setup containers and skeleton of chart
3892             var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3893             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3894             var gEnter = wrapEnter.append('g');
3895             var g = wrap.select('g');
3896
3897             gEnter.append('g').attr('class', 'nv-groups');
3898             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3899
3900             //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3901             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3902                 .data(function(d) { return d }, function(d) { return d.key });
3903             groups.enter().append('g')
3904                 .style('stroke-opacity', 1e-6)
3905                 .style('fill-opacity', 1e-6);
3906             groups.exit()
3907                 .watchTransition(renderWatch, 'discreteBar: exit groups')
3908                 .style('stroke-opacity', 1e-6)
3909                 .style('fill-opacity', 1e-6)
3910                 .remove();
3911             groups
3912                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3913                 .classed('hover', function(d) { return d.hover });
3914             groups
3915                 .watchTransition(renderWatch, 'discreteBar: groups')
3916                 .style('stroke-opacity', 1)
3917                 .style('fill-opacity', .75);
3918
3919             var bars = groups.selectAll('g.nv-bar')
3920                 .data(function(d) { return d.values });
3921             bars.exit().remove();
3922
3923             var barsEnter = bars.enter().append('g')
3924                 .attr('transform', function(d,i,j) {
3925                     return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3926                 })
3927                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3928                     d3.select(this).classed('hover', true);
3929                     dispatch.elementMouseover({
3930                         data: d,
3931                         index: i,
3932                         color: d3.select(this).style("fill")
3933                     });
3934                 })
3935                 .on('mouseout', function(d,i) {
3936                     d3.select(this).classed('hover', false);
3937                     dispatch.elementMouseout({
3938                         data: d,
3939                         index: i,
3940                         color: d3.select(this).style("fill")
3941                     });
3942                 })
3943                 .on('mousemove', function(d,i) {
3944                     dispatch.elementMousemove({
3945                         data: d,
3946                         index: i,
3947                         color: d3.select(this).style("fill")
3948                     });
3949                 })
3950                 .on('click', function(d,i) {
3951                     dispatch.elementClick({
3952                         data: d,
3953                         index: i,
3954                         color: d3.select(this).style("fill")
3955                     });
3956                     d3.event.stopPropagation();
3957                 })
3958                 .on('dblclick', function(d,i) {
3959                     dispatch.elementDblClick({
3960                         data: d,
3961                         index: i,
3962                         color: d3.select(this).style("fill")
3963                     });
3964                     d3.event.stopPropagation();
3965                 });
3966
3967             barsEnter.append('rect')
3968                 .attr('height', 0)
3969                 .attr('width', x.rangeBand() * .9 / data.length )
3970
3971             if (showValues) {
3972                 barsEnter.append('text')
3973                     .attr('text-anchor', 'middle')
3974                 ;
3975
3976                 bars.select('text')
3977                     .text(function(d,i) { return valueFormat(getY(d,i)) })
3978                     .watchTransition(renderWatch, 'discreteBar: bars text')
3979                     .attr('x', x.rangeBand() * .9 / 2)
3980                     .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3981
3982                 ;
3983             } else {
3984                 bars.selectAll('text').remove();
3985             }
3986
3987             bars
3988                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3989                 .style('fill', function(d,i) { return d.color || color(d,i) })
3990                 .style('stroke', function(d,i) { return d.color || color(d,i) })
3991                 .select('rect')
3992                 .attr('class', rectClass)
3993                 .watchTransition(renderWatch, 'discreteBar: bars rect')
3994                 .attr('width', x.rangeBand() * .9 / data.length);
3995             bars.watchTransition(renderWatch, 'discreteBar: bars')
3996                 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3997                 .attr('transform', function(d,i) {
3998                     var left = x(getX(d,i)) + x.rangeBand() * .05,
3999                         top = getY(d,i) < 0 ?
4000                             y(0) :
4001                                 y(0) - y(getY(d,i)) < 1 ?
4002                             y(0) - 1 : //make 1 px positive bars show up above y=0
4003                             y(getY(d,i));
4004
4005                     return 'translate(' + left + ', ' + top + ')'
4006                 })
4007                 .select('rect')
4008                 .attr('height', function(d,i) {
4009                     return  Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
4010                 });
4011
4012
4013             //store old scales for use in transitions on update
4014             x0 = x.copy();
4015             y0 = y.copy();
4016
4017         });
4018
4019         renderWatch.renderEnd('discreteBar immediate');
4020         return chart;
4021     }
4022
4023     //============================================================
4024     // Expose Public Variables
4025     //------------------------------------------------------------
4026
4027     chart.dispatch = dispatch;
4028     chart.options = nv.utils.optionsFunc.bind(chart);
4029
4030     chart._options = Object.create({}, {
4031         // simple options, just get/set the necessary values
4032         width:   {get: function(){return width;}, set: function(_){width=_;}},
4033         height:  {get: function(){return height;}, set: function(_){height=_;}},
4034         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
4035         showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
4036         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
4037         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
4038         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
4039         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
4040         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
4041         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
4042         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
4043         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
4044         valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
4045         id:          {get: function(){return id;}, set: function(_){id=_;}},
4046         rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
4047
4048         // options that require extra logic in the setter
4049         margin: {get: function(){return margin;}, set: function(_){
4050             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4051             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4052             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4053             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4054         }},
4055         color:  {get: function(){return color;}, set: function(_){
4056             color = nv.utils.getColor(_);
4057         }},
4058         duration: {get: function(){return duration;}, set: function(_){
4059             duration = _;
4060             renderWatch.reset(duration);
4061         }}
4062     });
4063
4064     nv.utils.initOptions(chart);
4065
4066     return chart;
4067 };
4068
4069 nv.models.discreteBarChart = function() {
4070     "use strict";
4071
4072     //============================================================
4073     // Public Variables with Default Settings
4074     //------------------------------------------------------------
4075
4076     var discretebar = nv.models.discreteBar()
4077         , xAxis = nv.models.axis()
4078         , yAxis = nv.models.axis()
4079         , tooltip = nv.models.tooltip()
4080         ;
4081
4082     var margin = {top: 15, right: 10, bottom: 50, left: 60}
4083         , width = null
4084         , height = null
4085         , color = nv.utils.getColor()
4086         , showXAxis = true
4087         , showYAxis = true
4088         , rightAlignYAxis = false
4089         , staggerLabels = false
4090         , x
4091         , y
4092         , noData = null
4093         , dispatch = d3.dispatch('beforeUpdate','renderEnd')
4094         , duration = 250
4095         ;
4096
4097     xAxis
4098         .orient('bottom')
4099         .showMaxMin(false)
4100         .tickFormat(function(d) { return d })
4101     ;
4102     yAxis
4103         .orient((rightAlignYAxis) ? 'right' : 'left')
4104         .tickFormat(d3.format(',.1f'))
4105     ;
4106
4107     tooltip
4108         .duration(0)
4109         .headerEnabled(false)
4110         .valueFormatter(function(d, i) {
4111             return yAxis.tickFormat()(d, i);
4112         })
4113         .keyFormatter(function(d, i) {
4114             return xAxis.tickFormat()(d, i);
4115         });
4116
4117     //============================================================
4118     // Private Variables
4119     //------------------------------------------------------------
4120
4121     var renderWatch = nv.utils.renderWatch(dispatch, duration);
4122
4123     function chart(selection) {
4124         renderWatch.reset();
4125         renderWatch.models(discretebar);
4126         if (showXAxis) renderWatch.models(xAxis);
4127         if (showYAxis) renderWatch.models(yAxis);
4128
4129         selection.each(function(data) {
4130             var container = d3.select(this),
4131                 that = this;
4132             nv.utils.initSVG(container);
4133             var availableWidth = nv.utils.availableWidth(width, container, margin),
4134                 availableHeight = nv.utils.availableHeight(height, container, margin);
4135
4136             chart.update = function() {
4137                 dispatch.beforeUpdate();
4138                 container.transition().duration(duration).call(chart);
4139             };
4140             chart.container = this;
4141
4142             // Display No Data message if there's nothing to show.
4143             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4144                 nv.utils.noData(chart, container);
4145                 return chart;
4146             } else {
4147                 container.selectAll('.nv-noData').remove();
4148             }
4149
4150             // Setup Scales
4151             x = discretebar.xScale();
4152             y = discretebar.yScale().clamp(true);
4153
4154             // Setup containers and skeleton of chart
4155             var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
4156             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
4157             var defsEnter = gEnter.append('defs');
4158             var g = wrap.select('g');
4159
4160             gEnter.append('g').attr('class', 'nv-x nv-axis');
4161             gEnter.append('g').attr('class', 'nv-y nv-axis')
4162                 .append('g').attr('class', 'nv-zeroLine')
4163                 .append('line');
4164
4165             gEnter.append('g').attr('class', 'nv-barsWrap');
4166
4167             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4168
4169             if (rightAlignYAxis) {
4170                 g.select(".nv-y.nv-axis")
4171                     .attr("transform", "translate(" + availableWidth + ",0)");
4172             }
4173
4174             // Main Chart Component(s)
4175             discretebar
4176                 .width(availableWidth)
4177                 .height(availableHeight);
4178
4179             var barsWrap = g.select('.nv-barsWrap')
4180                 .datum(data.filter(function(d) { return !d.disabled }));
4181
4182             barsWrap.transition().call(discretebar);
4183
4184
4185             defsEnter.append('clipPath')
4186                 .attr('id', 'nv-x-label-clip-' + discretebar.id())
4187                 .append('rect');
4188
4189             g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
4190                 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
4191                 .attr('height', 16)
4192                 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
4193
4194             // Setup Axes
4195             if (showXAxis) {
4196                 xAxis
4197                     .scale(x)
4198                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
4199                     .tickSize(-availableHeight, 0);
4200
4201                 g.select('.nv-x.nv-axis')
4202                     .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
4203                 g.select('.nv-x.nv-axis').call(xAxis);
4204
4205                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
4206                 if (staggerLabels) {
4207                     xTicks
4208                         .selectAll('text')
4209                         .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
4210                 }
4211             }
4212
4213             if (showYAxis) {
4214                 yAxis
4215                     .scale(y)
4216                     ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4217                     .tickSize( -availableWidth, 0);
4218
4219                 g.select('.nv-y.nv-axis').call(yAxis);
4220             }
4221
4222             // Zero line
4223             g.select(".nv-zeroLine line")
4224                 .attr("x1",0)
4225                 .attr("x2",availableWidth)
4226                 .attr("y1", y(0))
4227                 .attr("y2", y(0))
4228             ;
4229         });
4230
4231         renderWatch.renderEnd('discreteBar chart immediate');
4232         return chart;
4233     }
4234
4235     //============================================================
4236     // Event Handling/Dispatching (out of chart's scope)
4237     //------------------------------------------------------------
4238
4239     discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
4240         evt['series'] = {
4241             key: chart.x()(evt.data),
4242             value: chart.y()(evt.data),
4243             color: evt.color
4244         };
4245         tooltip.data(evt).hidden(false);
4246     });
4247
4248     discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
4249         tooltip.hidden(true);
4250     });
4251
4252     discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
4253         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
4254     });
4255
4256     //============================================================
4257     // Expose Public Variables
4258     //------------------------------------------------------------
4259
4260     chart.dispatch = dispatch;
4261     chart.discretebar = discretebar;
4262     chart.xAxis = xAxis;
4263     chart.yAxis = yAxis;
4264     chart.tooltip = tooltip;
4265
4266     chart.options = nv.utils.optionsFunc.bind(chart);
4267
4268     chart._options = Object.create({}, {
4269         // simple options, just get/set the necessary values
4270         width:      {get: function(){return width;}, set: function(_){width=_;}},
4271         height:     {get: function(){return height;}, set: function(_){height=_;}},
4272         staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
4273         showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
4274         showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
4275         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
4276
4277         // deprecated options
4278         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
4279             // deprecated after 1.7.1
4280             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
4281             tooltip.enabled(!!_);
4282         }},
4283         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
4284             // deprecated after 1.7.1
4285             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
4286             tooltip.contentGenerator(_);
4287         }},
4288
4289         // options that require extra logic in the setter
4290         margin: {get: function(){return margin;}, set: function(_){
4291             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4292             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4293             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4294             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4295         }},
4296         duration: {get: function(){return duration;}, set: function(_){
4297             duration = _;
4298             renderWatch.reset(duration);
4299             discretebar.duration(duration);
4300             xAxis.duration(duration);
4301             yAxis.duration(duration);
4302         }},
4303         color:  {get: function(){return color;}, set: function(_){
4304             color = nv.utils.getColor(_);
4305             discretebar.color(color);
4306         }},
4307         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
4308             rightAlignYAxis = _;
4309             yAxis.orient( (_) ? 'right' : 'left');
4310         }}
4311     });
4312
4313     nv.utils.inheritOptions(chart, discretebar);
4314     nv.utils.initOptions(chart);
4315
4316     return chart;
4317 }
4318
4319 nv.models.distribution = function() {
4320     "use strict";
4321     //============================================================
4322     // Public Variables with Default Settings
4323     //------------------------------------------------------------
4324
4325     var margin = {top: 0, right: 0, bottom: 0, left: 0}
4326         , width = 400 //technically width or height depending on x or y....
4327         , size = 8
4328         , axis = 'x' // 'x' or 'y'... horizontal or vertical
4329         , getData = function(d) { return d[axis] }  // defaults d.x or d.y
4330         , color = nv.utils.defaultColor()
4331         , scale = d3.scale.linear()
4332         , domain
4333         , duration = 250
4334         , dispatch = d3.dispatch('renderEnd')
4335         ;
4336
4337     //============================================================
4338
4339
4340     //============================================================
4341     // Private Variables
4342     //------------------------------------------------------------
4343
4344     var scale0;
4345     var renderWatch = nv.utils.renderWatch(dispatch, duration);
4346
4347     //============================================================
4348
4349
4350     function chart(selection) {
4351         renderWatch.reset();
4352         selection.each(function(data) {
4353             var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
4354                 naxis = axis == 'x' ? 'y' : 'x',
4355                 container = d3.select(this);
4356             nv.utils.initSVG(container);
4357
4358             //------------------------------------------------------------
4359             // Setup Scales
4360
4361             scale0 = scale0 || scale;
4362
4363             //------------------------------------------------------------
4364
4365
4366             //------------------------------------------------------------
4367             // Setup containers and skeleton of chart
4368
4369             var wrap = container.selectAll('g.nv-distribution').data([data]);
4370             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
4371             var gEnter = wrapEnter.append('g');
4372             var g = wrap.select('g');
4373
4374             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4375
4376             //------------------------------------------------------------
4377
4378
4379             var distWrap = g.selectAll('g.nv-dist')
4380                 .data(function(d) { return d }, function(d) { return d.key });
4381
4382             distWrap.enter().append('g');
4383             distWrap
4384                 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
4385                 .style('stroke', function(d,i) { return color(d, i) });
4386
4387             var dist = distWrap.selectAll('line.nv-dist' + axis)
4388                 .data(function(d) { return d.values })
4389             dist.enter().append('line')
4390                 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
4391                 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
4392             renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
4393                 // .transition()
4394                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4395                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4396                 .style('stroke-opacity', 0)
4397                 .remove();
4398             dist
4399                 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
4400                 .attr(naxis + '1', 0)
4401                 .attr(naxis + '2', size);
4402             renderWatch.transition(dist, 'dist')
4403                 // .transition()
4404                 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4405                 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4406
4407
4408             scale0 = scale.copy();
4409
4410         });
4411         renderWatch.renderEnd('distribution immediate');
4412         return chart;
4413     }
4414
4415
4416     //============================================================
4417     // Expose Public Variables
4418     //------------------------------------------------------------
4419     chart.options = nv.utils.optionsFunc.bind(chart);
4420     chart.dispatch = dispatch;
4421
4422     chart.margin = function(_) {
4423         if (!arguments.length) return margin;
4424         margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4425         margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4426         margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4427         margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4428         return chart;
4429     };
4430
4431     chart.width = function(_) {
4432         if (!arguments.length) return width;
4433         width = _;
4434         return chart;
4435     };
4436
4437     chart.axis = function(_) {
4438         if (!arguments.length) return axis;
4439         axis = _;
4440         return chart;
4441     };
4442
4443     chart.size = function(_) {
4444         if (!arguments.length) return size;
4445         size = _;
4446         return chart;
4447     };
4448
4449     chart.getData = function(_) {
4450         if (!arguments.length) return getData;
4451         getData = d3.functor(_);
4452         return chart;
4453     };
4454
4455     chart.scale = function(_) {
4456         if (!arguments.length) return scale;
4457         scale = _;
4458         return chart;
4459     };
4460
4461     chart.color = function(_) {
4462         if (!arguments.length) return color;
4463         color = nv.utils.getColor(_);
4464         return chart;
4465     };
4466
4467     chart.duration = function(_) {
4468         if (!arguments.length) return duration;
4469         duration = _;
4470         renderWatch.reset(duration);
4471         return chart;
4472     };
4473     //============================================================
4474
4475
4476     return chart;
4477 }
4478 nv.models.furiousLegend = function() {
4479     "use strict";
4480
4481     //============================================================
4482     // Public Variables with Default Settings
4483     //------------------------------------------------------------
4484
4485     var margin = {top: 5, right: 0, bottom: 5, left: 0}
4486         , width = 400
4487         , height = 20
4488         , getKey = function(d) { return d.key }
4489         , color = nv.utils.getColor()
4490         , align = true
4491         , padding = 28 //define how much space between legend items. - recommend 32 for furious version
4492         , rightAlign = true
4493         , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4494         , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4495         , expanded = false
4496         , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4497         , vers = 'classic' //Options are "classic" and "furious"
4498         ;
4499
4500     function chart(selection) {
4501         selection.each(function(data) {
4502             var availableWidth = width - margin.left - margin.right,
4503                 container = d3.select(this);
4504             nv.utils.initSVG(container);
4505
4506             // Setup containers and skeleton of chart
4507             var wrap = container.selectAll('g.nv-legend').data([data]);
4508             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4509             var g = wrap.select('g');
4510
4511             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4512
4513             var series = g.selectAll('.nv-series')
4514                 .data(function(d) {
4515                     if(vers != 'furious') return d;
4516
4517                     return d.filter(function(n) {
4518                         return expanded ? true : !n.disengaged;
4519                     });
4520                 });
4521             var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4522
4523             var seriesShape;
4524
4525             if(vers == 'classic') {
4526                 seriesEnter.append('circle')
4527                     .style('stroke-width', 2)
4528                     .attr('class','nv-legend-symbol')
4529                     .attr('r', 5);
4530
4531                 seriesShape = series.select('circle');
4532             } else if (vers == 'furious') {
4533                 seriesEnter.append('rect')
4534                     .style('stroke-width', 2)
4535                     .attr('class','nv-legend-symbol')
4536                     .attr('rx', 3)
4537                     .attr('ry', 3);
4538
4539                 seriesShape = series.select('rect');
4540
4541                 seriesEnter.append('g')
4542                     .attr('class', 'nv-check-box')
4543                     .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
4544                     .attr('transform', 'translate(-10,-8)scale(0.5)');
4545
4546                 var seriesCheckbox = series.select('.nv-check-box');
4547
4548                 seriesCheckbox.each(function(d,i) {
4549                     d3.select(this).selectAll('path')
4550                         .attr('stroke', setTextColor(d,i));
4551                 });
4552             }
4553
4554             seriesEnter.append('text')
4555                 .attr('text-anchor', 'start')
4556                 .attr('class','nv-legend-text')
4557                 .attr('dy', '.32em')
4558                 .attr('dx', '8');
4559
4560             var seriesText = series.select('text.nv-legend-text');
4561
4562             series
4563                 .on('mouseover', function(d,i) {
4564                     dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
4565                 })
4566                 .on('mouseout', function(d,i) {
4567                     dispatch.legendMouseout(d,i);
4568                 })
4569                 .on('click', function(d,i) {
4570                     dispatch.legendClick(d,i);
4571                     // make sure we re-get data in case it was modified
4572                     var data = series.data();
4573                     if (updateState) {
4574                         if(vers =='classic') {
4575                             if (radioButtonMode) {
4576                                 //Radio button mode: set every series to disabled,
4577                                 //  and enable the clicked series.
4578                                 data.forEach(function(series) { series.disabled = true});
4579                                 d.disabled = false;
4580                             }
4581                             else {
4582                                 d.disabled = !d.disabled;
4583                                 if (data.every(function(series) { return series.disabled})) {
4584                                     //the default behavior of NVD3 legends is, if every single series
4585                                     // is disabled, turn all series' back on.
4586                                     data.forEach(function(series) { series.disabled = false});
4587                                 }
4588                             }
4589                         } else if(vers == 'furious') {
4590                             if(expanded) {
4591                                 d.disengaged = !d.disengaged;
4592                                 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
4593                                 d.disabled = d.disengaged || d.userDisabled;
4594                             } else if (!expanded) {
4595                                 d.disabled = !d.disabled;
4596                                 d.userDisabled = d.disabled;
4597                                 var engaged = data.filter(function(d) { return !d.disengaged; });
4598                                 if (engaged.every(function(series) { return series.userDisabled })) {
4599                                     //the default behavior of NVD3 legends is, if every single series
4600                                     // is disabled, turn all series' back on.
4601                                     data.forEach(function(series) {
4602                                         series.disabled = series.userDisabled = false;
4603                                     });
4604                                 }
4605                             }
4606                         }
4607                         dispatch.stateChange({
4608                             disabled: data.map(function(d) { return !!d.disabled }),
4609                             disengaged: data.map(function(d) { return !!d.disengaged })
4610                         });
4611
4612                     }
4613                 })
4614                 .on('dblclick', function(d,i) {
4615                     if(vers == 'furious' && expanded) return;
4616                     dispatch.legendDblclick(d,i);
4617                     if (updateState) {
4618                         // make sure we re-get data in case it was modified
4619                         var data = series.data();
4620                         //the default behavior of NVD3 legends, when double clicking one,
4621                         // is to set all other series' to false, and make the double clicked series enabled.
4622                         data.forEach(function(series) {
4623                             series.disabled = true;
4624                             if(vers == 'furious') series.userDisabled = series.disabled;
4625                         });
4626                         d.disabled = false;
4627                         if(vers == 'furious') d.userDisabled = d.disabled;
4628                         dispatch.stateChange({
4629                             disabled: data.map(function(d) { return !!d.disabled })
4630                         });
4631                     }
4632                 });
4633
4634             series.classed('nv-disabled', function(d) { return d.userDisabled });
4635             series.exit().remove();
4636
4637             seriesText
4638                 .attr('fill', setTextColor)
4639                 .text(getKey);
4640
4641             //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4642             // NEW ALIGNING CODE, TODO: clean up
4643
4644             var versPadding;
4645             switch(vers) {
4646                 case 'furious' :
4647                     versPadding = 23;
4648                     break;
4649                 case 'classic' :
4650                     versPadding = 20;
4651             }
4652
4653             if (align) {
4654
4655                 var seriesWidths = [];
4656                 series.each(function(d,i) {
4657                     var legendText = d3.select(this).select('text');
4658                     var nodeTextLength;
4659                     try {
4660                         nodeTextLength = legendText.node().getComputedTextLength();
4661                         // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
4662                         if(nodeTextLength <= 0) throw Error();
4663                     }
4664                     catch(e) {
4665                         nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4666                     }
4667
4668                     seriesWidths.push(nodeTextLength + padding);
4669                 });
4670
4671                 var seriesPerRow = 0;
4672                 var legendWidth = 0;
4673                 var columnWidths = [];
4674
4675                 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4676                     columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4677                     legendWidth += seriesWidths[seriesPerRow++];
4678                 }
4679                 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4680
4681                 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4682                     columnWidths = [];
4683                     seriesPerRow--;
4684
4685                     for (var k = 0; k < seriesWidths.length; k++) {
4686                         if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4687                             columnWidths[k % seriesPerRow] = seriesWidths[k];
4688                     }
4689
4690                     legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4691                         return prev + cur;
4692                     });
4693                 }
4694
4695                 var xPositions = [];
4696                 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4697                     xPositions[i] = curX;
4698                     curX += columnWidths[i];
4699                 }
4700
4701                 series
4702                     .attr('transform', function(d, i) {
4703                         return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
4704                     });
4705
4706                 //position legend as far right as possible within the total width
4707                 if (rightAlign) {
4708                     g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4709                 }
4710                 else {
4711                     g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4712                 }
4713
4714                 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
4715
4716             } else {
4717
4718                 var ypos = 5,
4719                     newxpos = 5,
4720                     maxwidth = 0,
4721                     xpos;
4722                 series
4723                     .attr('transform', function(d, i) {
4724                         var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
4725                         xpos = newxpos;
4726
4727                         if (width < margin.left + margin.right + xpos + length) {
4728                             newxpos = xpos = 5;
4729                             ypos += versPadding;
4730                         }
4731
4732                         newxpos += length;
4733                         if (newxpos > maxwidth) maxwidth = newxpos;
4734
4735                         return 'translate(' + xpos + ',' + ypos + ')';
4736                     });
4737
4738                 //position legend as far right as possible within the total width
4739                 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4740
4741                 height = margin.top + margin.bottom + ypos + 15;
4742             }
4743
4744             if(vers == 'furious') {
4745                 // Size rectangles after text is placed
4746                 seriesShape
4747                     .attr('width', function(d,i) {
4748                         return seriesText[0][i].getComputedTextLength() + 27;
4749                     })
4750                     .attr('height', 18)
4751                     .attr('y', -9)
4752                     .attr('x', -15)
4753             }
4754
4755             seriesShape
4756                 .style('fill', setBGColor)
4757                 .style('stroke', function(d,i) { return d.color || color(d, i) });
4758         });
4759
4760         function setTextColor(d,i) {
4761             if(vers != 'furious') return '#000';
4762             if(expanded) {
4763                 return d.disengaged ? color(d,i) : '#fff';
4764             } else if (!expanded) {
4765                 return !!d.disabled ? color(d,i) : '#fff';
4766             }
4767         }
4768
4769         function setBGColor(d,i) {
4770             if(expanded && vers == 'furious') {
4771                 return d.disengaged ? '#fff' : color(d,i);
4772             } else {
4773                 return !!d.disabled ? '#fff' : color(d,i);
4774             }
4775         }
4776
4777         return chart;
4778     }
4779
4780     //============================================================
4781     // Expose Public Variables
4782     //------------------------------------------------------------
4783
4784     chart.dispatch = dispatch;
4785     chart.options = nv.utils.optionsFunc.bind(chart);
4786
4787     chart._options = Object.create({}, {
4788         // simple options, just get/set the necessary values
4789         width:      {get: function(){return width;}, set: function(_){width=_;}},
4790         height:     {get: function(){return height;}, set: function(_){height=_;}},
4791         key:        {get: function(){return getKey;}, set: function(_){getKey=_;}},
4792         align:      {get: function(){return align;}, set: function(_){align=_;}},
4793         rightAlign:    {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
4794         padding:       {get: function(){return padding;}, set: function(_){padding=_;}},
4795         updateState:   {get: function(){return updateState;}, set: function(_){updateState=_;}},
4796         radioButtonMode:    {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
4797         expanded:   {get: function(){return expanded;}, set: function(_){expanded=_;}},
4798         vers:   {get: function(){return vers;}, set: function(_){vers=_;}},
4799
4800         // options that require extra logic in the setter
4801         margin: {get: function(){return margin;}, set: function(_){
4802             margin.top    = _.top    !== undefined ? _.top    : margin.top;
4803             margin.right  = _.right  !== undefined ? _.right  : margin.right;
4804             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4805             margin.left   = _.left   !== undefined ? _.left   : margin.left;
4806         }},
4807         color:  {get: function(){return color;}, set: function(_){
4808             color = nv.utils.getColor(_);
4809         }}
4810     });
4811
4812     nv.utils.initOptions(chart);
4813
4814     return chart;
4815 };
4816 //TODO: consider deprecating and using multibar with single series for this
4817 nv.models.historicalBar = function() {
4818     "use strict";
4819
4820     //============================================================
4821     // Public Variables with Default Settings
4822     //------------------------------------------------------------
4823
4824     var margin = {top: 0, right: 0, bottom: 0, left: 0}
4825         , width = null
4826         , height = null
4827         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
4828         , container = null
4829         , x = d3.scale.linear()
4830         , y = d3.scale.linear()
4831         , getX = function(d) { return d.x }
4832         , getY = function(d) { return d.y }
4833         , forceX = []
4834         , forceY = [0]
4835         , padData = false
4836         , clipEdge = true
4837         , color = nv.utils.defaultColor()
4838         , xDomain
4839         , yDomain
4840         , xRange
4841         , yRange
4842         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
4843         , interactive = true
4844         ;
4845
4846     var renderWatch = nv.utils.renderWatch(dispatch, 0);
4847
4848     function chart(selection) {
4849         selection.each(function(data) {
4850             renderWatch.reset();
4851
4852             container = d3.select(this);
4853             var availableWidth = nv.utils.availableWidth(width, container, margin),
4854                 availableHeight = nv.utils.availableHeight(height, container, margin);
4855
4856             nv.utils.initSVG(container);
4857
4858             // Setup Scales
4859             x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
4860
4861             if (padData)
4862                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
4863             else
4864                 x.range(xRange || [0, availableWidth]);
4865
4866             y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
4867                 .range(yRange || [availableHeight, 0]);
4868
4869             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
4870             if (x.domain()[0] === x.domain()[1])
4871                 x.domain()[0] ?
4872                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
4873                     : x.domain([-1,1]);
4874
4875             if (y.domain()[0] === y.domain()[1])
4876                 y.domain()[0] ?
4877                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
4878                     : y.domain([-1,1]);
4879
4880             // Setup containers and skeleton of chart
4881             var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
4882             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
4883             var defsEnter = wrapEnter.append('defs');
4884             var gEnter = wrapEnter.append('g');
4885             var g = wrap.select('g');
4886
4887             gEnter.append('g').attr('class', 'nv-bars');
4888             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4889
4890             container
4891                 .on('click', function(d,i) {
4892                     dispatch.chartClick({
4893                         data: d,
4894                         index: i,
4895                         pos: d3.event,
4896                         id: id
4897                     });
4898                 });
4899
4900             defsEnter.append('clipPath')
4901                 .attr('id', 'nv-chart-clip-path-' + id)
4902                 .append('rect');
4903
4904             wrap.select('#nv-chart-clip-path-' + id + ' rect')
4905                 .attr('width', availableWidth)
4906                 .attr('height', availableHeight);
4907
4908             g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
4909
4910             var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
4911                 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
4912             bars.exit().remove();
4913
4914             bars.enter().append('rect')
4915                 .attr('x', 0 )
4916                 .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
4917                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
4918                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4919                 .on('mouseover', function(d,i) {
4920                     if (!interactive) return;
4921                     d3.select(this).classed('hover', true);
4922                     dispatch.elementMouseover({
4923                         data: d,
4924                         index: i,
4925                         color: d3.select(this).style("fill")
4926                     });
4927
4928                 })
4929                 .on('mouseout', function(d,i) {
4930                     if (!interactive) return;
4931                     d3.select(this).classed('hover', false);
4932                     dispatch.elementMouseout({
4933                         data: d,
4934                         index: i,
4935                         color: d3.select(this).style("fill")
4936                     });
4937                 })
4938                 .on('mousemove', function(d,i) {
4939                     if (!interactive) return;
4940                     dispatch.elementMousemove({
4941                         data: d,
4942                         index: i,
4943                         color: d3.select(this).style("fill")
4944                     });
4945                 })
4946                 .on('click', function(d,i) {
4947                     if (!interactive) return;
4948                     dispatch.elementClick({
4949                         data: d,
4950                         index: i,
4951                         color: d3.select(this).style("fill")
4952                     });
4953                     d3.event.stopPropagation();
4954                 })
4955                 .on('dblclick', function(d,i) {
4956                     if (!interactive) return;
4957                     dispatch.elementDblClick({
4958                         data: d,
4959                         index: i,
4960                         color: d3.select(this).style("fill")
4961                     });
4962                     d3.event.stopPropagation();
4963                 });
4964
4965             bars
4966                 .attr('fill', function(d,i) { return color(d, i); })
4967                 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4968                 .watchTransition(renderWatch, 'bars')
4969                 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4970                 //TODO: better width calculations that don't assume always uniform data spacing;w
4971                 .attr('width', (availableWidth / data[0].values.length) * .9 );
4972
4973             bars.watchTransition(renderWatch, 'bars')
4974                 .attr('y', function(d,i) {
4975                     var rval = getY(d,i) < 0 ?
4976                         y(0) :
4977                             y(0) - y(getY(d,i)) < 1 ?
4978                         y(0) - 1 :
4979                         y(getY(d,i));
4980                     return nv.utils.NaNtoZero(rval);
4981                 })
4982                 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
4983
4984         });
4985
4986         renderWatch.renderEnd('historicalBar immediate');
4987         return chart;
4988     }
4989
4990     //Create methods to allow outside functions to highlight a specific bar.
4991     chart.highlightPoint = function(pointIndex, isHoverOver) {
4992         container
4993             .select(".nv-bars .nv-bar-0-" + pointIndex)
4994             .classed("hover", isHoverOver)
4995         ;
4996     };
4997
4998     chart.clearHighlights = function() {
4999         container
5000             .select(".nv-bars .nv-bar.hover")
5001             .classed("hover", false)
5002         ;
5003     };
5004
5005     //============================================================
5006     // Expose Public Variables
5007     //------------------------------------------------------------
5008
5009     chart.dispatch = dispatch;
5010     chart.options = nv.utils.optionsFunc.bind(chart);
5011
5012     chart._options = Object.create({}, {
5013         // simple options, just get/set the necessary values
5014         width:   {get: function(){return width;}, set: function(_){width=_;}},
5015         height:  {get: function(){return height;}, set: function(_){height=_;}},
5016         forceX:  {get: function(){return forceX;}, set: function(_){forceX=_;}},
5017         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
5018         padData: {get: function(){return padData;}, set: function(_){padData=_;}},
5019         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
5020         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
5021         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
5022         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
5023         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
5024         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
5025         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
5026         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
5027         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
5028         id:          {get: function(){return id;}, set: function(_){id=_;}},
5029         interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
5030
5031         // options that require extra logic in the setter
5032         margin: {get: function(){return margin;}, set: function(_){
5033             margin.top    = _.top    !== undefined ? _.top    : margin.top;
5034             margin.right  = _.right  !== undefined ? _.right  : margin.right;
5035             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5036             margin.left   = _.left   !== undefined ? _.left   : margin.left;
5037         }},
5038         color:  {get: function(){return color;}, set: function(_){
5039             color = nv.utils.getColor(_);
5040         }}
5041     });
5042
5043     nv.utils.initOptions(chart);
5044
5045     return chart;
5046 };
5047
5048 nv.models.historicalBarChart = function(bar_model) {
5049     "use strict";
5050
5051     //============================================================
5052     // Public Variables with Default Settings
5053     //------------------------------------------------------------
5054
5055     var bars = bar_model || nv.models.historicalBar()
5056         , xAxis = nv.models.axis()
5057         , yAxis = nv.models.axis()
5058         , legend = nv.models.legend()
5059         , interactiveLayer = nv.interactiveGuideline()
5060         , tooltip = nv.models.tooltip()
5061         ;
5062
5063
5064     var margin = {top: 30, right: 90, bottom: 50, left: 90}
5065         , color = nv.utils.defaultColor()
5066         , width = null
5067         , height = null
5068         , showLegend = false
5069         , showXAxis = true
5070         , showYAxis = true
5071         , rightAlignYAxis = false
5072         , useInteractiveGuideline = false
5073         , x
5074         , y
5075         , state = {}
5076         , defaultState = null
5077         , noData = null
5078         , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
5079         , transitionDuration = 250
5080         ;
5081
5082     xAxis.orient('bottom').tickPadding(7);
5083     yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
5084     tooltip
5085         .duration(0)
5086         .headerEnabled(false)
5087         .valueFormatter(function(d, i) {
5088             return yAxis.tickFormat()(d, i);
5089         })
5090         .headerFormatter(function(d, i) {
5091             return xAxis.tickFormat()(d, i);
5092         });
5093
5094
5095     //============================================================
5096     // Private Variables
5097     //------------------------------------------------------------
5098
5099     var renderWatch = nv.utils.renderWatch(dispatch, 0);
5100
5101     function chart(selection) {
5102         selection.each(function(data) {
5103             renderWatch.reset();
5104             renderWatch.models(bars);
5105             if (showXAxis) renderWatch.models(xAxis);
5106             if (showYAxis) renderWatch.models(yAxis);
5107
5108             var container = d3.select(this),
5109                 that = this;
5110             nv.utils.initSVG(container);
5111             var availableWidth = nv.utils.availableWidth(width, container, margin),
5112                 availableHeight = nv.utils.availableHeight(height, container, margin);
5113
5114             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5115             chart.container = this;
5116
5117             //set state.disabled
5118             state.disabled = data.map(function(d) { return !!d.disabled });
5119
5120             if (!defaultState) {
5121                 var key;
5122                 defaultState = {};
5123                 for (key in state) {
5124                     if (state[key] instanceof Array)
5125                         defaultState[key] = state[key].slice(0);
5126                     else
5127                         defaultState[key] = state[key];
5128                 }
5129             }
5130
5131             // Display noData message if there's nothing to show.
5132             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5133                 nv.utils.noData(chart, container)
5134                 return chart;
5135             } else {
5136                 container.selectAll('.nv-noData').remove();
5137             }
5138
5139             // Setup Scales
5140             x = bars.xScale();
5141             y = bars.yScale();
5142
5143             // Setup containers and skeleton of chart
5144             var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
5145             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
5146             var g = wrap.select('g');
5147
5148             gEnter.append('g').attr('class', 'nv-x nv-axis');
5149             gEnter.append('g').attr('class', 'nv-y nv-axis');
5150             gEnter.append('g').attr('class', 'nv-barsWrap');
5151             gEnter.append('g').attr('class', 'nv-legendWrap');
5152             gEnter.append('g').attr('class', 'nv-interactive');
5153
5154             // Legend
5155             if (showLegend) {
5156                 legend.width(availableWidth);
5157
5158                 g.select('.nv-legendWrap')
5159                     .datum(data)
5160                     .call(legend);
5161
5162                 if ( margin.top != legend.height()) {
5163                     margin.top = legend.height();
5164                     availableHeight = nv.utils.availableHeight(height, container, margin);
5165                 }
5166
5167                 wrap.select('.nv-legendWrap')
5168                     .attr('transform', 'translate(0,' + (-margin.top) +')')
5169             }
5170             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5171
5172             if (rightAlignYAxis) {
5173                 g.select(".nv-y.nv-axis")
5174                     .attr("transform", "translate(" + availableWidth + ",0)");
5175             }
5176
5177             //Set up interactive layer
5178             if (useInteractiveGuideline) {
5179                 interactiveLayer
5180                     .width(availableWidth)
5181                     .height(availableHeight)
5182                     .margin({left:margin.left, top:margin.top})
5183                     .svgContainer(container)
5184                     .xScale(x);
5185                 wrap.select(".nv-interactive").call(interactiveLayer);
5186             }
5187             bars
5188                 .width(availableWidth)
5189                 .height(availableHeight)
5190                 .color(data.map(function(d,i) {
5191                     return d.color || color(d, i);
5192                 }).filter(function(d,i) { return !data[i].disabled }));
5193
5194             var barsWrap = g.select('.nv-barsWrap')
5195                 .datum(data.filter(function(d) { return !d.disabled }));
5196             barsWrap.transition().call(bars);
5197
5198             // Setup Axes
5199             if (showXAxis) {
5200                 xAxis
5201                     .scale(x)
5202                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5203                     .tickSize(-availableHeight, 0);
5204
5205                 g.select('.nv-x.nv-axis')
5206                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
5207                 g.select('.nv-x.nv-axis')
5208                     .transition()
5209                     .call(xAxis);
5210             }
5211
5212             if (showYAxis) {
5213                 yAxis
5214                     .scale(y)
5215                     ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5216                     .tickSize( -availableWidth, 0);
5217
5218                 g.select('.nv-y.nv-axis')
5219                     .transition()
5220                     .call(yAxis);
5221             }
5222
5223             //============================================================
5224             // Event Handling/Dispatching (in chart's scope)
5225             //------------------------------------------------------------
5226
5227             interactiveLayer.dispatch.on('elementMousemove', function(e) {
5228                 bars.clearHighlights();
5229
5230                 var singlePoint, pointIndex, pointXLocation, allData = [];
5231                 data
5232                     .filter(function(series, i) {
5233                         series.seriesIndex = i;
5234                         return !series.disabled;
5235                     })
5236                     .forEach(function(series,i) {
5237                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5238                         bars.highlightPoint(pointIndex,true);
5239                         var point = series.values[pointIndex];
5240                         if (point === undefined) return;
5241                         if (singlePoint === undefined) singlePoint = point;
5242                         if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5243                         allData.push({
5244                             key: series.key,
5245                             value: chart.y()(point, pointIndex),
5246                             color: color(series,series.seriesIndex),
5247                             data: series.values[pointIndex]
5248                         });
5249                     });
5250
5251                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5252                 interactiveLayer.tooltip
5253                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5254                     .chartContainer(that.parentNode)
5255                     .valueFormatter(function(d,i) {
5256                         return yAxis.tickFormat()(d);
5257                     })
5258                     .data({
5259                         value: xValue,
5260                         index: pointIndex,
5261                         series: allData
5262                     })();
5263
5264                 interactiveLayer.renderGuideLine(pointXLocation);
5265
5266             });
5267
5268             interactiveLayer.dispatch.on("elementMouseout",function(e) {
5269                 dispatch.tooltipHide();
5270                 bars.clearHighlights();
5271             });
5272
5273             legend.dispatch.on('legendClick', function(d,i) {
5274                 d.disabled = !d.disabled;
5275
5276                 if (!data.filter(function(d) { return !d.disabled }).length) {
5277                     data.map(function(d) {
5278                         d.disabled = false;
5279                         wrap.selectAll('.nv-series').classed('disabled', false);
5280                         return d;
5281                     });
5282                 }
5283
5284                 state.disabled = data.map(function(d) { return !!d.disabled });
5285                 dispatch.stateChange(state);
5286
5287                 selection.transition().call(chart);
5288             });
5289
5290             legend.dispatch.on('legendDblclick', function(d) {
5291                 //Double clicking should always enable current series, and disabled all others.
5292                 data.forEach(function(d) {
5293                     d.disabled = true;
5294                 });
5295                 d.disabled = false;
5296
5297                 state.disabled = data.map(function(d) { return !!d.disabled });
5298                 dispatch.stateChange(state);
5299                 chart.update();
5300             });
5301
5302             dispatch.on('changeState', function(e) {
5303                 if (typeof e.disabled !== 'undefined') {
5304                     data.forEach(function(series,i) {
5305                         series.disabled = e.disabled[i];
5306                     });
5307
5308                     state.disabled = e.disabled;
5309                 }
5310
5311                 chart.update();
5312             });
5313         });
5314
5315         renderWatch.renderEnd('historicalBarChart immediate');
5316         return chart;
5317     }
5318
5319     //============================================================
5320     // Event Handling/Dispatching (out of chart's scope)
5321     //------------------------------------------------------------
5322
5323     bars.dispatch.on('elementMouseover.tooltip', function(evt) {
5324         evt['series'] = {
5325             key: chart.x()(evt.data),
5326             value: chart.y()(evt.data),
5327             color: evt.color
5328         };
5329         tooltip.data(evt).hidden(false);
5330     });
5331
5332     bars.dispatch.on('elementMouseout.tooltip', function(evt) {
5333         tooltip.hidden(true);
5334     });
5335
5336     bars.dispatch.on('elementMousemove.tooltip', function(evt) {
5337         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
5338     });
5339
5340     //============================================================
5341     // Expose Public Variables
5342     //------------------------------------------------------------
5343
5344     // expose chart's sub-components
5345     chart.dispatch = dispatch;
5346     chart.bars = bars;
5347     chart.legend = legend;
5348     chart.xAxis = xAxis;
5349     chart.yAxis = yAxis;
5350     chart.interactiveLayer = interactiveLayer;
5351     chart.tooltip = tooltip;
5352
5353     chart.options = nv.utils.optionsFunc.bind(chart);
5354
5355     chart._options = Object.create({}, {
5356         // simple options, just get/set the necessary values
5357         width:      {get: function(){return width;}, set: function(_){width=_;}},
5358         height:     {get: function(){return height;}, set: function(_){height=_;}},
5359         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5360         showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
5361         showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
5362         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
5363         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
5364
5365         // deprecated options
5366         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
5367             // deprecated after 1.7.1
5368             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
5369             tooltip.enabled(!!_);
5370         }},
5371         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
5372             // deprecated after 1.7.1
5373             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
5374             tooltip.contentGenerator(_);
5375         }},
5376
5377         // options that require extra logic in the setter
5378         margin: {get: function(){return margin;}, set: function(_){
5379             margin.top    = _.top    !== undefined ? _.top    : margin.top;
5380             margin.right  = _.right  !== undefined ? _.right  : margin.right;
5381             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5382             margin.left   = _.left   !== undefined ? _.left   : margin.left;
5383         }},
5384         color:  {get: function(){return color;}, set: function(_){
5385             color = nv.utils.getColor(_);
5386             legend.color(color);
5387             bars.color(color);
5388         }},
5389         duration:    {get: function(){return transitionDuration;}, set: function(_){
5390             transitionDuration=_;
5391             renderWatch.reset(transitionDuration);
5392             yAxis.duration(transitionDuration);
5393             xAxis.duration(transitionDuration);
5394         }},
5395         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
5396             rightAlignYAxis = _;
5397             yAxis.orient( (_) ? 'right' : 'left');
5398         }},
5399         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
5400             useInteractiveGuideline = _;
5401             if (_ === true) {
5402                 chart.interactive(false);
5403             }
5404         }}
5405     });
5406
5407     nv.utils.inheritOptions(chart, bars);
5408     nv.utils.initOptions(chart);
5409
5410     return chart;
5411 };
5412
5413
5414 // ohlcChart is just a historical chart with ohlc bars and some tweaks
5415 nv.models.ohlcBarChart = function() {
5416     var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
5417
5418     // special default tooltip since we show multiple values per x
5419     chart.useInteractiveGuideline(true);
5420     chart.interactiveLayer.tooltip.contentGenerator(function(data) {
5421         // we assume only one series exists for this chart
5422         var d = data.series[0].data;
5423         // match line colors as defined in nv.d3.css
5424         var color = d.open < d.close ? "2ca02c" : "d62728";
5425         return '' +
5426             '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5427             '<table>' +
5428             '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
5429             '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
5430             '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
5431             '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
5432             '</table>';
5433     });
5434     return chart;
5435 };
5436
5437 // candlestickChart is just a historical chart with candlestick bars and some tweaks
5438 nv.models.candlestickBarChart = function() {
5439     var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
5440
5441     // special default tooltip since we show multiple values per x
5442     chart.useInteractiveGuideline(true);
5443     chart.interactiveLayer.tooltip.contentGenerator(function(data) {
5444         // we assume only one series exists for this chart
5445         var d = data.series[0].data;
5446         // match line colors as defined in nv.d3.css
5447         var color = d.open < d.close ? "2ca02c" : "d62728";
5448         return '' +
5449             '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5450             '<table>' +
5451             '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
5452             '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
5453             '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
5454             '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
5455             '</table>';
5456     });
5457     return chart;
5458 };
5459 nv.models.legend = function() {
5460     "use strict";
5461
5462     //============================================================
5463     // Public Variables with Default Settings
5464     //------------------------------------------------------------
5465
5466     var margin = {top: 5, right: 0, bottom: 5, left: 0}
5467         , width = 400
5468         , height = 20
5469         , getKey = function(d) { return d.key }
5470         , color = nv.utils.getColor()
5471         , align = true
5472         , padding = 32 //define how much space between legend items. - recommend 32 for furious version
5473         , rightAlign = true
5474         , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
5475         , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
5476         , expanded = false
5477         , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
5478         , vers = 'classic' //Options are "classic" and "furious"
5479         ;
5480
5481     function chart(selection) {
5482         selection.each(function(data) {
5483             var availableWidth = width - margin.left - margin.right,
5484                 container = d3.select(this);
5485             nv.utils.initSVG(container);
5486
5487             // Setup containers and skeleton of chart
5488             var wrap = container.selectAll('g.nv-legend').data([data]);
5489             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
5490             var g = wrap.select('g');
5491
5492             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5493
5494             var series = g.selectAll('.nv-series')
5495                 .data(function(d) {
5496                     if(vers != 'furious') return d;
5497
5498                     return d.filter(function(n) {
5499                         return expanded ? true : !n.disengaged;
5500                     });
5501                 });
5502
5503             var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
5504             var seriesShape;
5505
5506             var versPadding;
5507             switch(vers) {
5508                 case 'furious' :
5509                     versPadding = 23;
5510                     break;
5511                 case 'classic' :
5512                     versPadding = 20;
5513             }
5514
5515             if(vers == 'classic') {
5516                 seriesEnter.append('circle')
5517                     .style('stroke-width', 2)
5518                     .attr('class','nv-legend-symbol')
5519                     .attr('r', 5);
5520
5521                 seriesShape = series.select('circle');
5522             } else if (vers == 'furious') {
5523                 seriesEnter.append('rect')
5524                     .style('stroke-width', 2)
5525                     .attr('class','nv-legend-symbol')
5526                     .attr('rx', 3)
5527                     .attr('ry', 3);
5528
5529                 seriesShape = series.select('.nv-legend-symbol');
5530
5531                 seriesEnter.append('g')
5532                     .attr('class', 'nv-check-box')
5533                     .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
5534                     .attr('transform', 'translate(-10,-8)scale(0.5)');
5535
5536                 var seriesCheckbox = series.select('.nv-check-box');
5537
5538                 seriesCheckbox.each(function(d,i) {
5539                     d3.select(this).selectAll('path')
5540                         .attr('stroke', setTextColor(d,i));
5541                 });
5542             }
5543
5544             seriesEnter.append('text')
5545                 .attr('text-anchor', 'start')
5546                 .attr('class','nv-legend-text')
5547                 .attr('dy', '.32em')
5548                 .attr('dx', '8');
5549
5550             var seriesText = series.select('text.nv-legend-text');
5551
5552             series
5553                 .on('mouseover', function(d,i) {
5554                     dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
5555                 })
5556                 .on('mouseout', function(d,i) {
5557                     dispatch.legendMouseout(d,i);
5558                 })
5559                 .on('click', function(d,i) {
5560                     dispatch.legendClick(d,i);
5561                     // make sure we re-get data in case it was modified
5562                     var data = series.data();
5563                     if (updateState) {
5564                         if(vers =='classic') {
5565                             if (radioButtonMode) {
5566                                 //Radio button mode: set every series to disabled,
5567                                 //  and enable the clicked series.
5568                                 data.forEach(function(series) { series.disabled = true});
5569                                 d.disabled = false;
5570                             }
5571                             else {
5572                                 d.disabled = !d.disabled;
5573                                 if (data.every(function(series) { return series.disabled})) {
5574                                     //the default behavior of NVD3 legends is, if every single series
5575                                     // is disabled, turn all series' back on.
5576                                     data.forEach(function(series) { series.disabled = false});
5577                                 }
5578                             }
5579                         } else if(vers == 'furious') {
5580                             if(expanded) {
5581                                 d.disengaged = !d.disengaged;
5582                                 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
5583                                 d.disabled = d.disengaged || d.userDisabled;
5584                             } else if (!expanded) {
5585                                 d.disabled = !d.disabled;
5586                                 d.userDisabled = d.disabled;
5587                                 var engaged = data.filter(function(d) { return !d.disengaged; });
5588                                 if (engaged.every(function(series) { return series.userDisabled })) {
5589                                     //the default behavior of NVD3 legends is, if every single series
5590                                     // is disabled, turn all series' back on.
5591                                     data.forEach(function(series) {
5592                                         series.disabled = series.userDisabled = false;
5593                                     });
5594                                 }
5595                             }
5596                         }
5597                         dispatch.stateChange({
5598                             disabled: data.map(function(d) { return !!d.disabled }),
5599                             disengaged: data.map(function(d) { return !!d.disengaged })
5600                         });
5601
5602                     }
5603                 })
5604                 .on('dblclick', function(d,i) {
5605                     if(vers == 'furious' && expanded) return;
5606                     dispatch.legendDblclick(d,i);
5607                     if (updateState) {
5608                         // make sure we re-get data in case it was modified
5609                         var data = series.data();
5610                         //the default behavior of NVD3 legends, when double clicking one,
5611                         // is to set all other series' to false, and make the double clicked series enabled.
5612                         data.forEach(function(series) {
5613                             series.disabled = true;
5614                             if(vers == 'furious') series.userDisabled = series.disabled;
5615                         });
5616                         d.disabled = false;
5617                         if(vers == 'furious') d.userDisabled = d.disabled;
5618                         dispatch.stateChange({
5619                             disabled: data.map(function(d) { return !!d.disabled })
5620                         });
5621                     }
5622                 });
5623
5624             series.classed('nv-disabled', function(d) { return d.userDisabled });
5625             series.exit().remove();
5626
5627             seriesText
5628                 .attr('fill', setTextColor)
5629                 .text(getKey);
5630
5631             //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
5632             // NEW ALIGNING CODE, TODO: clean up
5633             var legendWidth = 0;
5634             if (align) {
5635
5636                 var seriesWidths = [];
5637                 series.each(function(d,i) {
5638                     var legendText = d3.select(this).select('text');
5639                     var nodeTextLength;
5640                     try {
5641                         nodeTextLength = legendText.node().getComputedTextLength();
5642                         // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
5643                         if(nodeTextLength <= 0) throw Error();
5644                     }
5645                     catch(e) {
5646                         nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5647                     }
5648
5649                     seriesWidths.push(nodeTextLength + padding);
5650                 });
5651
5652                 var seriesPerRow = 0;
5653                 var columnWidths = [];
5654                 legendWidth = 0;
5655
5656                 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
5657                     columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
5658                     legendWidth += seriesWidths[seriesPerRow++];
5659                 }
5660                 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
5661
5662                 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
5663                     columnWidths = [];
5664                     seriesPerRow--;
5665
5666                     for (var k = 0; k < seriesWidths.length; k++) {
5667                         if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
5668                             columnWidths[k % seriesPerRow] = seriesWidths[k];
5669                     }
5670
5671                     legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
5672                         return prev + cur;
5673                     });
5674                 }
5675
5676                 var xPositions = [];
5677                 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5678                     xPositions[i] = curX;
5679                     curX += columnWidths[i];
5680                 }
5681
5682                 series
5683                     .attr('transform', function(d, i) {
5684                         return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
5685                     });
5686
5687                 //position legend as far right as possible within the total width
5688                 if (rightAlign) {
5689                     g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5690                 }
5691                 else {
5692                     g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5693                 }
5694
5695                 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
5696
5697             } else {
5698
5699                 var ypos = 5,
5700                     newxpos = 5,
5701                     maxwidth = 0,
5702                     xpos;
5703                 series
5704                     .attr('transform', function(d, i) {
5705                         var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
5706                         xpos = newxpos;
5707
5708                         if (width < margin.left + margin.right + xpos + length) {
5709                             newxpos = xpos = 5;
5710                             ypos += versPadding;
5711                         }
5712
5713                         newxpos += length;
5714                         if (newxpos > maxwidth) maxwidth = newxpos;
5715
5716                         if(legendWidth < xpos + maxwidth) {
5717                             legendWidth = xpos + maxwidth;
5718                         }
5719                         return 'translate(' + xpos + ',' + ypos + ')';
5720                     });
5721
5722                 //position legend as far right as possible within the total width
5723                 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5724
5725                 height = margin.top + margin.bottom + ypos + 15;
5726             }
5727
5728             if(vers == 'furious') {
5729                 // Size rectangles after text is placed
5730                 seriesShape
5731                     .attr('width', function(d,i) {
5732                         return seriesText[0][i].getComputedTextLength() + 27;
5733                     })
5734                     .attr('height', 18)
5735                     .attr('y', -9)
5736                     .attr('x', -15);
5737
5738                 // The background for the expanded legend (UI)
5739                 gEnter.insert('rect',':first-child')
5740                     .attr('class', 'nv-legend-bg')
5741                     .attr('fill', '#eee')
5742                     // .attr('stroke', '#444')
5743                     .attr('opacity',0);
5744
5745                 var seriesBG = g.select('.nv-legend-bg');
5746
5747                 seriesBG
5748                 .transition().duration(300)
5749                     .attr('x', -versPadding )
5750                     .attr('width', legendWidth + versPadding - 12)
5751                     .attr('height', height + 10)
5752                     .attr('y', -margin.top - 10)
5753                     .attr('opacity', expanded ? 1 : 0);
5754
5755
5756             }
5757
5758             seriesShape
5759                 .style('fill', setBGColor)
5760                 .style('fill-opacity', setBGOpacity)
5761                 .style('stroke', setBGColor);
5762         });
5763
5764         function setTextColor(d,i) {
5765             if(vers != 'furious') return '#000';
5766             if(expanded) {
5767                 return d.disengaged ? '#000' : '#fff';
5768             } else if (!expanded) {
5769                 if(!d.color) d.color = color(d,i);
5770                 return !!d.disabled ? d.color : '#fff';
5771             }
5772         }
5773
5774         function setBGColor(d,i) {
5775             if(expanded && vers == 'furious') {
5776                 return d.disengaged ? '#eee' : d.color || color(d,i);
5777             } else {
5778                 return d.color || color(d,i);
5779             }
5780         }
5781
5782
5783         function setBGOpacity(d,i) {
5784             if(expanded && vers == 'furious') {
5785                 return 1;
5786             } else {
5787                 return !!d.disabled ? 0 : 1;
5788             }
5789         }
5790
5791         return chart;
5792     }
5793
5794     //============================================================
5795     // Expose Public Variables
5796     //------------------------------------------------------------
5797
5798     chart.dispatch = dispatch;
5799     chart.options = nv.utils.optionsFunc.bind(chart);
5800
5801     chart._options = Object.create({}, {
5802         // simple options, just get/set the necessary values
5803         width:      {get: function(){return width;}, set: function(_){width=_;}},
5804         height:     {get: function(){return height;}, set: function(_){height=_;}},
5805         key:        {get: function(){return getKey;}, set: function(_){getKey=_;}},
5806         align:      {get: function(){return align;}, set: function(_){align=_;}},
5807         rightAlign:    {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
5808         padding:       {get: function(){return padding;}, set: function(_){padding=_;}},
5809         updateState:   {get: function(){return updateState;}, set: function(_){updateState=_;}},
5810         radioButtonMode:    {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
5811         expanded:   {get: function(){return expanded;}, set: function(_){expanded=_;}},
5812         vers:   {get: function(){return vers;}, set: function(_){vers=_;}},
5813
5814         // options that require extra logic in the setter
5815         margin: {get: function(){return margin;}, set: function(_){
5816             margin.top    = _.top    !== undefined ? _.top    : margin.top;
5817             margin.right  = _.right  !== undefined ? _.right  : margin.right;
5818             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5819             margin.left   = _.left   !== undefined ? _.left   : margin.left;
5820         }},
5821         color:  {get: function(){return color;}, set: function(_){
5822             color = nv.utils.getColor(_);
5823         }}
5824     });
5825
5826     nv.utils.initOptions(chart);
5827
5828     return chart;
5829 };
5830
5831 nv.models.line = function() {
5832     "use strict";
5833     //============================================================
5834     // Public Variables with Default Settings
5835     //------------------------------------------------------------
5836
5837     var  scatter = nv.models.scatter()
5838         ;
5839
5840     var margin = {top: 0, right: 0, bottom: 0, left: 0}
5841         , width = 960
5842         , height = 500
5843         , container = null
5844         , strokeWidth = 1.5
5845         , color = nv.utils.defaultColor() // a function that returns a color
5846         , getX = function(d) { return d.x } // accessor to get the x value from a data point
5847         , getY = function(d) { return d.y } // accessor to get the y value from a data point
5848         , 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
5849         , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5850         , clipEdge = false // if true, masks lines within x and y scale
5851         , x //can be accessed via chart.xScale()
5852         , y //can be accessed via chart.yScale()
5853         , interpolate = "linear" // controls the line interpolation
5854         , duration = 250
5855         , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
5856         ;
5857
5858     scatter
5859         .pointSize(16) // default size
5860         .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5861     ;
5862
5863     //============================================================
5864
5865
5866     //============================================================
5867     // Private Variables
5868     //------------------------------------------------------------
5869
5870     var x0, y0 //used to store previous scales
5871         , renderWatch = nv.utils.renderWatch(dispatch, duration)
5872         ;
5873
5874     //============================================================
5875
5876
5877     function chart(selection) {
5878         renderWatch.reset();
5879         renderWatch.models(scatter);
5880         selection.each(function(data) {
5881             container = d3.select(this);
5882             var availableWidth = nv.utils.availableWidth(width, container, margin),
5883                 availableHeight = nv.utils.availableHeight(height, container, margin);
5884             nv.utils.initSVG(container);
5885
5886             // Setup Scales
5887             x = scatter.xScale();
5888             y = scatter.yScale();
5889
5890             x0 = x0 || x;
5891             y0 = y0 || y;
5892
5893             // Setup containers and skeleton of chart
5894             var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5895             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5896             var defsEnter = wrapEnter.append('defs');
5897             var gEnter = wrapEnter.append('g');
5898             var g = wrap.select('g');
5899
5900             gEnter.append('g').attr('class', 'nv-groups');
5901             gEnter.append('g').attr('class', 'nv-scatterWrap');
5902
5903             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5904
5905             scatter
5906                 .width(availableWidth)
5907                 .height(availableHeight);
5908
5909             var scatterWrap = wrap.select('.nv-scatterWrap');
5910             scatterWrap.call(scatter);
5911
5912             defsEnter.append('clipPath')
5913                 .attr('id', 'nv-edge-clip-' + scatter.id())
5914                 .append('rect');
5915
5916             wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5917                 .attr('width', availableWidth)
5918                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
5919
5920             g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5921             scatterWrap
5922                 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5923
5924             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5925                 .data(function(d) { return d }, function(d) { return d.key });
5926             groups.enter().append('g')
5927                 .style('stroke-opacity', 1e-6)
5928                 .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
5929                 .style('fill-opacity', 1e-6);
5930
5931             groups.exit().remove();
5932
5933             groups
5934                 .attr('class', function(d,i) {
5935                     return (d.classed || '') + ' nv-group nv-series-' + i;
5936                 })
5937                 .classed('hover', function(d) { return d.hover })
5938                 .style('fill', function(d,i){ return color(d, i) })
5939                 .style('stroke', function(d,i){ return color(d, i)});
5940             groups.watchTransition(renderWatch, 'line: groups')
5941                 .style('stroke-opacity', 1)
5942                 .style('fill-opacity', function(d) { return d.fillOpacity || .5});
5943
5944             var areaPaths = groups.selectAll('path.nv-area')
5945                 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5946             areaPaths.enter().append('path')
5947                 .attr('class', 'nv-area')
5948                 .attr('d', function(d) {
5949                     return d3.svg.area()
5950                         .interpolate(interpolate)
5951                         .defined(defined)
5952                         .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5953                         .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5954                         .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5955                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5956                         .apply(this, [d.values])
5957                 });
5958             groups.exit().selectAll('path.nv-area')
5959                 .remove();
5960
5961             areaPaths.watchTransition(renderWatch, 'line: areaPaths')
5962                 .attr('d', function(d) {
5963                     return d3.svg.area()
5964                         .interpolate(interpolate)
5965                         .defined(defined)
5966                         .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5967                         .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5968                         .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5969                         //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5970                         .apply(this, [d.values])
5971                 });
5972
5973             var linePaths = groups.selectAll('path.nv-line')
5974                 .data(function(d) { return [d.values] });
5975
5976             linePaths.enter().append('path')
5977                 .attr('class', 'nv-line')
5978                 .attr('d',
5979                     d3.svg.line()
5980                     .interpolate(interpolate)
5981                     .defined(defined)
5982                     .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5983                     .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5984             );
5985
5986             linePaths.watchTransition(renderWatch, 'line: linePaths')
5987                 .attr('d',
5988                     d3.svg.line()
5989                     .interpolate(interpolate)
5990                     .defined(defined)
5991                     .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5992                     .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5993             );
5994
5995             //store old scales for use in transitions on update
5996             x0 = x.copy();
5997             y0 = y.copy();
5998         });
5999         renderWatch.renderEnd('line immediate');
6000         return chart;
6001     }
6002
6003
6004     //============================================================
6005     // Expose Public Variables
6006     //------------------------------------------------------------
6007
6008     chart.dispatch = dispatch;
6009     chart.scatter = scatter;
6010     // Pass through events
6011     scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
6012     scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
6013     scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
6014
6015     chart.options = nv.utils.optionsFunc.bind(chart);
6016
6017     chart._options = Object.create({}, {
6018         // simple options, just get/set the necessary values
6019         width:      {get: function(){return width;}, set: function(_){width=_;}},
6020         height:     {get: function(){return height;}, set: function(_){height=_;}},
6021         defined: {get: function(){return defined;}, set: function(_){defined=_;}},
6022         interpolate:      {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
6023         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
6024
6025         // options that require extra logic in the setter
6026         margin: {get: function(){return margin;}, set: function(_){
6027             margin.top    = _.top    !== undefined ? _.top    : margin.top;
6028             margin.right  = _.right  !== undefined ? _.right  : margin.right;
6029             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6030             margin.left   = _.left   !== undefined ? _.left   : margin.left;
6031         }},
6032         duration: {get: function(){return duration;}, set: function(_){
6033             duration = _;
6034             renderWatch.reset(duration);
6035             scatter.duration(duration);
6036         }},
6037         isArea: {get: function(){return isArea;}, set: function(_){
6038             isArea = d3.functor(_);
6039         }},
6040         x: {get: function(){return getX;}, set: function(_){
6041             getX = _;
6042             scatter.x(_);
6043         }},
6044         y: {get: function(){return getY;}, set: function(_){
6045             getY = _;
6046             scatter.y(_);
6047         }},
6048         color:  {get: function(){return color;}, set: function(_){
6049             color = nv.utils.getColor(_);
6050             scatter.color(color);
6051         }}
6052     });
6053
6054     nv.utils.inheritOptions(chart, scatter);
6055     nv.utils.initOptions(chart);
6056
6057     return chart;
6058 };
6059 nv.models.lineChart = function() {
6060     "use strict";
6061
6062     //============================================================
6063     // Public Variables with Default Settings
6064     //------------------------------------------------------------
6065
6066     var lines = nv.models.line()
6067         , xAxis = nv.models.axis()
6068         , yAxis = nv.models.axis()
6069         , legend = nv.models.legend()
6070         , interactiveLayer = nv.interactiveGuideline()
6071         , tooltip = nv.models.tooltip()
6072         ;
6073
6074     var margin = {top: 30, right: 20, bottom: 50, left: 60}
6075         , color = nv.utils.defaultColor()
6076         , width = null
6077         , height = null
6078         , showLegend = true
6079         , showXAxis = true
6080         , showYAxis = true
6081         , rightAlignYAxis = false
6082         , useInteractiveGuideline = false
6083         , x
6084         , y
6085         , state = nv.utils.state()
6086         , defaultState = null
6087         , noData = null
6088         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
6089         , duration = 250
6090         ;
6091
6092     // set options on sub-objects for this chart
6093     xAxis.orient('bottom').tickPadding(7);
6094     yAxis.orient(rightAlignYAxis ? 'right' : 'left');
6095     tooltip.valueFormatter(function(d, i) {
6096         return yAxis.tickFormat()(d, i);
6097     }).headerFormatter(function(d, i) {
6098         return xAxis.tickFormat()(d, i);
6099     });
6100
6101
6102     //============================================================
6103     // Private Variables
6104     //------------------------------------------------------------
6105
6106     var renderWatch = nv.utils.renderWatch(dispatch, duration);
6107
6108     var stateGetter = function(data) {
6109         return function(){
6110             return {
6111                 active: data.map(function(d) { return !d.disabled })
6112             };
6113         }
6114     };
6115
6116     var stateSetter = function(data) {
6117         return function(state) {
6118             if (state.active !== undefined)
6119                 data.forEach(function(series,i) {
6120                     series.disabled = !state.active[i];
6121                 });
6122         }
6123     };
6124
6125     function chart(selection) {
6126         renderWatch.reset();
6127         renderWatch.models(lines);
6128         if (showXAxis) renderWatch.models(xAxis);
6129         if (showYAxis) renderWatch.models(yAxis);
6130
6131         selection.each(function(data) {
6132             var container = d3.select(this),
6133                 that = this;
6134             nv.utils.initSVG(container);
6135             var availableWidth = nv.utils.availableWidth(width, container, margin),
6136                 availableHeight = nv.utils.availableHeight(height, container, margin);
6137
6138             chart.update = function() {
6139                 if (duration === 0)
6140                     container.call(chart);
6141                 else
6142                     container.transition().duration(duration).call(chart)
6143             };
6144             chart.container = this;
6145
6146             state
6147                 .setter(stateSetter(data), chart.update)
6148                 .getter(stateGetter(data))
6149                 .update();
6150
6151             // DEPRECATED set state.disableddisabled
6152             state.disabled = data.map(function(d) { return !!d.disabled });
6153
6154             if (!defaultState) {
6155                 var key;
6156                 defaultState = {};
6157                 for (key in state) {
6158                     if (state[key] instanceof Array)
6159                         defaultState[key] = state[key].slice(0);
6160                     else
6161                         defaultState[key] = state[key];
6162                 }
6163             }
6164
6165             // Display noData message if there's nothing to show.
6166             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6167                 nv.utils.noData(chart, container)
6168                 return chart;
6169             } else {
6170                 container.selectAll('.nv-noData').remove();
6171             }
6172
6173
6174             // Setup Scales
6175             x = lines.xScale();
6176             y = lines.yScale();
6177
6178             // Setup containers and skeleton of chart
6179             var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
6180             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
6181             var g = wrap.select('g');
6182
6183             gEnter.append("rect").style("opacity",0);
6184             gEnter.append('g').attr('class', 'nv-x nv-axis');
6185             gEnter.append('g').attr('class', 'nv-y nv-axis');
6186             gEnter.append('g').attr('class', 'nv-linesWrap');
6187             gEnter.append('g').attr('class', 'nv-legendWrap');
6188             gEnter.append('g').attr('class', 'nv-interactive');
6189
6190             g.select("rect")
6191                 .attr("width",availableWidth)
6192                 .attr("height",(availableHeight > 0) ? availableHeight : 0);
6193
6194             // Legend
6195             if (showLegend) {
6196                 legend.width(availableWidth);
6197
6198                 g.select('.nv-legendWrap')
6199                     .datum(data)
6200                     .call(legend);
6201
6202                 if ( margin.top != legend.height()) {
6203                     margin.top = legend.height();
6204                     availableHeight = nv.utils.availableHeight(height, container, margin);
6205                 }
6206
6207                 wrap.select('.nv-legendWrap')
6208                     .attr('transform', 'translate(0,' + (-margin.top) +')')
6209             }
6210
6211             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6212
6213             if (rightAlignYAxis) {
6214                 g.select(".nv-y.nv-axis")
6215                     .attr("transform", "translate(" + availableWidth + ",0)");
6216             }
6217
6218             //Set up interactive layer
6219             if (useInteractiveGuideline) {
6220                 interactiveLayer
6221                     .width(availableWidth)
6222                     .height(availableHeight)
6223                     .margin({left:margin.left, top:margin.top})
6224                     .svgContainer(container)
6225                     .xScale(x);
6226                 wrap.select(".nv-interactive").call(interactiveLayer);
6227             }
6228
6229             lines
6230                 .width(availableWidth)
6231                 .height(availableHeight)
6232                 .color(data.map(function(d,i) {
6233                     return d.color || color(d, i);
6234                 }).filter(function(d,i) { return !data[i].disabled }));
6235
6236
6237             var linesWrap = g.select('.nv-linesWrap')
6238                 .datum(data.filter(function(d) { return !d.disabled }));
6239
6240             linesWrap.call(lines);
6241
6242             // Setup Axes
6243             if (showXAxis) {
6244                 xAxis
6245                     .scale(x)
6246                     ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
6247                     .tickSize(-availableHeight, 0);
6248
6249                 g.select('.nv-x.nv-axis')
6250                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
6251                 g.select('.nv-x.nv-axis')
6252                     .call(xAxis);
6253             }
6254
6255             if (showYAxis) {
6256                 yAxis
6257                     .scale(y)
6258                     ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
6259                     .tickSize( -availableWidth, 0);
6260
6261                 g.select('.nv-y.nv-axis')
6262                     .call(yAxis);
6263             }
6264
6265             //============================================================
6266             // Event Handling/Dispatching (in chart's scope)
6267             //------------------------------------------------------------
6268
6269             legend.dispatch.on('stateChange', function(newState) {
6270                 for (var key in newState)
6271                     state[key] = newState[key];
6272                 dispatch.stateChange(state);
6273                 chart.update();
6274             });
6275
6276             interactiveLayer.dispatch.on('elementMousemove', function(e) {
6277                 lines.clearHighlights();
6278                 var singlePoint, pointIndex, pointXLocation, allData = [];
6279                 data
6280                     .filter(function(series, i) {
6281                         series.seriesIndex = i;
6282                         return !series.disabled;
6283                     })
6284                     .forEach(function(series,i) {
6285                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
6286                         var point = series.values[pointIndex];
6287                         var pointYValue = chart.y()(point, pointIndex);
6288                         if (pointYValue != null) {
6289                             lines.highlightPoint(i, pointIndex, true);
6290                         }
6291                         if (point === undefined) return;
6292                         if (singlePoint === undefined) singlePoint = point;
6293                         if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
6294                         allData.push({
6295                             key: series.key,
6296                             value: pointYValue,
6297                             color: color(series,series.seriesIndex)
6298                         });
6299                     });
6300                 //Highlight the tooltip entry based on which point the mouse is closest to.
6301                 if (allData.length > 2) {
6302                     var yValue = chart.yScale().invert(e.mouseY);
6303                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
6304                     var threshold = 0.03 * domainExtent;
6305                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
6306                     if (indexToHighlight !== null)
6307                         allData[indexToHighlight].highlight = true;
6308                 }
6309
6310                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
6311                 interactiveLayer.tooltip
6312                     .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
6313                     .chartContainer(that.parentNode)
6314                     .valueFormatter(function(d,i) {
6315                         return d == null ? "N/A" : yAxis.tickFormat()(d);
6316                     })
6317                     .data({
6318                         value: xValue,
6319                         index: pointIndex,
6320                         series: allData
6321                     })();
6322
6323                 interactiveLayer.renderGuideLine(pointXLocation);
6324
6325             });
6326
6327             interactiveLayer.dispatch.on('elementClick', function(e) {
6328                 var pointXLocation, allData = [];
6329
6330                 data.filter(function(series, i) {
6331                     series.seriesIndex = i;
6332                     return !series.disabled;
6333                 }).forEach(function(series) {
6334                     var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
6335                     var point = series.values[pointIndex];
6336                     if (typeof point === 'undefined') return;
6337                     if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
6338                     var yPos = chart.yScale()(chart.y()(point,pointIndex));
6339                     allData.push({
6340                         point: point,
6341                         pointIndex: pointIndex,
6342                         pos: [pointXLocation, yPos],
6343                         seriesIndex: series.seriesIndex,
6344                         series: series
6345                     });
6346                 });
6347
6348                 lines.dispatch.elementClick(allData);
6349             });
6350
6351             interactiveLayer.dispatch.on("elementMouseout",function(e) {
6352                 lines.clearHighlights();
6353             });
6354
6355             dispatch.on('changeState', function(e) {
6356                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
6357                     data.forEach(function(series,i) {
6358                         series.disabled = e.disabled[i];
6359                     });
6360
6361                     state.disabled = e.disabled;
6362                 }
6363
6364                 chart.update();
6365             });
6366
6367         });
6368
6369         renderWatch.renderEnd('lineChart immediate');
6370         return chart;
6371     }
6372
6373     //============================================================
6374     // Event Handling/Dispatching (out of chart's scope)
6375     //------------------------------------------------------------
6376
6377     lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6378         tooltip.data(evt).position(evt.pos).hidden(false);
6379     });
6380
6381     lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6382         tooltip.hidden(true)
6383     });
6384
6385     //============================================================
6386     // Expose Public Variables
6387     //------------------------------------------------------------
6388
6389     // expose chart's sub-components
6390     chart.dispatch = dispatch;
6391     chart.lines = lines;
6392     chart.legend = legend;
6393     chart.xAxis = xAxis;
6394     chart.yAxis = yAxis;
6395     chart.interactiveLayer = interactiveLayer;
6396     chart.tooltip = tooltip;
6397
6398     chart.dispatch = dispatch;
6399     chart.options = nv.utils.optionsFunc.bind(chart);
6400
6401     chart._options = Object.create({}, {
6402         // simple options, just get/set the necessary values
6403         width:      {get: function(){return width;}, set: function(_){width=_;}},
6404         height:     {get: function(){return height;}, set: function(_){height=_;}},
6405         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6406         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
6407         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
6408         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
6409         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
6410
6411         // deprecated options
6412         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
6413             // deprecated after 1.7.1
6414             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
6415             tooltip.enabled(!!_);
6416         }},
6417         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
6418             // deprecated after 1.7.1
6419             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
6420             tooltip.contentGenerator(_);
6421         }},
6422
6423         // options that require extra logic in the setter
6424         margin: {get: function(){return margin;}, set: function(_){
6425             margin.top    = _.top    !== undefined ? _.top    : margin.top;
6426             margin.right  = _.right  !== undefined ? _.right  : margin.right;
6427             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6428             margin.left   = _.left   !== undefined ? _.left   : margin.left;
6429         }},
6430         duration: {get: function(){return duration;}, set: function(_){
6431             duration = _;
6432             renderWatch.reset(duration);
6433             lines.duration(duration);
6434             xAxis.duration(duration);
6435             yAxis.duration(duration);
6436         }},
6437         color:  {get: function(){return color;}, set: function(_){
6438             color = nv.utils.getColor(_);
6439             legend.color(color);
6440             lines.color(color);
6441         }},
6442         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
6443             rightAlignYAxis = _;
6444             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
6445         }},
6446         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
6447             useInteractiveGuideline = _;
6448             if (useInteractiveGuideline) {
6449                 lines.interactive(false);
6450                 lines.useVoronoi(false);
6451             }
6452         }}
6453     });
6454
6455     nv.utils.inheritOptions(chart, lines);
6456     nv.utils.initOptions(chart);
6457
6458     return chart;
6459 };
6460 nv.models.linePlusBarChart = function() {
6461     "use strict";
6462
6463     //============================================================
6464     // Public Variables with Default Settings
6465     //------------------------------------------------------------
6466
6467     var lines = nv.models.line()
6468         , lines2 = nv.models.line()
6469         , bars = nv.models.historicalBar()
6470         , bars2 = nv.models.historicalBar()
6471         , xAxis = nv.models.axis()
6472         , x2Axis = nv.models.axis()
6473         , y1Axis = nv.models.axis()
6474         , y2Axis = nv.models.axis()
6475         , y3Axis = nv.models.axis()
6476         , y4Axis = nv.models.axis()
6477         , legend = nv.models.legend()
6478         , brush = d3.svg.brush()
6479         , tooltip = nv.models.tooltip()
6480         ;
6481
6482     var margin = {top: 30, right: 30, bottom: 30, left: 60}
6483         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6484         , width = null
6485         , height = null
6486         , getX = function(d) { return d.x }
6487         , getY = function(d) { return d.y }
6488         , color = nv.utils.defaultColor()
6489         , showLegend = true
6490         , focusEnable = true
6491         , focusShowAxisY = false
6492         , focusShowAxisX = true
6493         , focusHeight = 50
6494         , extent
6495         , brushExtent = null
6496         , x
6497         , x2
6498         , y1
6499         , y2
6500         , y3
6501         , y4
6502         , noData = null
6503         , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
6504         , transitionDuration = 0
6505         , state = nv.utils.state()
6506         , defaultState = null
6507         , legendLeftAxisHint = ' (left axis)'
6508         , legendRightAxisHint = ' (right axis)'
6509         ;
6510
6511     lines.clipEdge(true);
6512     lines2.interactive(false);
6513     xAxis.orient('bottom').tickPadding(5);
6514     y1Axis.orient('left');
6515     y2Axis.orient('right');
6516     x2Axis.orient('bottom').tickPadding(5);
6517     y3Axis.orient('left');
6518     y4Axis.orient('right');
6519
6520     tooltip.headerEnabled(true).headerFormatter(function(d, i) {
6521         return xAxis.tickFormat()(d, i);
6522     });
6523
6524     //============================================================
6525     // Private Variables
6526     //------------------------------------------------------------
6527
6528     var stateGetter = function(data) {
6529         return function(){
6530             return {
6531                 active: data.map(function(d) { return !d.disabled })
6532             };
6533         }
6534     };
6535
6536     var stateSetter = function(data) {
6537         return function(state) {
6538             if (state.active !== undefined)
6539                 data.forEach(function(series,i) {
6540                     series.disabled = !state.active[i];
6541                 });
6542         }
6543     };
6544
6545     function chart(selection) {
6546         selection.each(function(data) {
6547             var container = d3.select(this),
6548                 that = this;
6549             nv.utils.initSVG(container);
6550             var availableWidth = nv.utils.availableWidth(width, container, margin),
6551                 availableHeight1 = nv.utils.availableHeight(height, container, margin)
6552                     - (focusEnable ? focusHeight : 0),
6553                 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
6554
6555             chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6556             chart.container = this;
6557
6558             state
6559                 .setter(stateSetter(data), chart.update)
6560                 .getter(stateGetter(data))
6561                 .update();
6562
6563             // DEPRECATED set state.disableddisabled
6564             state.disabled = data.map(function(d) { return !!d.disabled });
6565
6566             if (!defaultState) {
6567                 var key;
6568                 defaultState = {};
6569                 for (key in state) {
6570                     if (state[key] instanceof Array)
6571                         defaultState[key] = state[key].slice(0);
6572                     else
6573                         defaultState[key] = state[key];
6574                 }
6575             }
6576
6577             // Display No Data message if there's nothing to show.
6578             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6579                 nv.utils.noData(chart, container)
6580                 return chart;
6581             } else {
6582                 container.selectAll('.nv-noData').remove();
6583             }
6584
6585             // Setup Scales
6586             var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6587             var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6588
6589             x = bars.xScale();
6590             x2 = x2Axis.scale();
6591             y1 = bars.yScale();
6592             y2 = lines.yScale();
6593             y3 = bars2.yScale();
6594             y4 = lines2.yScale();
6595
6596             var series1 = data
6597                 .filter(function(d) { return !d.disabled && d.bar })
6598                 .map(function(d) {
6599                     return d.values.map(function(d,i) {
6600                         return { x: getX(d,i), y: getY(d,i) }
6601                     })
6602                 });
6603
6604             var series2 = data
6605                 .filter(function(d) { return !d.disabled && !d.bar })
6606                 .map(function(d) {
6607                     return d.values.map(function(d,i) {
6608                         return { x: getX(d,i), y: getY(d,i) }
6609                     })
6610                 });
6611
6612             x.range([0, availableWidth]);
6613
6614             x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6615                 .range([0, availableWidth]);
6616
6617             // Setup containers and skeleton of chart
6618             var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6619             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6620             var g = wrap.select('g');
6621
6622             gEnter.append('g').attr('class', 'nv-legendWrap');
6623
6624             // this is the main chart
6625             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6626             focusEnter.append('g').attr('class', 'nv-x nv-axis');
6627             focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6628             focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6629             focusEnter.append('g').attr('class', 'nv-barsWrap');
6630             focusEnter.append('g').attr('class', 'nv-linesWrap');
6631
6632             // context chart is where you can focus in
6633             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6634             contextEnter.append('g').attr('class', 'nv-x nv-axis');
6635             contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6636             contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6637             contextEnter.append('g').attr('class', 'nv-barsWrap');
6638             contextEnter.append('g').attr('class', 'nv-linesWrap');
6639             contextEnter.append('g').attr('class', 'nv-brushBackground');
6640             contextEnter.append('g').attr('class', 'nv-x nv-brush');
6641
6642             //============================================================
6643             // Legend
6644             //------------------------------------------------------------
6645
6646             if (showLegend) {
6647                 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
6648                 var legendXPosition = legend.align() ? legendWidth : 0;
6649
6650                 legend.width(legendWidth);
6651
6652                 g.select('.nv-legendWrap')
6653                     .datum(data.map(function(series) {
6654                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6655                         series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
6656                         return series;
6657                     }))
6658                     .call(legend);
6659
6660                 if ( margin.top != legend.height()) {
6661                     margin.top = legend.height();
6662                     // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
6663                     availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
6664                 }
6665
6666                 g.select('.nv-legendWrap')
6667                     .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
6668             }
6669
6670             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6671
6672             //============================================================
6673             // Context chart (focus chart) components
6674             //------------------------------------------------------------
6675
6676             // hide or show the focus context chart
6677             g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
6678
6679             bars2
6680                 .width(availableWidth)
6681                 .height(availableHeight2)
6682                 .color(data.map(function (d, i) {
6683                     return d.color || color(d, i);
6684                 }).filter(function (d, i) {
6685                     return !data[i].disabled && data[i].bar
6686                 }));
6687             lines2
6688                 .width(availableWidth)
6689                 .height(availableHeight2)
6690                 .color(data.map(function (d, i) {
6691                     return d.color || color(d, i);
6692                 }).filter(function (d, i) {
6693                     return !data[i].disabled && !data[i].bar
6694                 }));
6695
6696             var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6697                 .datum(dataBars.length ? dataBars : [
6698                     {values: []}
6699                 ]);
6700             var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6701                 .datum(!dataLines[0].disabled ? dataLines : [
6702                     {values: []}
6703                 ]);
6704
6705             g.select('.nv-context')
6706                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
6707
6708             bars2Wrap.transition().call(bars2);
6709             lines2Wrap.transition().call(lines2);
6710
6711             // context (focus chart) axis controls
6712             if (focusShowAxisX) {
6713                 x2Axis
6714                     ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
6715                     .tickSize(-availableHeight2, 0);
6716                 g.select('.nv-context .nv-x.nv-axis')
6717                     .attr('transform', 'translate(0,' + y3.range()[0] + ')');
6718                 g.select('.nv-context .nv-x.nv-axis').transition()
6719                     .call(x2Axis);
6720             }
6721
6722             if (focusShowAxisY) {
6723                 y3Axis
6724                     .scale(y3)
6725                     ._ticks( availableHeight2 / 36 )
6726                     .tickSize( -availableWidth, 0);
6727                 y4Axis
6728                     .scale(y4)
6729                     ._ticks( availableHeight2 / 36 )
6730                     .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6731
6732                 g.select('.nv-context .nv-y3.nv-axis')
6733                     .style('opacity', dataBars.length ? 1 : 0)
6734                     .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6735                 g.select('.nv-context .nv-y2.nv-axis')
6736                     .style('opacity', dataLines.length ? 1 : 0)
6737                     .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6738
6739                 g.select('.nv-context .nv-y1.nv-axis').transition()
6740                     .call(y3Axis);
6741                 g.select('.nv-context .nv-y2.nv-axis').transition()
6742                     .call(y4Axis);
6743             }
6744
6745             // Setup Brush
6746             brush.x(x2).on('brush', onBrush);
6747
6748             if (brushExtent) brush.extent(brushExtent);
6749
6750             var brushBG = g.select('.nv-brushBackground').selectAll('g')
6751                 .data([brushExtent || brush.extent()]);
6752
6753             var brushBGenter = brushBG.enter()
6754                 .append('g');
6755
6756             brushBGenter.append('rect')
6757                 .attr('class', 'left')
6758                 .attr('x', 0)
6759                 .attr('y', 0)
6760                 .attr('height', availableHeight2);
6761
6762             brushBGenter.append('rect')
6763                 .attr('class', 'right')
6764                 .attr('x', 0)
6765                 .attr('y', 0)
6766                 .attr('height', availableHeight2);
6767
6768             var gBrush = g.select('.nv-x.nv-brush')
6769                 .call(brush);
6770             gBrush.selectAll('rect')
6771                 //.attr('y', -5)
6772                 .attr('height', availableHeight2);
6773             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6774
6775             //============================================================
6776             // Event Handling/Dispatching (in chart's scope)
6777             //------------------------------------------------------------
6778
6779             legend.dispatch.on('stateChange', function(newState) {
6780                 for (var key in newState)
6781                     state[key] = newState[key];
6782                 dispatch.stateChange(state);
6783                 chart.update();
6784             });
6785
6786             // Update chart from a state object passed to event handler
6787             dispatch.on('changeState', function(e) {
6788                 if (typeof e.disabled !== 'undefined') {
6789                     data.forEach(function(series,i) {
6790                         series.disabled = e.disabled[i];
6791                     });
6792                     state.disabled = e.disabled;
6793                 }
6794                 chart.update();
6795             });
6796
6797             //============================================================
6798             // Functions
6799             //------------------------------------------------------------
6800
6801             // Taken from crossfilter (http://square.github.com/crossfilter/)
6802             function resizePath(d) {
6803                 var e = +(d == 'e'),
6804                     x = e ? 1 : -1,
6805                     y = availableHeight2 / 3;
6806                 return 'M' + (.5 * x) + ',' + y
6807                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6808                     + 'V' + (2 * y - 6)
6809                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6810                     + 'Z'
6811                     + 'M' + (2.5 * x) + ',' + (y + 8)
6812                     + 'V' + (2 * y - 8)
6813                     + 'M' + (4.5 * x) + ',' + (y + 8)
6814                     + 'V' + (2 * y - 8);
6815             }
6816
6817
6818             function updateBrushBG() {
6819                 if (!brush.empty()) brush.extent(brushExtent);
6820                 brushBG
6821                     .data([brush.empty() ? x2.domain() : brushExtent])
6822                     .each(function(d,i) {
6823                         var leftWidth = x2(d[0]) - x2.range()[0],
6824                             rightWidth = x2.range()[1] - x2(d[1]);
6825                         d3.select(this).select('.left')
6826                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
6827
6828                         d3.select(this).select('.right')
6829                             .attr('x', x2(d[1]))
6830                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
6831                     });
6832             }
6833
6834             function onBrush() {
6835                 brushExtent = brush.empty() ? null : brush.extent();
6836                 extent = brush.empty() ? x2.domain() : brush.extent();
6837                 dispatch.brush({extent: extent, brush: brush});
6838                 updateBrushBG();
6839
6840                 // Prepare Main (Focus) Bars and Lines
6841                 bars
6842                     .width(availableWidth)
6843                     .height(availableHeight1)
6844                     .color(data.map(function(d,i) {
6845                         return d.color || color(d, i);
6846                     }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6847
6848                 lines
6849                     .width(availableWidth)
6850                     .height(availableHeight1)
6851                     .color(data.map(function(d,i) {
6852                         return d.color || color(d, i);
6853                     }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6854
6855                 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
6856                     .datum(!dataBars.length ? [{values:[]}] :
6857                         dataBars
6858                             .map(function(d,i) {
6859                                 return {
6860                                     key: d.key,
6861                                     values: d.values.filter(function(d,i) {
6862                                         return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
6863                                     })
6864                                 }
6865                             })
6866                 );
6867
6868                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6869                     .datum(dataLines[0].disabled ? [{values:[]}] :
6870                         dataLines
6871                             .map(function(d,i) {
6872                                 return {
6873                                     area: d.area,
6874                                     fillOpacity: d.fillOpacity,
6875                                     key: d.key,
6876                                     values: d.values.filter(function(d,i) {
6877                                         return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6878                                     })
6879                                 }
6880                             })
6881                 );
6882
6883                 // Update Main (Focus) X Axis
6884                 if (dataBars.length) {
6885                     x = bars.xScale();
6886                 } else {
6887                     x = lines.xScale();
6888                 }
6889
6890                 xAxis
6891                     .scale(x)
6892                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6893                     .tickSize(-availableHeight1, 0);
6894
6895                 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
6896
6897                 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
6898                     .call(xAxis);
6899
6900                 // Update Main (Focus) Bars and Lines
6901                 focusBarsWrap.transition().duration(transitionDuration).call(bars);
6902                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6903
6904                 // Setup and Update Main (Focus) Y Axes
6905                 g.select('.nv-focus .nv-x.nv-axis')
6906                     .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6907
6908                 y1Axis
6909                     .scale(y1)
6910                     ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
6911                     .tickSize(-availableWidth, 0);
6912                 y2Axis
6913                     .scale(y2)
6914                     ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
6915                     .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6916
6917                 g.select('.nv-focus .nv-y1.nv-axis')
6918                     .style('opacity', dataBars.length ? 1 : 0);
6919                 g.select('.nv-focus .nv-y2.nv-axis')
6920                     .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
6921                     .attr('transform', 'translate(' + x.range()[1] + ',0)');
6922
6923                 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
6924                     .call(y1Axis);
6925                 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
6926                     .call(y2Axis);
6927             }
6928
6929             onBrush();
6930
6931         });
6932
6933         return chart;
6934     }
6935
6936     //============================================================
6937     // Event Handling/Dispatching (out of chart's scope)
6938     //------------------------------------------------------------
6939
6940     lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6941         tooltip
6942             .duration(100)
6943             .valueFormatter(function(d, i) {
6944                 return y2Axis.tickFormat()(d, i);
6945             })
6946             .data(evt)
6947             .position(evt.pos)
6948             .hidden(false);
6949     });
6950
6951     lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6952         tooltip.hidden(true)
6953     });
6954
6955     bars.dispatch.on('elementMouseover.tooltip', function(evt) {
6956         evt.value = chart.x()(evt.data);
6957         evt['series'] = {
6958             value: chart.y()(evt.data),
6959             color: evt.color
6960         };
6961         tooltip
6962             .duration(0)
6963             .valueFormatter(function(d, i) {
6964                 return y1Axis.tickFormat()(d, i);
6965             })
6966             .data(evt)
6967             .hidden(false);
6968     });
6969
6970     bars.dispatch.on('elementMouseout.tooltip', function(evt) {
6971         tooltip.hidden(true);
6972     });
6973
6974     bars.dispatch.on('elementMousemove.tooltip', function(evt) {
6975         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
6976     });
6977
6978     //============================================================
6979
6980
6981     //============================================================
6982     // Expose Public Variables
6983     //------------------------------------------------------------
6984
6985     // expose chart's sub-components
6986     chart.dispatch = dispatch;
6987     chart.legend = legend;
6988     chart.lines = lines;
6989     chart.lines2 = lines2;
6990     chart.bars = bars;
6991     chart.bars2 = bars2;
6992     chart.xAxis = xAxis;
6993     chart.x2Axis = x2Axis;
6994     chart.y1Axis = y1Axis;
6995     chart.y2Axis = y2Axis;
6996     chart.y3Axis = y3Axis;
6997     chart.y4Axis = y4Axis;
6998     chart.tooltip = tooltip;
6999
7000     chart.options = nv.utils.optionsFunc.bind(chart);
7001
7002     chart._options = Object.create({}, {
7003         // simple options, just get/set the necessary values
7004         width:      {get: function(){return width;}, set: function(_){width=_;}},
7005         height:     {get: function(){return height;}, set: function(_){height=_;}},
7006         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7007         brushExtent:    {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
7008         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
7009         focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
7010         focusHeight:    {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
7011         focusShowAxisX:    {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
7012         focusShowAxisY:    {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
7013         legendLeftAxisHint:    {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
7014         legendRightAxisHint:    {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
7015
7016         // deprecated options
7017         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
7018             // deprecated after 1.7.1
7019             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
7020             tooltip.enabled(!!_);
7021         }},
7022         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
7023             // deprecated after 1.7.1
7024             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
7025             tooltip.contentGenerator(_);
7026         }},
7027
7028         // options that require extra logic in the setter
7029         margin: {get: function(){return margin;}, set: function(_){
7030             margin.top    = _.top    !== undefined ? _.top    : margin.top;
7031             margin.right  = _.right  !== undefined ? _.right  : margin.right;
7032             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7033             margin.left   = _.left   !== undefined ? _.left   : margin.left;
7034         }},
7035         duration: {get: function(){return transitionDuration;}, set: function(_){
7036             transitionDuration = _;
7037         }},
7038         color:  {get: function(){return color;}, set: function(_){
7039             color = nv.utils.getColor(_);
7040             legend.color(color);
7041         }},
7042         x: {get: function(){return getX;}, set: function(_){
7043             getX = _;
7044             lines.x(_);
7045             lines2.x(_);
7046             bars.x(_);
7047             bars2.x(_);
7048         }},
7049         y: {get: function(){return getY;}, set: function(_){
7050             getY = _;
7051             lines.y(_);
7052             lines2.y(_);
7053             bars.y(_);
7054             bars2.y(_);
7055         }}
7056     });
7057
7058     nv.utils.inheritOptions(chart, lines);
7059     nv.utils.initOptions(chart);
7060
7061     return chart;
7062 };
7063 nv.models.lineWithFocusChart = function() {
7064     "use strict";
7065
7066     //============================================================
7067     // Public Variables with Default Settings
7068     //------------------------------------------------------------
7069
7070     var lines = nv.models.line()
7071         , lines2 = nv.models.line()
7072         , xAxis = nv.models.axis()
7073         , yAxis = nv.models.axis()
7074         , x2Axis = nv.models.axis()
7075         , y2Axis = nv.models.axis()
7076         , legend = nv.models.legend()
7077         , brush = d3.svg.brush()
7078         , tooltip = nv.models.tooltip()
7079         , interactiveLayer = nv.interactiveGuideline()
7080         ;
7081
7082     var margin = {top: 30, right: 30, bottom: 30, left: 60}
7083         , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
7084         , color = nv.utils.defaultColor()
7085         , width = null
7086         , height = null
7087         , height2 = 50
7088         , useInteractiveGuideline = false
7089         , x
7090         , y
7091         , x2
7092         , y2
7093         , showLegend = true
7094         , brushExtent = null
7095         , noData = null
7096         , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
7097         , transitionDuration = 250
7098         , state = nv.utils.state()
7099         , defaultState = null
7100         ;
7101
7102     lines.clipEdge(true).duration(0);
7103     lines2.interactive(false);
7104     xAxis.orient('bottom').tickPadding(5);
7105     yAxis.orient('left');
7106     x2Axis.orient('bottom').tickPadding(5);
7107     y2Axis.orient('left');
7108
7109     tooltip.valueFormatter(function(d, i) {
7110         return yAxis.tickFormat()(d, i);
7111     }).headerFormatter(function(d, i) {
7112         return xAxis.tickFormat()(d, i);
7113     });
7114
7115     //============================================================
7116     // Private Variables
7117     //------------------------------------------------------------
7118
7119     var stateGetter = function(data) {
7120         return function(){
7121             return {
7122                 active: data.map(function(d) { return !d.disabled })
7123             };
7124         }
7125     };
7126
7127     var stateSetter = function(data) {
7128         return function(state) {
7129             if (state.active !== undefined)
7130                 data.forEach(function(series,i) {
7131                     series.disabled = !state.active[i];
7132                 });
7133         }
7134     };
7135
7136     function chart(selection) {
7137         selection.each(function(data) {
7138             var container = d3.select(this),
7139                 that = this;
7140             nv.utils.initSVG(container);
7141             var availableWidth = nv.utils.availableWidth(width, container, margin),
7142                 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2,
7143                 availableHeight2 = height2 - margin2.top - margin2.bottom;
7144
7145             chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7146             chart.container = this;
7147
7148             state
7149                 .setter(stateSetter(data), chart.update)
7150                 .getter(stateGetter(data))
7151                 .update();
7152
7153             // DEPRECATED set state.disableddisabled
7154             state.disabled = data.map(function(d) { return !!d.disabled });
7155
7156             if (!defaultState) {
7157                 var key;
7158                 defaultState = {};
7159                 for (key in state) {
7160                     if (state[key] instanceof Array)
7161                         defaultState[key] = state[key].slice(0);
7162                     else
7163                         defaultState[key] = state[key];
7164                 }
7165             }
7166
7167             // Display No Data message if there's nothing to show.
7168             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7169                 nv.utils.noData(chart, container)
7170                 return chart;
7171             } else {
7172                 container.selectAll('.nv-noData').remove();
7173             }
7174
7175             // Setup Scales
7176             x = lines.xScale();
7177             y = lines.yScale();
7178             x2 = lines2.xScale();
7179             y2 = lines2.yScale();
7180
7181             // Setup containers and skeleton of chart
7182             var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
7183             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
7184             var g = wrap.select('g');
7185
7186             gEnter.append('g').attr('class', 'nv-legendWrap');
7187
7188             var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
7189             focusEnter.append('g').attr('class', 'nv-x nv-axis');
7190             focusEnter.append('g').attr('class', 'nv-y nv-axis');
7191             focusEnter.append('g').attr('class', 'nv-linesWrap');
7192             focusEnter.append('g').attr('class', 'nv-interactive');
7193
7194             var contextEnter = gEnter.append('g').attr('class', 'nv-context');
7195             contextEnter.append('g').attr('class', 'nv-x nv-axis');
7196             contextEnter.append('g').attr('class', 'nv-y nv-axis');
7197             contextEnter.append('g').attr('class', 'nv-linesWrap');
7198             contextEnter.append('g').attr('class', 'nv-brushBackground');
7199             contextEnter.append('g').attr('class', 'nv-x nv-brush');
7200
7201             // Legend
7202             if (showLegend) {
7203                 legend.width(availableWidth);
7204
7205                 g.select('.nv-legendWrap')
7206                     .datum(data)
7207                     .call(legend);
7208
7209                 if ( margin.top != legend.height()) {
7210                     margin.top = legend.height();
7211                     availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2;
7212                 }
7213
7214                 g.select('.nv-legendWrap')
7215                     .attr('transform', 'translate(0,' + (-margin.top) +')')
7216             }
7217
7218             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7219
7220             
7221             //Set up interactive layer
7222             if (useInteractiveGuideline) {
7223                 interactiveLayer
7224                     .width(availableWidth)
7225                     .height(availableHeight1)
7226                     .margin({left:margin.left, top:margin.top})
7227                     .svgContainer(container)
7228                     .xScale(x);
7229                 wrap.select(".nv-interactive").call(interactiveLayer);
7230             }
7231
7232             // Main Chart Component(s)
7233             lines
7234                 .width(availableWidth)
7235                 .height(availableHeight1)
7236                 .color(
7237                 data
7238                     .map(function(d,i) {
7239                         return d.color || color(d, i);
7240                     })
7241                     .filter(function(d,i) {
7242                         return !data[i].disabled;
7243                     })
7244             );
7245
7246             lines2
7247                 .defined(lines.defined())
7248                 .width(availableWidth)
7249                 .height(availableHeight2)
7250                 .color(
7251                 data
7252                     .map(function(d,i) {
7253                         return d.color || color(d, i);
7254                     })
7255                     .filter(function(d,i) {
7256                         return !data[i].disabled;
7257                     })
7258             );
7259
7260             g.select('.nv-context')
7261                 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7262
7263             var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
7264                 .datum(data.filter(function(d) { return !d.disabled }))
7265
7266             d3.transition(contextLinesWrap).call(lines2);
7267
7268             // Setup Main (Focus) Axes
7269             xAxis
7270                 .scale(x)
7271                 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7272                 .tickSize(-availableHeight1, 0);
7273
7274             yAxis
7275                 .scale(y)
7276                 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7277                 .tickSize( -availableWidth, 0);
7278
7279             g.select('.nv-focus .nv-x.nv-axis')
7280                 .attr('transform', 'translate(0,' + availableHeight1 + ')');
7281
7282             // Setup Brush
7283             brush
7284                 .x(x2)
7285                 .on('brush', function() {
7286                     onBrush();
7287                 });
7288
7289             if (brushExtent) brush.extent(brushExtent);
7290
7291             var brushBG = g.select('.nv-brushBackground').selectAll('g')
7292                 .data([brushExtent || brush.extent()])
7293
7294             var brushBGenter = brushBG.enter()
7295                 .append('g');
7296
7297             brushBGenter.append('rect')
7298                 .attr('class', 'left')
7299                 .attr('x', 0)
7300                 .attr('y', 0)
7301                 .attr('height', availableHeight2);
7302
7303             brushBGenter.append('rect')
7304                 .attr('class', 'right')
7305                 .attr('x', 0)
7306                 .attr('y', 0)
7307                 .attr('height', availableHeight2);
7308
7309             var gBrush = g.select('.nv-x.nv-brush')
7310                 .call(brush);
7311             gBrush.selectAll('rect')
7312                 .attr('height', availableHeight2);
7313             gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7314
7315             onBrush();
7316
7317             // Setup Secondary (Context) Axes
7318             x2Axis
7319                 .scale(x2)
7320                 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7321                 .tickSize(-availableHeight2, 0);
7322
7323             g.select('.nv-context .nv-x.nv-axis')
7324                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7325             d3.transition(g.select('.nv-context .nv-x.nv-axis'))
7326                 .call(x2Axis);
7327
7328             y2Axis
7329                 .scale(y2)
7330                 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7331                 .tickSize( -availableWidth, 0);
7332
7333             d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7334                 .call(y2Axis);
7335
7336             g.select('.nv-context .nv-x.nv-axis')
7337                 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7338
7339             //============================================================
7340             // Event Handling/Dispatching (in chart's scope)
7341             //------------------------------------------------------------
7342
7343             legend.dispatch.on('stateChange', function(newState) {
7344                 for (var key in newState)
7345                     state[key] = newState[key];
7346                 dispatch.stateChange(state);
7347                 chart.update();
7348             });
7349
7350             interactiveLayer.dispatch.on('elementMousemove', function(e) {
7351                 lines.clearHighlights();
7352                 var singlePoint, pointIndex, pointXLocation, allData = [];
7353                 data
7354                     .filter(function(series, i) {
7355                         series.seriesIndex = i;
7356                         return !series.disabled;
7357                     })
7358                     .forEach(function(series,i) {
7359                             var extent = brush.empty() ? x2.domain() : brush.extent();
7360                             var currentValues = series.values.filter(function(d,i) {
7361                             return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7362                         });
7363  
7364                         pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
7365                         var point = currentValues[pointIndex];
7366                         var pointYValue = chart.y()(point, pointIndex);
7367                         if (pointYValue != null) {
7368                             lines.highlightPoint(i, pointIndex, true);
7369                         }
7370                         if (point === undefined) return;
7371                         if (singlePoint === undefined) singlePoint = point;
7372                         if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
7373                         allData.push({
7374                             key: series.key,
7375                             value: chart.y()(point, pointIndex),
7376                             color: color(series,series.seriesIndex)
7377                         });
7378                     });
7379                 //Highlight the tooltip entry based on which point the mouse is closest to.
7380                 if (allData.length > 2) {
7381                     var yValue = chart.yScale().invert(e.mouseY);
7382                     var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
7383                     var threshold = 0.03 * domainExtent;
7384                     var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
7385                     if (indexToHighlight !== null)
7386                         allData[indexToHighlight].highlight = true;
7387                 }
7388
7389                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
7390                 interactiveLayer.tooltip
7391                     .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
7392                     .chartContainer(that.parentNode)
7393                     .valueFormatter(function(d,i) {
7394                         return d == null ? "N/A" : yAxis.tickFormat()(d);
7395                     })
7396                     .data({
7397                         value: xValue,
7398                         index: pointIndex,
7399                         series: allData
7400                     })();
7401
7402                 interactiveLayer.renderGuideLine(pointXLocation);
7403
7404             });
7405
7406             interactiveLayer.dispatch.on("elementMouseout",function(e) {
7407                 lines.clearHighlights();
7408             });
7409
7410             dispatch.on('changeState', function(e) {
7411                 if (typeof e.disabled !== 'undefined') {
7412                     data.forEach(function(series,i) {
7413                         series.disabled = e.disabled[i];
7414                     });
7415                 }
7416                 chart.update();
7417             });
7418
7419             //============================================================
7420             // Functions
7421             //------------------------------------------------------------
7422
7423             // Taken from crossfilter (http://square.github.com/crossfilter/)
7424             function resizePath(d) {
7425                 var e = +(d == 'e'),
7426                     x = e ? 1 : -1,
7427                     y = availableHeight2 / 3;
7428                 return 'M' + (.5 * x) + ',' + y
7429                     + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7430                     + 'V' + (2 * y - 6)
7431                     + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7432                     + 'Z'
7433                     + 'M' + (2.5 * x) + ',' + (y + 8)
7434                     + 'V' + (2 * y - 8)
7435                     + 'M' + (4.5 * x) + ',' + (y + 8)
7436                     + 'V' + (2 * y - 8);
7437             }
7438
7439
7440             function updateBrushBG() {
7441                 if (!brush.empty()) brush.extent(brushExtent);
7442                 brushBG
7443                     .data([brush.empty() ? x2.domain() : brushExtent])
7444                     .each(function(d,i) {
7445                         var leftWidth = x2(d[0]) - x.range()[0],
7446                             rightWidth = availableWidth - x2(d[1]);
7447                         d3.select(this).select('.left')
7448                             .attr('width',  leftWidth < 0 ? 0 : leftWidth);
7449
7450                         d3.select(this).select('.right')
7451                             .attr('x', x2(d[1]))
7452                             .attr('width', rightWidth < 0 ? 0 : rightWidth);
7453                     });
7454             }
7455
7456
7457             function onBrush() {
7458                 brushExtent = brush.empty() ? null : brush.extent();
7459                 var extent = brush.empty() ? x2.domain() : brush.extent();
7460
7461                 //The brush extent cannot be less than one.  If it is, don't update the line chart.
7462                 if (Math.abs(extent[0] - extent[1]) <= 1) {
7463                     return;
7464                 }
7465
7466                 dispatch.brush({extent: extent, brush: brush});
7467
7468
7469                 updateBrushBG();
7470
7471                 // Update Main (Focus)
7472                 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7473                     .datum(
7474                     data
7475                         .filter(function(d) { return !d.disabled })
7476                         .map(function(d,i) {
7477                             return {
7478                                 key: d.key,
7479                                 area: d.area,
7480                                 values: d.values.filter(function(d,i) {
7481                                     return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7482                                 })
7483                             }
7484                         })
7485                 );
7486                 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7487
7488
7489                 // Update Main (Focus) Axes
7490                 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
7491                     .call(xAxis);
7492                 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
7493                     .call(yAxis);
7494             }
7495         });
7496
7497         return chart;
7498     }
7499
7500     //============================================================
7501     // Event Handling/Dispatching (out of chart's scope)
7502     //------------------------------------------------------------
7503
7504     lines.dispatch.on('elementMouseover.tooltip', function(evt) {
7505         tooltip.data(evt).position(evt.pos).hidden(false);
7506     });
7507
7508     lines.dispatch.on('elementMouseout.tooltip', function(evt) {
7509         tooltip.hidden(true)
7510     });
7511
7512     //============================================================
7513     // Expose Public Variables
7514     //------------------------------------------------------------
7515
7516     // expose chart's sub-components
7517     chart.dispatch = dispatch;
7518     chart.legend = legend;
7519     chart.lines = lines;
7520     chart.lines2 = lines2;
7521     chart.xAxis = xAxis;
7522     chart.yAxis = yAxis;
7523     chart.x2Axis = x2Axis;
7524     chart.y2Axis = y2Axis;
7525     chart.interactiveLayer = interactiveLayer;
7526     chart.tooltip = tooltip;
7527
7528     chart.options = nv.utils.optionsFunc.bind(chart);
7529
7530     chart._options = Object.create({}, {
7531         // simple options, just get/set the necessary values
7532         width:      {get: function(){return width;}, set: function(_){width=_;}},
7533         height:     {get: function(){return height;}, set: function(_){height=_;}},
7534         focusHeight:     {get: function(){return height2;}, set: function(_){height2=_;}},
7535         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7536         brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
7537         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7538         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
7539
7540         // deprecated options
7541         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
7542             // deprecated after 1.7.1
7543             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
7544             tooltip.enabled(!!_);
7545         }},
7546         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
7547             // deprecated after 1.7.1
7548             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
7549             tooltip.contentGenerator(_);
7550         }},
7551
7552         // options that require extra logic in the setter
7553         margin: {get: function(){return margin;}, set: function(_){
7554             margin.top    = _.top    !== undefined ? _.top    : margin.top;
7555             margin.right  = _.right  !== undefined ? _.right  : margin.right;
7556             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7557             margin.left   = _.left   !== undefined ? _.left   : margin.left;
7558         }},
7559         color:  {get: function(){return color;}, set: function(_){
7560             color = nv.utils.getColor(_);
7561             legend.color(color);
7562             // line color is handled above?
7563         }},
7564         interpolate: {get: function(){return lines.interpolate();}, set: function(_){
7565             lines.interpolate(_);
7566             lines2.interpolate(_);
7567         }},
7568         xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
7569             xAxis.tickFormat(_);
7570             x2Axis.tickFormat(_);
7571         }},
7572         yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
7573             yAxis.tickFormat(_);
7574             y2Axis.tickFormat(_);
7575         }},
7576         duration:    {get: function(){return transitionDuration;}, set: function(_){
7577             transitionDuration=_;
7578             yAxis.duration(transitionDuration);
7579             y2Axis.duration(transitionDuration);
7580             xAxis.duration(transitionDuration);
7581             x2Axis.duration(transitionDuration);
7582         }},
7583         x: {get: function(){return lines.x();}, set: function(_){
7584             lines.x(_);
7585             lines2.x(_);
7586         }},
7587         y: {get: function(){return lines.y();}, set: function(_){
7588             lines.y(_);
7589             lines2.y(_);
7590         }},
7591         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
7592             useInteractiveGuideline = _;
7593             if (useInteractiveGuideline) {
7594                 lines.interactive(false);
7595                 lines.useVoronoi(false);
7596             }
7597         }}
7598     });
7599
7600     nv.utils.inheritOptions(chart, lines);
7601     nv.utils.initOptions(chart);
7602
7603     return chart;
7604 };
7605
7606 nv.models.multiBar = function() {
7607     "use strict";
7608
7609     //============================================================
7610     // Public Variables with Default Settings
7611     //------------------------------------------------------------
7612
7613     var margin = {top: 0, right: 0, bottom: 0, left: 0}
7614         , width = 960
7615         , height = 500
7616         , x = d3.scale.ordinal()
7617         , y = d3.scale.linear()
7618         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7619         , container = null
7620         , getX = function(d) { return d.x }
7621         , getY = function(d) { return d.y }
7622         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7623         , clipEdge = true
7624         , stacked = false
7625         , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7626         , color = nv.utils.defaultColor()
7627         , hideable = false
7628         , barColor = null // adding the ability to set the color for each rather than the whole group
7629         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7630         , duration = 500
7631         , xDomain
7632         , yDomain
7633         , xRange
7634         , yRange
7635         , groupSpacing = 0.1
7636         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
7637         ;
7638
7639     //============================================================
7640     // Private Variables
7641     //------------------------------------------------------------
7642
7643     var x0, y0 //used to store previous scales
7644         , renderWatch = nv.utils.renderWatch(dispatch, duration)
7645         ;
7646
7647     var last_datalength = 0;
7648
7649     function chart(selection) {
7650         renderWatch.reset();
7651         selection.each(function(data) {
7652             var availableWidth = width - margin.left - margin.right,
7653                 availableHeight = height - margin.top - margin.bottom;
7654
7655             container = d3.select(this);
7656             nv.utils.initSVG(container);
7657             var nonStackableCount = 0;
7658             // This function defines the requirements for render complete
7659             var endFn = function(d, i) {
7660                 if (d.series === data.length - 1 && i === data[0].values.length - 1)
7661                     return true;
7662                 return false;
7663             };
7664
7665             if(hideable && data.length) hideable = [{
7666                 values: data[0].values.map(function(d) {
7667                         return {
7668                             x: d.x,
7669                             y: 0,
7670                             series: d.series,
7671                             size: 0.01
7672                         };}
7673                 )}];
7674
7675             if (stacked) {
7676                 var parsed = d3.layout.stack()
7677                     .offset(stackOffset)
7678                     .values(function(d){ return d.values })
7679                     .y(getY)
7680                 (!data.length && hideable ? hideable : data);
7681
7682                 parsed.forEach(function(series, i){
7683                     // if series is non-stackable, use un-parsed data
7684                     if (series.nonStackable) {
7685                         data[i].nonStackableSeries = nonStackableCount++; 
7686                         parsed[i] = data[i];
7687                     } else {
7688                         // don't stack this seires on top of the nonStackable seriees 
7689                         if (i > 0 && parsed[i - 1].nonStackable){
7690                             parsed[i].values.map(function(d,j){
7691                                 d.y0 -= parsed[i - 1].values[j].y;
7692                                 d.y1 = d.y0 + d.y;
7693                             });
7694                         }
7695                     }
7696                 });
7697                 data = parsed;
7698             }
7699             //add series index and key to each data point for reference
7700             data.forEach(function(series, i) {
7701                 series.values.forEach(function(point) {
7702                     point.series = i;
7703                     point.key = series.key;
7704                 });
7705             });
7706
7707             // HACK for negative value stacking
7708             if (stacked) {
7709                 data[0].values.map(function(d,i) {
7710                     var posBase = 0, negBase = 0;
7711                     data.map(function(d, idx) {
7712                         if (!data[idx].nonStackable) {
7713                             var f = d.values[i]
7714                             f.size = Math.abs(f.y);
7715                             if (f.y<0)  {
7716                                 f.y1 = negBase;
7717                                 negBase = negBase - f.size;
7718                             } else
7719                             {
7720                                 f.y1 = f.size + posBase;
7721                                 posBase = posBase + f.size;
7722                             }
7723                         }
7724                         
7725                     });
7726                 });
7727             }
7728             // Setup Scales
7729             // remap and flatten the data for use in calculating the scales' domains
7730             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7731                 data.map(function(d, idx) {
7732                     return d.values.map(function(d,i) {
7733                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
7734                     })
7735                 });
7736
7737             x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7738                 .rangeBands(xRange || [0, availableWidth], groupSpacing);
7739
7740             y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
7741                 var domain = d.y;
7742                 // increase the domain range if this series is stackable
7743                 if (stacked && !data[d.idx].nonStackable) {
7744                     if (d.y > 0){
7745                         domain = d.y1
7746                     } else {
7747                         domain = d.y1 + d.y
7748                     }
7749                 }
7750                 return domain;
7751             }).concat(forceY)))
7752             .range(yRange || [availableHeight, 0]);
7753
7754             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7755             if (x.domain()[0] === x.domain()[1])
7756                 x.domain()[0] ?
7757                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7758                     : x.domain([-1,1]);
7759
7760             if (y.domain()[0] === y.domain()[1])
7761                 y.domain()[0] ?
7762                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7763                     : y.domain([-1,1]);
7764
7765             x0 = x0 || x;
7766             y0 = y0 || y;
7767
7768             // Setup containers and skeleton of chart
7769             var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7770             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7771             var defsEnter = wrapEnter.append('defs');
7772             var gEnter = wrapEnter.append('g');
7773             var g = wrap.select('g');
7774
7775             gEnter.append('g').attr('class', 'nv-groups');
7776             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7777
7778             defsEnter.append('clipPath')
7779                 .attr('id', 'nv-edge-clip-' + id)
7780                 .append('rect');
7781             wrap.select('#nv-edge-clip-' + id + ' rect')
7782                 .attr('width', availableWidth)
7783                 .attr('height', availableHeight);
7784
7785             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7786
7787             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7788                 .data(function(d) { return d }, function(d,i) { return i });
7789             groups.enter().append('g')
7790                 .style('stroke-opacity', 1e-6)
7791                 .style('fill-opacity', 1e-6);
7792
7793             var exitTransition = renderWatch
7794                 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
7795                 .attr('y', function(d, i, j) {
7796                     var yVal = y0(0) || 0;
7797                     if (stacked) {
7798                         if (data[d.series] && !data[d.series].nonStackable) {
7799                             yVal = y0(d.y0);
7800                         }
7801                     }
7802                     return yVal;
7803                 })
7804                 .attr('height', 0)
7805                 .remove();
7806             if (exitTransition.delay)
7807                 exitTransition.delay(function(d,i) {
7808                     var delay = i * (duration / (last_datalength + 1)) - i;
7809                     return delay;
7810                 });
7811             groups
7812                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7813                 .classed('hover', function(d) { return d.hover })
7814                 .style('fill', function(d,i){ return color(d, i) })
7815                 .style('stroke', function(d,i){ return color(d, i) });
7816             groups
7817                 .style('stroke-opacity', 1)
7818                 .style('fill-opacity', 0.75);
7819
7820             var bars = groups.selectAll('rect.nv-bar')
7821                 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7822             bars.exit().remove();
7823
7824             var barsEnter = bars.enter().append('rect')
7825                     .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7826                     .attr('x', function(d,i,j) {
7827                         return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
7828                     })
7829                     .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
7830                     .attr('height', 0)
7831                     .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
7832                     .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7833                 ;
7834             bars
7835                 .style('fill', function(d,i,j){ return color(d, j, i);  })
7836                 .style('stroke', function(d,i,j){ return color(d, j, i); })
7837                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7838                     d3.select(this).classed('hover', true);
7839                     dispatch.elementMouseover({
7840                         data: d,
7841                         index: i,
7842                         color: d3.select(this).style("fill")
7843                     });
7844                 })
7845                 .on('mouseout', function(d,i) {
7846                     d3.select(this).classed('hover', false);
7847                     dispatch.elementMouseout({
7848                         data: d,
7849                         index: i,
7850                         color: d3.select(this).style("fill")
7851                     });
7852                 })
7853                 .on('mousemove', function(d,i) {
7854                     dispatch.elementMousemove({
7855                         data: d,
7856                         index: i,
7857                         color: d3.select(this).style("fill")
7858                     });
7859                 })
7860                 .on('click', function(d,i) {
7861                     dispatch.elementClick({
7862                         data: d,
7863                         index: i,
7864                         color: d3.select(this).style("fill")
7865                     });
7866                     d3.event.stopPropagation();
7867                 })
7868                 .on('dblclick', function(d,i) {
7869                     dispatch.elementDblClick({
7870                         data: d,
7871                         index: i,
7872                         color: d3.select(this).style("fill")
7873                     });
7874                     d3.event.stopPropagation();
7875                 });
7876             bars
7877                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7878                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7879
7880             if (barColor) {
7881                 if (!disabled) disabled = data.map(function() { return true });
7882                 bars
7883                     .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(); })
7884                     .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(); });
7885             }
7886
7887             var barSelection =
7888                 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
7889                     .delay(function(d,i) {
7890                         return i * duration / data[0].values.length;
7891                     });
7892             if (stacked){
7893                 barSelection
7894                     .attr('y', function(d,i,j) {
7895                         var yVal = 0;
7896                         // if stackable, stack it on top of the previous series
7897                         if (!data[j].nonStackable) {
7898                             yVal = y(d.y1);
7899                         } else {
7900                             if (getY(d,i) < 0){
7901                                 yVal = y(0);
7902                             } else {
7903                                 if (y(0) - y(getY(d,i)) < -1){
7904                                     yVal = y(0) - 1;
7905                                 } else {
7906                                     yVal = y(getY(d, i)) || 0;
7907                                 }
7908                             }
7909                         }
7910                         return yVal;
7911                     })
7912                     .attr('height', function(d,i,j) {
7913                         if (!data[j].nonStackable) {
7914                             return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1);
7915                         } else {
7916                             return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7917                         }
7918                     })
7919                     .attr('x', function(d,i,j) {
7920                         var width = 0;
7921                         if (data[j].nonStackable) {
7922                             width = d.series * x.rangeBand() / data.length;
7923                             if (data.length !== nonStackableCount){
7924                                 width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2); 
7925                             }
7926                         }
7927                         return width;
7928                     })
7929                     .attr('width', function(d,i,j){
7930                         if (!data[j].nonStackable) {
7931                             return x.rangeBand();
7932                         } else {
7933                             // if all series are nonStacable, take the full width
7934                             var width = (x.rangeBand() / nonStackableCount);
7935                             // otherwise, nonStackable graph will be only taking the half-width 
7936                             // of the x rangeBand
7937                             if (data.length !== nonStackableCount) {
7938                                 width = x.rangeBand()/(nonStackableCount*2);
7939                             }
7940                             return width;
7941                         }
7942                     });
7943             }
7944             else {
7945                 barSelection
7946                     .attr('x', function(d,i) {
7947                         return d.series * x.rangeBand() / data.length;
7948                     })
7949                     .attr('width', x.rangeBand() / data.length)
7950                     .attr('y', function(d,i) {
7951                         return getY(d,i) < 0 ?
7952                             y(0) :
7953                                 y(0) - y(getY(d,i)) < 1 ?
7954                             y(0) - 1 :
7955                             y(getY(d,i)) || 0;
7956                     })
7957                     .attr('height', function(d,i) {
7958                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7959                     });
7960             }
7961
7962             //store old scales for use in transitions on update
7963             x0 = x.copy();
7964             y0 = y.copy();
7965
7966             // keep track of the last data value length for transition calculations
7967             if (data[0] && data[0].values) {
7968                 last_datalength = data[0].values.length;
7969             }
7970
7971         });
7972
7973         renderWatch.renderEnd('multibar immediate');
7974
7975         return chart;
7976     }
7977
7978     //============================================================
7979     // Expose Public Variables
7980     //------------------------------------------------------------
7981
7982     chart.dispatch = dispatch;
7983
7984     chart.options = nv.utils.optionsFunc.bind(chart);
7985
7986     chart._options = Object.create({}, {
7987         // simple options, just get/set the necessary values
7988         width:   {get: function(){return width;}, set: function(_){width=_;}},
7989         height:  {get: function(){return height;}, set: function(_){height=_;}},
7990         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
7991         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
7992         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
7993         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
7994         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
7995         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
7996         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
7997         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
7998         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
7999         stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
8000         stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
8001         clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
8002         disabled:    {get: function(){return disabled;}, set: function(_){disabled=_;}},
8003         id:          {get: function(){return id;}, set: function(_){id=_;}},
8004         hideable:    {get: function(){return hideable;}, set: function(_){hideable=_;}},
8005         groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
8006
8007         // options that require extra logic in the setter
8008         margin: {get: function(){return margin;}, set: function(_){
8009             margin.top    = _.top    !== undefined ? _.top    : margin.top;
8010             margin.right  = _.right  !== undefined ? _.right  : margin.right;
8011             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8012             margin.left   = _.left   !== undefined ? _.left   : margin.left;
8013         }},
8014         duration: {get: function(){return duration;}, set: function(_){
8015             duration = _;
8016             renderWatch.reset(duration);
8017         }},
8018         color:  {get: function(){return color;}, set: function(_){
8019             color = nv.utils.getColor(_);
8020         }},
8021         barColor:  {get: function(){return barColor;}, set: function(_){
8022             barColor = _ ? nv.utils.getColor(_) : null;
8023         }}
8024     });
8025
8026     nv.utils.initOptions(chart);
8027
8028     return chart;
8029 };
8030 nv.models.multiBarChart = function() {
8031     "use strict";
8032
8033     //============================================================
8034     // Public Variables with Default Settings
8035     //------------------------------------------------------------
8036
8037     var multibar = nv.models.multiBar()
8038         , xAxis = nv.models.axis()
8039         , yAxis = nv.models.axis()
8040         , legend = nv.models.legend()
8041         , controls = nv.models.legend()
8042         , tooltip = nv.models.tooltip()
8043         ;
8044
8045     var margin = {top: 30, right: 20, bottom: 50, left: 60}
8046         , width = null
8047         , height = null
8048         , color = nv.utils.defaultColor()
8049         , showControls = true
8050         , controlLabels = {}
8051         , showLegend = true
8052         , showXAxis = true
8053         , showYAxis = true
8054         , rightAlignYAxis = false
8055         , reduceXTicks = true // if false a tick will show for every data point
8056         , staggerLabels = false
8057         , rotateLabels = 0
8058         , x //can be accessed via chart.xScale()
8059         , y //can be accessed via chart.yScale()
8060         , state = nv.utils.state()
8061         , defaultState = null
8062         , noData = null
8063         , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
8064         , controlWidth = function() { return showControls ? 180 : 0 }
8065         , duration = 250
8066         ;
8067
8068     state.stacked = false // DEPRECATED Maintained for backward compatibility
8069
8070     multibar.stacked(false);
8071     xAxis
8072         .orient('bottom')
8073         .tickPadding(7)
8074         .showMaxMin(false)
8075         .tickFormat(function(d) { return d })
8076     ;
8077     yAxis
8078         .orient((rightAlignYAxis) ? 'right' : 'left')
8079         .tickFormat(d3.format(',.1f'))
8080     ;
8081
8082     tooltip
8083         .duration(0)
8084         .valueFormatter(function(d, i) {
8085             return yAxis.tickFormat()(d, i);
8086         })
8087         .headerFormatter(function(d, i) {
8088             return xAxis.tickFormat()(d, i);
8089         });
8090
8091     controls.updateState(false);
8092
8093     //============================================================
8094     // Private Variables
8095     //------------------------------------------------------------
8096
8097     var renderWatch = nv.utils.renderWatch(dispatch);
8098     var stacked = false;
8099
8100     var stateGetter = function(data) {
8101         return function(){
8102             return {
8103                 active: data.map(function(d) { return !d.disabled }),
8104                 stacked: stacked
8105             };
8106         }
8107     };
8108
8109     var stateSetter = function(data) {
8110         return function(state) {
8111             if (state.stacked !== undefined)
8112                 stacked = state.stacked;
8113             if (state.active !== undefined)
8114                 data.forEach(function(series,i) {
8115                     series.disabled = !state.active[i];
8116                 });
8117         }
8118     };
8119
8120     function chart(selection) {
8121         renderWatch.reset();
8122         renderWatch.models(multibar);
8123         if (showXAxis) renderWatch.models(xAxis);
8124         if (showYAxis) renderWatch.models(yAxis);
8125
8126         selection.each(function(data) {
8127             var container = d3.select(this),
8128                 that = this;
8129             nv.utils.initSVG(container);
8130             var availableWidth = nv.utils.availableWidth(width, container, margin),
8131                 availableHeight = nv.utils.availableHeight(height, container, margin);
8132
8133             chart.update = function() {
8134                 if (duration === 0)
8135                     container.call(chart);
8136                 else
8137                     container.transition()
8138                         .duration(duration)
8139                         .call(chart);
8140             };
8141             chart.container = this;
8142
8143             state
8144                 .setter(stateSetter(data), chart.update)
8145                 .getter(stateGetter(data))
8146                 .update();
8147
8148             // DEPRECATED set state.disableddisabled
8149             state.disabled = data.map(function(d) { return !!d.disabled });
8150
8151             if (!defaultState) {
8152                 var key;
8153                 defaultState = {};
8154                 for (key in state) {
8155                     if (state[key] instanceof Array)
8156                         defaultState[key] = state[key].slice(0);
8157                     else
8158                         defaultState[key] = state[key];
8159                 }
8160             }
8161
8162             // Display noData message if there's nothing to show.
8163             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8164                 nv.utils.noData(chart, container)
8165                 return chart;
8166             } else {
8167                 container.selectAll('.nv-noData').remove();
8168             }
8169
8170             // Setup Scales
8171             x = multibar.xScale();
8172             y = multibar.yScale();
8173
8174             // Setup containers and skeleton of chart
8175             var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8176             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8177             var g = wrap.select('g');
8178
8179             gEnter.append('g').attr('class', 'nv-x nv-axis');
8180             gEnter.append('g').attr('class', 'nv-y nv-axis');
8181             gEnter.append('g').attr('class', 'nv-barsWrap');
8182             gEnter.append('g').attr('class', 'nv-legendWrap');
8183             gEnter.append('g').attr('class', 'nv-controlsWrap');
8184
8185             // Legend
8186             if (showLegend) {
8187                 legend.width(availableWidth - controlWidth());
8188
8189                 g.select('.nv-legendWrap')
8190                     .datum(data)
8191                     .call(legend);
8192
8193                 if ( margin.top != legend.height()) {
8194                     margin.top = legend.height();
8195                     availableHeight = nv.utils.availableHeight(height, container, margin);
8196                 }
8197
8198                 g.select('.nv-legendWrap')
8199                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8200             }
8201
8202             // Controls
8203             if (showControls) {
8204                 var controlsData = [
8205                     { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8206                     { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8207                 ];
8208
8209                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8210                 g.select('.nv-controlsWrap')
8211                     .datum(controlsData)
8212                     .attr('transform', 'translate(0,' + (-margin.top) +')')
8213                     .call(controls);
8214             }
8215
8216             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8217             if (rightAlignYAxis) {
8218                 g.select(".nv-y.nv-axis")
8219                     .attr("transform", "translate(" + availableWidth + ",0)");
8220             }
8221
8222             // Main Chart Component(s)
8223             multibar
8224                 .disabled(data.map(function(series) { return series.disabled }))
8225                 .width(availableWidth)
8226                 .height(availableHeight)
8227                 .color(data.map(function(d,i) {
8228                     return d.color || color(d, i);
8229                 }).filter(function(d,i) { return !data[i].disabled }));
8230
8231
8232             var barsWrap = g.select('.nv-barsWrap')
8233                 .datum(data.filter(function(d) { return !d.disabled }));
8234
8235             barsWrap.call(multibar);
8236
8237             // Setup Axes
8238             if (showXAxis) {
8239                 xAxis
8240                     .scale(x)
8241                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8242                     .tickSize(-availableHeight, 0);
8243
8244                 g.select('.nv-x.nv-axis')
8245                     .attr('transform', 'translate(0,' + y.range()[0] + ')');
8246                 g.select('.nv-x.nv-axis')
8247                     .call(xAxis);
8248
8249                 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8250
8251                 xTicks
8252                     .selectAll('line, text')
8253                     .style('opacity', 1)
8254
8255                 if (staggerLabels) {
8256                     var getTranslate = function(x,y) {
8257                         return "translate(" + x + "," + y + ")";
8258                     };
8259
8260                     var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
8261                     // Issue #140
8262                     xTicks
8263                         .selectAll("text")
8264                         .attr('transform', function(d,i,j) {
8265                             return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8266                         });
8267
8268                     var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8269                     g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8270                         .attr("transform", function(d,i) {
8271                             return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8272                         });
8273                 }
8274
8275                 if (reduceXTicks)
8276                     xTicks
8277                         .filter(function(d,i) {
8278                             return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8279                         })
8280                         .selectAll('text, line')
8281                         .style('opacity', 0);
8282
8283                 if(rotateLabels)
8284                     xTicks
8285                         .selectAll('.tick text')
8286                         .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8287                         .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8288
8289                 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8290                     .style('opacity', 1);
8291             }
8292
8293             if (showYAxis) {
8294                 yAxis
8295                     .scale(y)
8296                     ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8297                     .tickSize( -availableWidth, 0);
8298
8299                 g.select('.nv-y.nv-axis')
8300                     .call(yAxis);
8301             }
8302
8303             //============================================================
8304             // Event Handling/Dispatching (in chart's scope)
8305             //------------------------------------------------------------
8306
8307             legend.dispatch.on('stateChange', function(newState) {
8308                 for (var key in newState)
8309                     state[key] = newState[key];
8310                 dispatch.stateChange(state);
8311                 chart.update();
8312             });
8313
8314             controls.dispatch.on('legendClick', function(d,i) {
8315                 if (!d.disabled) return;
8316                 controlsData = controlsData.map(function(s) {
8317                     s.disabled = true;
8318                     return s;
8319                 });
8320                 d.disabled = false;
8321
8322                 switch (d.key) {
8323                     case 'Grouped':
8324                     case controlLabels.grouped:
8325                         multibar.stacked(false);
8326                         break;
8327                     case 'Stacked':
8328                     case controlLabels.stacked:
8329                         multibar.stacked(true);
8330                         break;
8331                 }
8332
8333                 state.stacked = multibar.stacked();
8334                 dispatch.stateChange(state);
8335                 chart.update();
8336             });
8337
8338             // Update chart from a state object passed to event handler
8339             dispatch.on('changeState', function(e) {
8340                 if (typeof e.disabled !== 'undefined') {
8341                     data.forEach(function(series,i) {
8342                         series.disabled = e.disabled[i];
8343                     });
8344                     state.disabled = e.disabled;
8345                 }
8346                 if (typeof e.stacked !== 'undefined') {
8347                     multibar.stacked(e.stacked);
8348                     state.stacked = e.stacked;
8349                     stacked = e.stacked;
8350                 }
8351                 chart.update();
8352             });
8353         });
8354
8355         renderWatch.renderEnd('multibarchart immediate');
8356         return chart;
8357     }
8358
8359     //============================================================
8360     // Event Handling/Dispatching (out of chart's scope)
8361     //------------------------------------------------------------
8362
8363     multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
8364         evt.value = chart.x()(evt.data);
8365         evt['series'] = {
8366             key: evt.data.key,
8367             value: chart.y()(evt.data),
8368             color: evt.color
8369         };
8370         tooltip.data(evt).hidden(false);
8371     });
8372
8373     multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
8374         tooltip.hidden(true);
8375     });
8376
8377     multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
8378         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
8379     });
8380
8381     //============================================================
8382     // Expose Public Variables
8383     //------------------------------------------------------------
8384
8385     // expose chart's sub-components
8386     chart.dispatch = dispatch;
8387     chart.multibar = multibar;
8388     chart.legend = legend;
8389     chart.controls = controls;
8390     chart.xAxis = xAxis;
8391     chart.yAxis = yAxis;
8392     chart.state = state;
8393     chart.tooltip = tooltip;
8394
8395     chart.options = nv.utils.optionsFunc.bind(chart);
8396
8397     chart._options = Object.create({}, {
8398         // simple options, just get/set the necessary values
8399         width:      {get: function(){return width;}, set: function(_){width=_;}},
8400         height:     {get: function(){return height;}, set: function(_){height=_;}},
8401         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
8402         showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
8403         controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
8404         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
8405         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
8406         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
8407         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
8408         reduceXTicks:    {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
8409         rotateLabels:    {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
8410         staggerLabels:    {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
8411
8412         // deprecated options
8413         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
8414             // deprecated after 1.7.1
8415             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
8416             tooltip.enabled(!!_);
8417         }},
8418         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
8419             // deprecated after 1.7.1
8420             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
8421             tooltip.contentGenerator(_);
8422         }},
8423
8424         // options that require extra logic in the setter
8425         margin: {get: function(){return margin;}, set: function(_){
8426             margin.top    = _.top    !== undefined ? _.top    : margin.top;
8427             margin.right  = _.right  !== undefined ? _.right  : margin.right;
8428             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8429             margin.left   = _.left   !== undefined ? _.left   : margin.left;
8430         }},
8431         duration: {get: function(){return duration;}, set: function(_){
8432             duration = _;
8433             multibar.duration(duration);
8434             xAxis.duration(duration);
8435             yAxis.duration(duration);
8436             renderWatch.reset(duration);
8437         }},
8438         color:  {get: function(){return color;}, set: function(_){
8439             color = nv.utils.getColor(_);
8440             legend.color(color);
8441         }},
8442         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
8443             rightAlignYAxis = _;
8444             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
8445         }},
8446         barColor:  {get: function(){return multibar.barColor;}, set: function(_){
8447             multibar.barColor(_);
8448             legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
8449         }}
8450     });
8451
8452     nv.utils.inheritOptions(chart, multibar);
8453     nv.utils.initOptions(chart);
8454
8455     return chart;
8456 };
8457
8458 nv.models.multiBarHorizontal = function() {
8459     "use strict";
8460
8461     //============================================================
8462     // Public Variables with Default Settings
8463     //------------------------------------------------------------
8464
8465     var margin = {top: 0, right: 0, bottom: 0, left: 0}
8466         , width = 960
8467         , height = 500
8468         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8469         , container = null
8470         , x = d3.scale.ordinal()
8471         , y = d3.scale.linear()
8472         , getX = function(d) { return d.x }
8473         , getY = function(d) { return d.y }
8474         , getYerr = function(d) { return d.yErr }
8475         , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8476         , color = nv.utils.defaultColor()
8477         , barColor = null // adding the ability to set the color for each rather than the whole group
8478         , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8479         , stacked = false
8480         , showValues = false
8481         , showBarLabels = false
8482         , valuePadding = 60
8483         , groupSpacing = 0.1
8484         , valueFormat = d3.format(',.2f')
8485         , delay = 1200
8486         , xDomain
8487         , yDomain
8488         , xRange
8489         , yRange
8490         , duration = 250
8491         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
8492         ;
8493
8494     //============================================================
8495     // Private Variables
8496     //------------------------------------------------------------
8497
8498     var x0, y0; //used to store previous scales
8499     var renderWatch = nv.utils.renderWatch(dispatch, duration);
8500
8501     function chart(selection) {
8502         renderWatch.reset();
8503         selection.each(function(data) {
8504             var availableWidth = width - margin.left - margin.right,
8505                 availableHeight = height - margin.top - margin.bottom;
8506
8507             container = d3.select(this);
8508             nv.utils.initSVG(container);
8509
8510             if (stacked)
8511                 data = d3.layout.stack()
8512                     .offset('zero')
8513                     .values(function(d){ return d.values })
8514                     .y(getY)
8515                 (data);
8516
8517             //add series index and key to each data point for reference
8518             data.forEach(function(series, i) {
8519                 series.values.forEach(function(point) {
8520                     point.series = i;
8521                     point.key = series.key;
8522                 });
8523             });
8524
8525             // HACK for negative value stacking
8526             if (stacked)
8527                 data[0].values.map(function(d,i) {
8528                     var posBase = 0, negBase = 0;
8529                     data.map(function(d) {
8530                         var f = d.values[i]
8531                         f.size = Math.abs(f.y);
8532                         if (f.y<0)  {
8533                             f.y1 = negBase - f.size;
8534                             negBase = negBase - f.size;
8535                         } else
8536                         {
8537                             f.y1 = posBase;
8538                             posBase = posBase + f.size;
8539                         }
8540                     });
8541                 });
8542
8543             // Setup Scales
8544             // remap and flatten the data for use in calculating the scales' domains
8545             var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8546                 data.map(function(d) {
8547                     return d.values.map(function(d,i) {
8548                         return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8549                     })
8550                 });
8551
8552             x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8553                 .rangeBands(xRange || [0, availableHeight], groupSpacing);
8554
8555             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)))
8556
8557             if (showValues && !stacked)
8558                 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8559             else
8560                 y.range(yRange || [0, availableWidth]);
8561
8562             x0 = x0 || x;
8563             y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8564
8565             // Setup containers and skeleton of chart
8566             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8567             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8568             var defsEnter = wrapEnter.append('defs');
8569             var gEnter = wrapEnter.append('g');
8570             var g = wrap.select('g');
8571
8572             gEnter.append('g').attr('class', 'nv-groups');
8573             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8574
8575             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8576                 .data(function(d) { return d }, function(d,i) { return i });
8577             groups.enter().append('g')
8578                 .style('stroke-opacity', 1e-6)
8579                 .style('fill-opacity', 1e-6);
8580             groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
8581                 .style('stroke-opacity', 1e-6)
8582                 .style('fill-opacity', 1e-6)
8583                 .remove();
8584             groups
8585                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8586                 .classed('hover', function(d) { return d.hover })
8587                 .style('fill', function(d,i){ return color(d, i) })
8588                 .style('stroke', function(d,i){ return color(d, i) });
8589             groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
8590                 .style('stroke-opacity', 1)
8591                 .style('fill-opacity', .75);
8592
8593             var bars = groups.selectAll('g.nv-bar')
8594                 .data(function(d) { return d.values });
8595             bars.exit().remove();
8596
8597             var barsEnter = bars.enter().append('g')
8598                 .attr('transform', function(d,i,j) {
8599                     return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8600                 });
8601
8602             barsEnter.append('rect')
8603                 .attr('width', 0)
8604                 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8605
8606             bars
8607                 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8608                     d3.select(this).classed('hover', true);
8609                     dispatch.elementMouseover({
8610                         data: d,
8611                         index: i,
8612                         color: d3.select(this).style("fill")
8613                     });
8614                 })
8615                 .on('mouseout', function(d,i) {
8616                     d3.select(this).classed('hover', false);
8617                     dispatch.elementMouseout({
8618                         data: d,
8619                         index: i,
8620                         color: d3.select(this).style("fill")
8621                     });
8622                 })
8623                 .on('mouseout', function(d,i) {
8624                     dispatch.elementMouseout({
8625                         data: d,
8626                         index: i,
8627                         color: d3.select(this).style("fill")
8628                     });
8629                 })
8630                 .on('mousemove', function(d,i) {
8631                     dispatch.elementMousemove({
8632                         data: d,
8633                         index: i,
8634                         color: d3.select(this).style("fill")
8635                     });
8636                 })
8637                 .on('click', function(d,i) {
8638                     dispatch.elementClick({
8639                         data: d,
8640                         index: i,
8641                         color: d3.select(this).style("fill")
8642                     });
8643                     d3.event.stopPropagation();
8644                 })
8645                 .on('dblclick', function(d,i) {
8646                     dispatch.elementDblClick({
8647                         data: d,
8648                         index: i,
8649                         color: d3.select(this).style("fill")
8650                     });
8651                     d3.event.stopPropagation();
8652                 });
8653
8654             if (getYerr(data[0],0)) {
8655                 barsEnter.append('polyline');
8656
8657                 bars.select('polyline')
8658                     .attr('fill', 'none')
8659                     .attr('points', function(d,i) {
8660                         var xerr = getYerr(d,i)
8661                             , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
8662                         xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
8663                         xerr = xerr.map(function(e) { return y(e) - y(0); });
8664                         var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
8665                         return a.map(function (path) { return path.join(',') }).join(' ');
8666                     })
8667                     .attr('transform', function(d,i) {
8668                         var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
8669                         return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
8670                     });
8671             }
8672
8673             barsEnter.append('text');
8674
8675             if (showValues && !stacked) {
8676                 bars.select('text')
8677                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8678                     .attr('y', x.rangeBand() / (data.length * 2))
8679                     .attr('dy', '.32em')
8680                     .text(function(d,i) {
8681                         var t = valueFormat(getY(d,i))
8682                             , yerr = getYerr(d,i);
8683                         if (yerr === undefined)
8684                             return t;
8685                         if (!yerr.length)
8686                             return t + '±' + valueFormat(Math.abs(yerr));
8687                         return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
8688                     });
8689                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8690                     .select('text')
8691                     .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8692             } else {
8693                 bars.selectAll('text').text('');
8694             }
8695
8696             if (showBarLabels && !stacked) {
8697                 barsEnter.append('text').classed('nv-bar-label',true);
8698                 bars.select('text.nv-bar-label')
8699                     .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
8700                     .attr('y', x.rangeBand() / (data.length * 2))
8701                     .attr('dy', '.32em')
8702                     .text(function(d,i) { return getX(d,i) });
8703                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8704                     .select('text.nv-bar-label')
8705                     .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
8706             }
8707             else {
8708                 bars.selectAll('text.nv-bar-label').text('');
8709             }
8710
8711             bars
8712                 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8713
8714             if (barColor) {
8715                 if (!disabled) disabled = data.map(function() { return true });
8716                 bars
8717                     .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(); })
8718                     .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(); });
8719             }
8720
8721             if (stacked)
8722                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8723                     .attr('transform', function(d,i) {
8724                         return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8725                     })
8726                     .select('rect')
8727                     .attr('width', function(d,i) {
8728                         return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8729                     })
8730                     .attr('height', x.rangeBand() );
8731             else
8732                 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8733                     .attr('transform', function(d,i) {
8734                         //TODO: stacked must be all positive or all negative, not both?
8735                         return 'translate(' +
8736                             (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8737                             + ',' +
8738                             (d.series * x.rangeBand() / data.length
8739                                 +
8740                                 x(getX(d,i)) )
8741                             + ')'
8742                     })
8743                     .select('rect')
8744                     .attr('height', x.rangeBand() / data.length )
8745                     .attr('width', function(d,i) {
8746                         return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8747                     });
8748
8749             //store old scales for use in transitions on update
8750             x0 = x.copy();
8751             y0 = y.copy();
8752
8753         });
8754
8755         renderWatch.renderEnd('multibarHorizontal immediate');
8756         return chart;
8757     }
8758
8759     //============================================================
8760     // Expose Public Variables
8761     //------------------------------------------------------------
8762
8763     chart.dispatch = dispatch;
8764
8765     chart.options = nv.utils.optionsFunc.bind(chart);
8766
8767     chart._options = Object.create({}, {
8768         // simple options, just get/set the necessary values
8769         width:   {get: function(){return width;}, set: function(_){width=_;}},
8770         height:  {get: function(){return height;}, set: function(_){height=_;}},
8771         x:       {get: function(){return getX;}, set: function(_){getX=_;}},
8772         y:       {get: function(){return getY;}, set: function(_){getY=_;}},
8773         yErr:       {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
8774         xScale:  {get: function(){return x;}, set: function(_){x=_;}},
8775         yScale:  {get: function(){return y;}, set: function(_){y=_;}},
8776         xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
8777         yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
8778         xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
8779         yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
8780         forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
8781         stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
8782         showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
8783         // this shows the group name, seems pointless?
8784         //showBarLabels:    {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
8785         disabled:     {get: function(){return disabled;}, set: function(_){disabled=_;}},
8786         id:           {get: function(){return id;}, set: function(_){id=_;}},
8787         valueFormat:  {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
8788         valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
8789         groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
8790
8791         // options that require extra logic in the setter
8792         margin: {get: function(){return margin;}, set: function(_){
8793             margin.top    = _.top    !== undefined ? _.top    : margin.top;
8794             margin.right  = _.right  !== undefined ? _.right  : margin.right;
8795             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8796             margin.left   = _.left   !== undefined ? _.left   : margin.left;
8797         }},
8798         duration: {get: function(){return duration;}, set: function(_){
8799             duration = _;
8800             renderWatch.reset(duration);
8801         }},
8802         color:  {get: function(){return color;}, set: function(_){
8803             color = nv.utils.getColor(_);
8804         }},
8805         barColor:  {get: function(){return barColor;}, set: function(_){
8806             barColor = _ ? nv.utils.getColor(_) : null;
8807         }}
8808     });
8809
8810     nv.utils.initOptions(chart);
8811
8812     return chart;
8813 };
8814
8815 nv.models.multiBarHorizontalChart = function() {
8816     "use strict";
8817
8818     //============================================================
8819     // Public Variables with Default Settings
8820     //------------------------------------------------------------
8821
8822     var multibar = nv.models.multiBarHorizontal()
8823         , xAxis = nv.models.axis()
8824         , yAxis = nv.models.axis()
8825         , legend = nv.models.legend().height(30)
8826         , controls = nv.models.legend().height(30)
8827         , tooltip = nv.models.tooltip()
8828         ;
8829
8830     var margin = {top: 30, right: 20, bottom: 50, left: 60}
8831         , width = null
8832         , height = null
8833         , color = nv.utils.defaultColor()
8834         , showControls = true
8835         , controlLabels = {}
8836         , showLegend = true
8837         , showXAxis = true
8838         , showYAxis = true
8839         , stacked = false
8840         , x //can be accessed via chart.xScale()
8841         , y //can be accessed via chart.yScale()
8842         , state = nv.utils.state()
8843         , defaultState = null
8844         , noData = null
8845         , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
8846         , controlWidth = function() { return showControls ? 180 : 0 }
8847         , duration = 250
8848         ;
8849
8850     state.stacked = false; // DEPRECATED Maintained for backward compatibility
8851
8852     multibar.stacked(stacked);
8853
8854     xAxis
8855         .orient('left')
8856         .tickPadding(5)
8857         .showMaxMin(false)
8858         .tickFormat(function(d) { return d })
8859     ;
8860     yAxis
8861         .orient('bottom')
8862         .tickFormat(d3.format(',.1f'))
8863     ;
8864
8865     tooltip
8866         .duration(0)
8867         .valueFormatter(function(d, i) {
8868             return yAxis.tickFormat()(d, i);
8869         })
8870         .headerFormatter(function(d, i) {
8871             return xAxis.tickFormat()(d, i);
8872         });
8873
8874     controls.updateState(false);
8875
8876     //============================================================
8877     // Private Variables
8878     //------------------------------------------------------------
8879
8880     var stateGetter = function(data) {
8881         return function(){
8882             return {
8883                 active: data.map(function(d) { return !d.disabled }),
8884                 stacked: stacked
8885             };
8886         }
8887     };
8888
8889     var stateSetter = function(data) {
8890         return function(state) {
8891             if (state.stacked !== undefined)
8892                 stacked = state.stacked;
8893             if (state.active !== undefined)
8894                 data.forEach(function(series,i) {
8895                     series.disabled = !state.active[i];
8896                 });
8897         }
8898     };
8899
8900     var renderWatch = nv.utils.renderWatch(dispatch, duration);
8901
8902     function chart(selection) {
8903         renderWatch.reset();
8904         renderWatch.models(multibar);
8905         if (showXAxis) renderWatch.models(xAxis);
8906         if (showYAxis) renderWatch.models(yAxis);
8907
8908         selection.each(function(data) {
8909             var container = d3.select(this),
8910                 that = this;
8911             nv.utils.initSVG(container);
8912             var availableWidth = nv.utils.availableWidth(width, container, margin),
8913                 availableHeight = nv.utils.availableHeight(height, container, margin);
8914
8915             chart.update = function() { container.transition().duration(duration).call(chart) };
8916             chart.container = this;
8917
8918             stacked = multibar.stacked();
8919
8920             state
8921                 .setter(stateSetter(data), chart.update)
8922                 .getter(stateGetter(data))
8923                 .update();
8924
8925             // DEPRECATED set state.disableddisabled
8926             state.disabled = data.map(function(d) { return !!d.disabled });
8927
8928             if (!defaultState) {
8929                 var key;
8930                 defaultState = {};
8931                 for (key in state) {
8932                     if (state[key] instanceof Array)
8933                         defaultState[key] = state[key].slice(0);
8934                     else
8935                         defaultState[key] = state[key];
8936                 }
8937             }
8938
8939             // Display No Data message if there's nothing to show.
8940             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8941                 nv.utils.noData(chart, container)
8942                 return chart;
8943             } else {
8944                 container.selectAll('.nv-noData').remove();
8945             }
8946
8947             // Setup Scales
8948             x = multibar.xScale();
8949             y = multibar.yScale();
8950
8951             // Setup containers and skeleton of chart
8952             var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8953             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8954             var g = wrap.select('g');
8955
8956             gEnter.append('g').attr('class', 'nv-x nv-axis');
8957             gEnter.append('g').attr('class', 'nv-y nv-axis')
8958                 .append('g').attr('class', 'nv-zeroLine')
8959                 .append('line');
8960             gEnter.append('g').attr('class', 'nv-barsWrap');
8961             gEnter.append('g').attr('class', 'nv-legendWrap');
8962             gEnter.append('g').attr('class', 'nv-controlsWrap');
8963
8964             // Legend
8965             if (showLegend) {
8966                 legend.width(availableWidth - controlWidth());
8967
8968                 g.select('.nv-legendWrap')
8969                     .datum(data)
8970                     .call(legend);
8971
8972                 if ( margin.top != legend.height()) {
8973                     margin.top = legend.height();
8974                     availableHeight = nv.utils.availableHeight(height, container, margin);
8975                 }
8976
8977                 g.select('.nv-legendWrap')
8978                     .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8979             }
8980
8981             // Controls
8982             if (showControls) {
8983                 var controlsData = [
8984                     { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8985                     { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8986                 ];
8987
8988                 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8989                 g.select('.nv-controlsWrap')
8990                     .datum(controlsData)
8991                     .attr('transform', 'translate(0,' + (-margin.top) +')')
8992                     .call(controls);
8993             }
8994
8995             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8996
8997             // Main Chart Component(s)
8998             multibar
8999                 .disabled(data.map(function(series) { return series.disabled }))
9000                 .width(availableWidth)
9001                 .height(availableHeight)
9002                 .color(data.map(function(d,i) {
9003                     return d.color || color(d, i);
9004                 }).filter(function(d,i) { return !data[i].disabled }));
9005
9006             var barsWrap = g.select('.nv-barsWrap')
9007                 .datum(data.filter(function(d) { return !d.disabled }));
9008
9009             barsWrap.transition().call(multibar);
9010
9011             // Setup Axes
9012             if (showXAxis) {
9013                 xAxis
9014                     .scale(x)
9015                     ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
9016                     .tickSize(-availableWidth, 0);
9017
9018                 g.select('.nv-x.nv-axis').call(xAxis);
9019
9020                 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9021
9022                 xTicks
9023                     .selectAll('line, text');
9024             }
9025
9026             if (showYAxis) {
9027                 yAxis
9028                     .scale(y)
9029                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9030                     .tickSize( -availableHeight, 0);
9031
9032                 g.select('.nv-y.nv-axis')
9033                     .attr('transform', 'translate(0,' + availableHeight + ')');
9034                 g.select('.nv-y.nv-axis').call(yAxis);
9035             }
9036
9037             // Zero line
9038             g.select(".nv-zeroLine line")
9039                 .attr("x1", y(0))
9040                 .attr("x2", y(0))
9041                 .attr("y1", 0)
9042                 .attr("y2", -availableHeight)
9043             ;
9044
9045             //============================================================
9046             // Event Handling/Dispatching (in chart's scope)
9047             //------------------------------------------------------------
9048
9049             legend.dispatch.on('stateChange', function(newState) {
9050                 for (var key in newState)
9051                     state[key] = newState[key];
9052                 dispatch.stateChange(state);
9053                 chart.update();
9054             });
9055
9056             controls.dispatch.on('legendClick', function(d,i) {
9057                 if (!d.disabled) return;
9058                 controlsData = controlsData.map(function(s) {
9059                     s.disabled = true;
9060                     return s;
9061                 });
9062                 d.disabled = false;
9063
9064                 switch (d.key) {
9065                     case 'Grouped':
9066                         multibar.stacked(false);
9067                         break;
9068                     case 'Stacked':
9069                         multibar.stacked(true);
9070                         break;
9071                 }
9072
9073                 state.stacked = multibar.stacked();
9074                 dispatch.stateChange(state);
9075                 stacked = multibar.stacked();
9076
9077                 chart.update();
9078             });
9079
9080             // Update chart from a state object passed to event handler
9081             dispatch.on('changeState', function(e) {
9082
9083                 if (typeof e.disabled !== 'undefined') {
9084                     data.forEach(function(series,i) {
9085                         series.disabled = e.disabled[i];
9086                     });
9087
9088                     state.disabled = e.disabled;
9089                 }
9090
9091                 if (typeof e.stacked !== 'undefined') {
9092                     multibar.stacked(e.stacked);
9093                     state.stacked = e.stacked;
9094                     stacked = e.stacked;
9095                 }
9096
9097                 chart.update();
9098             });
9099         });
9100         renderWatch.renderEnd('multibar horizontal chart immediate');
9101         return chart;
9102     }
9103
9104     //============================================================
9105     // Event Handling/Dispatching (out of chart's scope)
9106     //------------------------------------------------------------
9107
9108     multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
9109         evt.value = chart.x()(evt.data);
9110         evt['series'] = {
9111             key: evt.data.key,
9112             value: chart.y()(evt.data),
9113             color: evt.color
9114         };
9115         tooltip.data(evt).hidden(false);
9116     });
9117
9118     multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
9119         tooltip.hidden(true);
9120     });
9121
9122     multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
9123         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9124     });
9125
9126     //============================================================
9127     // Expose Public Variables
9128     //------------------------------------------------------------
9129
9130     // expose chart's sub-components
9131     chart.dispatch = dispatch;
9132     chart.multibar = multibar;
9133     chart.legend = legend;
9134     chart.controls = controls;
9135     chart.xAxis = xAxis;
9136     chart.yAxis = yAxis;
9137     chart.state = state;
9138     chart.tooltip = tooltip;
9139
9140     chart.options = nv.utils.optionsFunc.bind(chart);
9141
9142     chart._options = Object.create({}, {
9143         // simple options, just get/set the necessary values
9144         width:      {get: function(){return width;}, set: function(_){width=_;}},
9145         height:     {get: function(){return height;}, set: function(_){height=_;}},
9146         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
9147         showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
9148         controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
9149         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
9150         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
9151         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
9152         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
9153
9154         // deprecated options
9155         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
9156             // deprecated after 1.7.1
9157             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
9158             tooltip.enabled(!!_);
9159         }},
9160         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
9161             // deprecated after 1.7.1
9162             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
9163             tooltip.contentGenerator(_);
9164         }},
9165
9166         // options that require extra logic in the setter
9167         margin: {get: function(){return margin;}, set: function(_){
9168             margin.top    = _.top    !== undefined ? _.top    : margin.top;
9169             margin.right  = _.right  !== undefined ? _.right  : margin.right;
9170             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9171             margin.left   = _.left   !== undefined ? _.left   : margin.left;
9172         }},
9173         duration: {get: function(){return duration;}, set: function(_){
9174             duration = _;
9175             renderWatch.reset(duration);
9176             multibar.duration(duration);
9177             xAxis.duration(duration);
9178             yAxis.duration(duration);
9179         }},
9180         color:  {get: function(){return color;}, set: function(_){
9181             color = nv.utils.getColor(_);
9182             legend.color(color);
9183         }},
9184         barColor:  {get: function(){return multibar.barColor;}, set: function(_){
9185             multibar.barColor(_);
9186             legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
9187         }}
9188     });
9189
9190     nv.utils.inheritOptions(chart, multibar);
9191     nv.utils.initOptions(chart);
9192
9193     return chart;
9194 };
9195 nv.models.multiChart = function() {
9196     "use strict";
9197
9198     //============================================================
9199     // Public Variables with Default Settings
9200     //------------------------------------------------------------
9201
9202     var margin = {top: 30, right: 20, bottom: 50, left: 60},
9203         color = nv.utils.defaultColor(),
9204         width = null,
9205         height = null,
9206         showLegend = true,
9207         noData = null,
9208         yDomain1,
9209         yDomain2,
9210         getX = function(d) { return d.x },
9211         getY = function(d) { return d.y},
9212         interpolate = 'monotone',
9213         useVoronoi = true
9214         ;
9215
9216     //============================================================
9217     // Private Variables
9218     //------------------------------------------------------------
9219
9220     var x = d3.scale.linear(),
9221         yScale1 = d3.scale.linear(),
9222         yScale2 = d3.scale.linear(),
9223
9224         lines1 = nv.models.line().yScale(yScale1),
9225         lines2 = nv.models.line().yScale(yScale2),
9226
9227         bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9228         bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9229
9230         stack1 = nv.models.stackedArea().yScale(yScale1),
9231         stack2 = nv.models.stackedArea().yScale(yScale2),
9232
9233         xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9234         yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9235         yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9236
9237         legend = nv.models.legend().height(30),
9238         tooltip = nv.models.tooltip(),
9239         dispatch = d3.dispatch();
9240
9241     function chart(selection) {
9242         selection.each(function(data) {
9243             var container = d3.select(this),
9244                 that = this;
9245             nv.utils.initSVG(container);
9246
9247             chart.update = function() { container.transition().call(chart); };
9248             chart.container = this;
9249
9250             var availableWidth = nv.utils.availableWidth(width, container, margin),
9251                 availableHeight = nv.utils.availableHeight(height, container, margin);
9252
9253             var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
9254             var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
9255             var dataBars1 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 1});
9256             var dataBars2 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 2});
9257             var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
9258             var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
9259
9260             // Display noData message if there's nothing to show.
9261             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9262                 nv.utils.noData(chart, container);
9263                 return chart;
9264             } else {
9265                 container.selectAll('.nv-noData').remove();
9266             }
9267
9268             var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9269                 .map(function(d) {
9270                     return d.values.map(function(d,i) {
9271                         return { x: d.x, y: d.y }
9272                     })
9273                 });
9274
9275             var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9276                 .map(function(d) {
9277                     return d.values.map(function(d,i) {
9278                         return { x: d.x, y: d.y }
9279                     })
9280                 });
9281
9282             x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9283                 .range([0, availableWidth]);
9284
9285             var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9286             var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9287
9288             gEnter.append('g').attr('class', 'nv-x nv-axis');
9289             gEnter.append('g').attr('class', 'nv-y1 nv-axis');
9290             gEnter.append('g').attr('class', 'nv-y2 nv-axis');
9291             gEnter.append('g').attr('class', 'lines1Wrap');
9292             gEnter.append('g').attr('class', 'lines2Wrap');
9293             gEnter.append('g').attr('class', 'bars1Wrap');
9294             gEnter.append('g').attr('class', 'bars2Wrap');
9295             gEnter.append('g').attr('class', 'stack1Wrap');
9296             gEnter.append('g').attr('class', 'stack2Wrap');
9297             gEnter.append('g').attr('class', 'legendWrap');
9298
9299             var g = wrap.select('g');
9300
9301             var color_array = data.map(function(d,i) {
9302                 return data[i].color || color(d, i);
9303             });
9304
9305             if (showLegend) {
9306                 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
9307                 var legendXPosition = legend.align() ? legendWidth : 0;
9308
9309                 legend.width(legendWidth);
9310                 legend.color(color_array);
9311
9312                 g.select('.legendWrap')
9313                     .datum(data.map(function(series) {
9314                         series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9315                         series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9316                         return series;
9317                     }))
9318                     .call(legend);
9319
9320                 if ( margin.top != legend.height()) {
9321                     margin.top = legend.height();
9322                     availableHeight = nv.utils.availableHeight(height, container, margin);
9323                 }
9324
9325                 g.select('.legendWrap')
9326                     .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
9327             }
9328
9329             lines1
9330                 .width(availableWidth)
9331                 .height(availableHeight)
9332                 .interpolate(interpolate)
9333                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9334             lines2
9335                 .width(availableWidth)
9336                 .height(availableHeight)
9337                 .interpolate(interpolate)
9338                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9339             bars1
9340                 .width(availableWidth)
9341                 .height(availableHeight)
9342                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9343             bars2
9344                 .width(availableWidth)
9345                 .height(availableHeight)
9346                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9347             stack1
9348                 .width(availableWidth)
9349                 .height(availableHeight)
9350                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9351             stack2
9352                 .width(availableWidth)
9353                 .height(availableHeight)
9354                 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9355
9356             g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9357
9358             var lines1Wrap = g.select('.lines1Wrap')
9359                 .datum(dataLines1.filter(function(d){return !d.disabled}));
9360             var bars1Wrap = g.select('.bars1Wrap')
9361                 .datum(dataBars1.filter(function(d){return !d.disabled}));
9362             var stack1Wrap = g.select('.stack1Wrap')
9363                 .datum(dataStack1.filter(function(d){return !d.disabled}));
9364             var lines2Wrap = g.select('.lines2Wrap')
9365                 .datum(dataLines2.filter(function(d){return !d.disabled}));
9366             var bars2Wrap = g.select('.bars2Wrap')
9367                 .datum(dataBars2.filter(function(d){return !d.disabled}));
9368             var stack2Wrap = g.select('.stack2Wrap')
9369                 .datum(dataStack2.filter(function(d){return !d.disabled}));
9370
9371             var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9372                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9373             }).concat([{x:0, y:0}]) : [];
9374             var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9375                 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9376             }).concat([{x:0, y:0}]) : [];
9377
9378             yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9379                 .range([0, availableHeight]);
9380
9381             yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9382                 .range([0, availableHeight]);
9383
9384             lines1.yDomain(yScale1.domain());
9385             bars1.yDomain(yScale1.domain());
9386             stack1.yDomain(yScale1.domain());
9387
9388             lines2.yDomain(yScale2.domain());
9389             bars2.yDomain(yScale2.domain());
9390             stack2.yDomain(yScale2.domain());
9391
9392             if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9393             if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9394
9395             if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9396             if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9397
9398             if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9399             if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9400
9401             xAxis
9402                 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9403                 .tickSize(-availableHeight, 0);
9404
9405             g.select('.nv-x.nv-axis')
9406                 .attr('transform', 'translate(0,' + availableHeight + ')');
9407             d3.transition(g.select('.nv-x.nv-axis'))
9408                 .call(xAxis);
9409
9410             yAxis1
9411                 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9412                 .tickSize( -availableWidth, 0);
9413
9414
9415             d3.transition(g.select('.nv-y1.nv-axis'))
9416                 .call(yAxis1);
9417
9418             yAxis2
9419                 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9420                 .tickSize( -availableWidth, 0);
9421
9422             d3.transition(g.select('.nv-y2.nv-axis'))
9423                 .call(yAxis2);
9424
9425             g.select('.nv-y1.nv-axis')
9426                 .classed('nv-disabled', series1.length ? false : true)
9427                 .attr('transform', 'translate(' + x.range()[0] + ',0)');
9428
9429             g.select('.nv-y2.nv-axis')
9430                 .classed('nv-disabled', series2.length ? false : true)
9431                 .attr('transform', 'translate(' + x.range()[1] + ',0)');
9432
9433             legend.dispatch.on('stateChange', function(newState) {
9434                 chart.update();
9435             });
9436
9437             //============================================================
9438             // Event Handling/Dispatching
9439             //------------------------------------------------------------
9440
9441             function mouseover_line(evt) {
9442                 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
9443                 evt.value = evt.point.x;
9444                 evt.series = {
9445                     value: evt.point.y,
9446                     color: evt.point.color
9447                 };
9448                 tooltip
9449                     .duration(100)
9450                     .valueFormatter(function(d, i) {
9451                         return yaxis.tickFormat()(d, i);
9452                     })
9453                     .data(evt)
9454                     .position(evt.pos)
9455                     .hidden(false);
9456             }
9457
9458             function mouseover_stack(evt) {
9459                 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
9460                 evt.point['x'] = stack1.x()(evt.point);
9461                 evt.point['y'] = stack1.y()(evt.point);
9462                 tooltip
9463                     .duration(100)
9464                     .valueFormatter(function(d, i) {
9465                         return yaxis.tickFormat()(d, i);
9466                     })
9467                     .data(evt)
9468                     .position(evt.pos)
9469                     .hidden(false);
9470             }
9471
9472             function mouseover_bar(evt) {
9473                 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
9474
9475                 evt.value = bars1.x()(evt.data);
9476                 evt['series'] = {
9477                     value: bars1.y()(evt.data),
9478                     color: evt.color
9479                 };
9480                 tooltip
9481                     .duration(0)
9482                     .valueFormatter(function(d, i) {
9483                         return yaxis.tickFormat()(d, i);
9484                     })
9485                     .data(evt)
9486                     .hidden(false);
9487             }
9488
9489             lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
9490             lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
9491             lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
9492                 tooltip.hidden(true)
9493             });
9494             lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
9495                 tooltip.hidden(true)
9496             });
9497
9498             stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
9499             stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
9500             stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
9501                 tooltip.hidden(true)
9502             });
9503             stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
9504                 tooltip.hidden(true)
9505             });
9506
9507             bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9508             bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9509
9510             bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
9511                 tooltip.hidden(true);
9512             });
9513             bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
9514                 tooltip.hidden(true);
9515             });
9516             bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
9517                 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9518             });
9519             bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
9520                 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9521             });
9522
9523         });
9524
9525         return chart;
9526     }
9527
9528     //============================================================
9529     // Global getters and setters
9530     //------------------------------------------------------------
9531
9532     chart.dispatch = dispatch;
9533     chart.lines1 = lines1;
9534     chart.lines2 = lines2;
9535     chart.bars1 = bars1;
9536     chart.bars2 = bars2;
9537     chart.stack1 = stack1;
9538     chart.stack2 = stack2;
9539     chart.xAxis = xAxis;
9540     chart.yAxis1 = yAxis1;
9541     chart.yAxis2 = yAxis2;
9542     chart.tooltip = tooltip;
9543
9544     chart.options = nv.utils.optionsFunc.bind(chart);
9545
9546     chart._options = Object.create({}, {
9547         // simple options, just get/set the necessary values
9548         width:      {get: function(){return width;}, set: function(_){width=_;}},
9549         height:     {get: function(){return height;}, set: function(_){height=_;}},
9550         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
9551         yDomain1:      {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
9552         yDomain2:    {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
9553         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
9554         interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
9555
9556         // deprecated options
9557         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
9558             // deprecated after 1.7.1
9559             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
9560             tooltip.enabled(!!_);
9561         }},
9562         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
9563             // deprecated after 1.7.1
9564             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
9565             tooltip.contentGenerator(_);
9566         }},
9567
9568         // options that require extra logic in the setter
9569         margin: {get: function(){return margin;}, set: function(_){
9570             margin.top    = _.top    !== undefined ? _.top    : margin.top;
9571             margin.right  = _.right  !== undefined ? _.right  : margin.right;
9572             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9573             margin.left   = _.left   !== undefined ? _.left   : margin.left;
9574         }},
9575         color:  {get: function(){return color;}, set: function(_){
9576             color = nv.utils.getColor(_);
9577         }},
9578         x: {get: function(){return getX;}, set: function(_){
9579             getX = _;
9580             lines1.x(_);
9581             lines2.x(_);
9582             bars1.x(_);
9583             bars2.x(_);
9584             stack1.x(_);
9585             stack2.x(_);
9586         }},
9587         y: {get: function(){return getY;}, set: function(_){
9588             getY = _;
9589             lines1.y(_);
9590             lines2.y(_);
9591             stack1.y(_);
9592             stack2.y(_);
9593             bars1.y(_);
9594             bars2.y(_);
9595         }},
9596         useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
9597             useVoronoi=_;
9598             lines1.useVoronoi(_);
9599             lines2.useVoronoi(_);
9600             stack1.useVoronoi(_);
9601             stack2.useVoronoi(_);
9602         }}
9603     });
9604
9605     nv.utils.initOptions(chart);
9606
9607     return chart;
9608 };
9609
9610
9611 nv.models.ohlcBar = function() {
9612     "use strict";
9613
9614     //============================================================
9615     // Public Variables with Default Settings
9616     //------------------------------------------------------------
9617
9618     var margin = {top: 0, right: 0, bottom: 0, left: 0}
9619         , width = null
9620         , height = null
9621         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9622         , container = null
9623         , x = d3.scale.linear()
9624         , y = d3.scale.linear()
9625         , getX = function(d) { return d.x }
9626         , getY = function(d) { return d.y }
9627         , getOpen = function(d) { return d.open }
9628         , getClose = function(d) { return d.close }
9629         , getHigh = function(d) { return d.high }
9630         , getLow = function(d) { return d.low }
9631         , forceX = []
9632         , forceY = []
9633         , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9634         , clipEdge = true
9635         , color = nv.utils.defaultColor()
9636         , interactive = false
9637         , xDomain
9638         , yDomain
9639         , xRange
9640         , yRange
9641         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
9642         ;
9643
9644     //============================================================
9645     // Private Variables
9646     //------------------------------------------------------------
9647
9648     function chart(selection) {
9649         selection.each(function(data) {
9650             container = d3.select(this);
9651             var availableWidth = nv.utils.availableWidth(width, container, margin),
9652                 availableHeight = nv.utils.availableHeight(height, container, margin);
9653
9654             nv.utils.initSVG(container);
9655
9656             // ohlc bar width.
9657             var w = (availableWidth / data[0].values.length) * .9;
9658
9659             // Setup Scales
9660             x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9661
9662             if (padData)
9663                 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
9664             else
9665                 x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
9666
9667             y.domain(yDomain || [
9668                     d3.min(data[0].values.map(getLow).concat(forceY)),
9669                     d3.max(data[0].values.map(getHigh).concat(forceY))
9670                 ]
9671             ).range(yRange || [availableHeight, 0]);
9672
9673             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9674             if (x.domain()[0] === x.domain()[1])
9675                 x.domain()[0] ?
9676                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9677                     : x.domain([-1,1]);
9678
9679             if (y.domain()[0] === y.domain()[1])
9680                 y.domain()[0] ?
9681                     y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9682                     : y.domain([-1,1]);
9683
9684             // Setup containers and skeleton of chart
9685             var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9686             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9687             var defsEnter = wrapEnter.append('defs');
9688             var gEnter = wrapEnter.append('g');
9689             var g = wrap.select('g');
9690
9691             gEnter.append('g').attr('class', 'nv-ticks');
9692
9693             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9694
9695             container
9696                 .on('click', function(d,i) {
9697                     dispatch.chartClick({
9698                         data: d,
9699                         index: i,
9700                         pos: d3.event,
9701                         id: id
9702                     });
9703                 });
9704
9705             defsEnter.append('clipPath')
9706                 .attr('id', 'nv-chart-clip-path-' + id)
9707                 .append('rect');
9708
9709             wrap.select('#nv-chart-clip-path-' + id + ' rect')
9710                 .attr('width', availableWidth)
9711                 .attr('height', availableHeight);
9712
9713             g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9714
9715             var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9716                 .data(function(d) { return d });
9717             ticks.exit().remove();
9718
9719             ticks.enter().append('path')
9720                 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9721                 .attr('d', function(d,i) {
9722                     return 'm0,0l0,'
9723                         + (y(getOpen(d,i))
9724                             - y(getHigh(d,i)))
9725                         + 'l'
9726                         + (-w/2)
9727                         + ',0l'
9728                         + (w/2)
9729                         + ',0l0,'
9730                         + (y(getLow(d,i)) - y(getOpen(d,i)))
9731                         + 'l0,'
9732                         + (y(getClose(d,i))
9733                             - y(getLow(d,i)))
9734                         + 'l'
9735                         + (w/2)
9736                         + ',0l'
9737                         + (-w/2)
9738                         + ',0z';
9739                 })
9740                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9741                 .attr('fill', function(d,i) { return color[0]; })
9742                 .attr('stroke', function(d,i) { return color[0]; })
9743                 .attr('x', 0 )
9744                 .attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
9745                 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9746
9747             // the bar colors are controlled by CSS currently
9748             ticks.attr('class', function(d,i,j) {
9749                 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
9750             });
9751
9752             d3.transition(ticks)
9753                 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9754                 .attr('d', function(d,i) {
9755                     var w = (availableWidth / data[0].values.length) * .9;
9756                     return 'm0,0l0,'
9757                         + (y(getOpen(d,i))
9758                             - y(getHigh(d,i)))
9759                         + 'l'
9760                         + (-w/2)
9761                         + ',0l'
9762                         + (w/2)
9763                         + ',0l0,'
9764                         + (y(getLow(d,i))
9765                             - y(getOpen(d,i)))
9766                         + 'l0,'
9767                         + (y(getClose(d,i))
9768                             - y(getLow(d,i)))
9769                         + 'l'
9770                         + (w/2)
9771                         + ',0l'
9772                         + (-w/2)
9773                         + ',0z';
9774                 });
9775         });
9776
9777         return chart;
9778     }
9779
9780
9781     //Create methods to allow outside functions to highlight a specific bar.
9782     chart.highlightPoint = function(pointIndex, isHoverOver) {
9783         chart.clearHighlights();
9784         container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
9785             .classed("hover", isHoverOver)
9786         ;
9787     };
9788
9789     chart.clearHighlights = function() {
9790         container.select(".nv-ohlcBar .nv-tick.hover")
9791             .classed("hover", false)
9792         ;
9793     };
9794
9795     //============================================================
9796     // Expose Public Variables
9797     //------------------------------------------------------------
9798
9799     chart.dispatch = dispatch;
9800     chart.options = nv.utils.optionsFunc.bind(chart);
9801
9802     chart._options = Object.create({}, {
9803         // simple options, just get/set the necessary values
9804         width:    {get: function(){return width;}, set: function(_){width=_;}},
9805         height:   {get: function(){return height;}, set: function(_){height=_;}},
9806         xScale:   {get: function(){return x;}, set: function(_){x=_;}},
9807         yScale:   {get: function(){return y;}, set: function(_){y=_;}},
9808         xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
9809         yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
9810         xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
9811         yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
9812         forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
9813         forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
9814         padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
9815         clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
9816         id:       {get: function(){return id;}, set: function(_){id=_;}},
9817         interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
9818
9819         x:     {get: function(){return getX;}, set: function(_){getX=_;}},
9820         y:     {get: function(){return getY;}, set: function(_){getY=_;}},
9821         open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
9822         close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
9823         high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
9824         low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},
9825
9826         // options that require extra logic in the setter
9827         margin: {get: function(){return margin;}, set: function(_){
9828             margin.top    = _.top    != undefined ? _.top    : margin.top;
9829             margin.right  = _.right  != undefined ? _.right  : margin.right;
9830             margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
9831             margin.left   = _.left   != undefined ? _.left   : margin.left;
9832         }},
9833         color:  {get: function(){return color;}, set: function(_){
9834             color = nv.utils.getColor(_);
9835         }}
9836     });
9837
9838     nv.utils.initOptions(chart);
9839     return chart;
9840 };
9841 // Code adapted from Jason Davies' "Parallel Coordinates"
9842 // http://bl.ocks.org/jasondavies/1341281
9843 nv.models.parallelCoordinates = function() {
9844     "use strict";
9845
9846     //============================================================
9847     // Public Variables with Default Settings
9848     //------------------------------------------------------------
9849
9850     var margin = {top: 30, right: 0, bottom: 10, left: 0}
9851         , width = null
9852         , height = null
9853         , x = d3.scale.ordinal()
9854         , y = {}
9855         , dimensionNames = []
9856         , dimensionFormats = []
9857         , color = nv.utils.defaultColor()
9858         , filters = []
9859         , active = []
9860         , dragging = []
9861         , lineTension = 1
9862         , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout')
9863         ;
9864
9865     //============================================================
9866     // Private Variables
9867     //------------------------------------------------------------
9868
9869     function chart(selection) {
9870         selection.each(function(data) {
9871             var container = d3.select(this);
9872             var availableWidth = nv.utils.availableWidth(width, container, margin),
9873                 availableHeight = nv.utils.availableHeight(height, container, margin);
9874
9875             nv.utils.initSVG(container);
9876
9877             active = data; //set all active before first brush call
9878
9879             // Setup Scales
9880             x.rangePoints([0, availableWidth], 1).domain(dimensionNames);
9881
9882             //Set as true if all values on an axis are missing.
9883             var onlyNanValues = {};
9884             // Extract the list of dimensions and create a scale for each.
9885             dimensionNames.forEach(function(d) {
9886                 var extent = d3.extent(data, function(p) { return +p[d]; });
9887                 onlyNanValues[d] = false;
9888                 //If there is no values to display on an axis, set the extent to 0
9889                 if (extent[0] === undefined) {
9890                     onlyNanValues[d] = true;
9891                     extent[0] = 0;
9892                     extent[1] = 0;
9893                 }
9894                 //Scale axis if there is only one value
9895                 if (extent[0] === extent[1]) {
9896                     extent[0] = extent[0] - 1;
9897                     extent[1] = extent[1] + 1;
9898                 }
9899                 //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
9900                 //The remaining 10% are used to display the missingValue line.
9901                 y[d] = d3.scale.linear()
9902                     .domain(extent)
9903                     .range([(availableHeight - 12) * 0.9, 0]);
9904
9905                 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
9906
9907                 return d != 'name';
9908             });
9909
9910             // Setup containers and skeleton of chart
9911             var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
9912             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
9913             var gEnter = wrapEnter.append('g');
9914             var g = wrap.select('g');
9915
9916             gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
9917             gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
9918             gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
9919
9920             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9921
9922             var line = d3.svg.line().interpolate('cardinal').tension(lineTension),
9923                 axis = d3.svg.axis().orient('left'),
9924                 axisDrag = d3.behavior.drag()
9925                         .on('dragstart', dragStart)
9926                         .on('drag', dragMove)
9927                         .on('dragend', dragEnd);
9928
9929             //Add missing value line at the bottom of the chart
9930             var missingValuesline, missingValueslineText;
9931             var step = x.range()[1] - x.range()[0];
9932             var axisWithMissingValues = [];
9933             var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
9934             missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
9935             missingValuesline.enter().append('line');
9936             missingValuesline.exit().remove();
9937             missingValuesline.attr("x1", function(d) { return d[0]; })
9938                     .attr("y1", function(d) { return d[1]; })
9939                     .attr("x2", function(d) { return d[2]; })
9940                     .attr("y2", function(d) { return d[3]; });
9941
9942             //Add the text "undefined values" under the missing value line
9943             missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]);
9944             missingValueslineText.append('text').data(["undefined values"]);
9945             missingValueslineText.enter().append('text');
9946             missingValueslineText.exit().remove();
9947             missingValueslineText.attr("y", availableHeight)
9948                     //To have the text right align with the missingValues line, substract 92 representing the text size.
9949                     .attr("x", availableWidth - 92 - step / 2)
9950                     .text(function(d) { return d; });
9951
9952             // Add grey background lines for context.
9953             var background = wrap.select('.background').selectAll('path').data(data);
9954             background.enter().append('path');
9955             background.exit().remove();
9956             background.attr('d', path);
9957
9958             // Add blue foreground lines for focus.
9959             var foreground = wrap.select('.foreground').selectAll('path').data(data);
9960             foreground.enter().append('path')
9961             foreground.exit().remove();
9962             foreground.attr('d', path).attr('stroke', color);
9963             foreground.on("mouseover", function (d, i) {
9964                 d3.select(this).classed('hover', true);
9965                 dispatch.elementMouseover({
9966                     label: d.name,
9967                     data: d.data,
9968                     index: i,
9969                     pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]]
9970                 });
9971
9972             });
9973             foreground.on("mouseout", function (d, i) {
9974                 d3.select(this).classed('hover', false);
9975                 dispatch.elementMouseout({
9976                     label: d.name,
9977                     data: d.data,
9978                     index: i
9979                 });
9980             });
9981
9982             // Add a group element for each dimension.
9983             var dimensions = g.selectAll('.dimension').data(dimensionNames);
9984             var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
9985             dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis');
9986             dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush');
9987             dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label');
9988
9989             dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
9990             dimensions.exit().remove();
9991
9992             // Add an axis and title.
9993             dimensions.select('.nv-label')
9994                 .style("cursor", "move")
9995                 .attr('dy', '-1em')
9996                 .attr('text-anchor', 'middle')
9997                 .text(String)
9998                 .on("mouseover", function(d, i) {
9999                     dispatch.elementMouseover({
10000                         dim: d,
10001                         pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]]
10002                     });
10003                 })
10004                 .on("mouseout", function(d, i) {
10005                     dispatch.elementMouseout({
10006                         dim: d
10007                     });
10008                 })
10009                 .call(axisDrag);
10010
10011             dimensions.select('.nv-axis')
10012                 .each(function (d, i) {
10013                     d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i])));
10014                 });
10015
10016                 dimensions.select('.nv-parallelCoordinates-brush')
10017                 .each(function (d) {
10018                     d3.select(this).call(y[d].brush);
10019                 })
10020                 .selectAll('rect')
10021                 .attr('x', -8)
10022                 .attr('width', 16);
10023
10024             // Returns the path for a given data point.
10025             function path(d) {
10026                 return line(dimensionNames.map(function (p) {
10027                     //If value if missing, put the value on the missing value line
10028                     if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) {
10029                         var domain = y[p].domain();
10030                         var range = y[p].range();
10031                         var min = domain[0] - (domain[1] - domain[0]) / 9;
10032
10033                         //If it's not already the case, allow brush to select undefined values
10034                         if(axisWithMissingValues.indexOf(p) < 0) {
10035
10036                             var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
10037                             y[p].brush.y(newscale);
10038                             axisWithMissingValues.push(p);
10039                         }
10040
10041                         return [x(p), y[p](min)];
10042                     }
10043
10044                     //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
10045                     if(axisWithMissingValues.length > 0) {
10046                         missingValuesline.style("display", "inline");
10047                         missingValueslineText.style("display", "inline");
10048                     } else {
10049                         missingValuesline.style("display", "none");
10050                         missingValueslineText.style("display", "none");
10051                     }
10052
10053                      return [x(p), y[p](d[p])];
10054                 }));
10055             }
10056
10057             // Handles a brush event, toggling the display of foreground lines.
10058             function brush() {
10059                 var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }),
10060                     extents = actives.map(function(p) { return y[p].brush.extent(); });
10061
10062                 filters = []; //erase current filters
10063                 actives.forEach(function(d,i) {
10064                     filters[i] = {
10065                         dimension: d,
10066                         extent: extents[i]
10067                     }
10068                 });
10069
10070                 active = []; //erase current active list
10071                 foreground.style('display', function(d) {
10072                     var isActive = actives.every(function(p, i) {
10073                         if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
10074                         return extents[i][0] <= d[p] && d[p] <= extents[i][1];
10075                     });
10076                     if (isActive) active.push(d);
10077                     return isActive ? null : 'none';
10078                 });
10079
10080                 dispatch.brush({
10081                     filters: filters,
10082                     active: active
10083                 });
10084             }
10085
10086             function dragStart(d, i) {
10087                 dragging[d] = this.parentNode.__origin__ = x(d);
10088                 background.attr("visibility", "hidden");
10089
10090             }
10091
10092             function dragMove(d, i) {
10093                 dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
10094                 foreground.attr("d", path);
10095                 dimensionNames.sort(function (a, b) { return position(a) - position(b); });
10096                 x.domain(dimensionNames);
10097                 dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
10098             }
10099
10100             function dragEnd(d, i) {
10101                 delete this.parentNode.__origin__;
10102                 delete dragging[d];
10103                 d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")");
10104                 foreground
10105                   .attr("d", path);
10106                 background
10107                   .attr("d", path)
10108                   .attr("visibility", null);
10109
10110             }
10111
10112             function position(d) {
10113                 var v = dragging[d];
10114                 return v == null ? x(d) : v;
10115             }
10116         });
10117
10118         return chart;
10119     }
10120
10121     //============================================================
10122     // Expose Public Variables
10123     //------------------------------------------------------------
10124
10125     chart.dispatch = dispatch;
10126     chart.options = nv.utils.optionsFunc.bind(chart);
10127
10128     chart._options = Object.create({}, {
10129         // simple options, just get/set the necessary values
10130         width:         {get: function(){return width;},           set: function(_){width= _;}},
10131         height:        {get: function(){return height;},          set: function(_){height= _;}},
10132         dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}},
10133         dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}},
10134         lineTension:   {get: function(){return lineTension;},     set: function(_){lineTension = _;}},
10135
10136         // deprecated options
10137         dimensions: {get: function (){return dimensionNames;}, set: function(_){
10138             // deprecated after 1.8.1
10139             nv.deprecated('dimensions', 'use dimensionNames instead');
10140             dimensionNames = _;
10141         }},
10142
10143         // options that require extra logic in the setter
10144         margin: {get: function(){return margin;}, set: function(_){
10145             margin.top    =  _.top    !== undefined ? _.top    : margin.top;
10146             margin.right  =  _.right  !== undefined ? _.right  : margin.right;
10147             margin.bottom =  _.bottom !== undefined ? _.bottom : margin.bottom;
10148             margin.left   =  _.left   !== undefined ? _.left   : margin.left;
10149         }},
10150         color:  {get: function(){return color;}, set: function(_){
10151             color = nv.utils.getColor(_);
10152         }}
10153     });
10154
10155     nv.utils.initOptions(chart);
10156     return chart;
10157 };
10158 nv.models.pie = function() {
10159     "use strict";
10160
10161     //============================================================
10162     // Public Variables with Default Settings
10163     //------------------------------------------------------------
10164
10165     var margin = {top: 0, right: 0, bottom: 0, left: 0}
10166         , width = 500
10167         , height = 500
10168         , getX = function(d) { return d.x }
10169         , getY = function(d) { return d.y }
10170         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10171         , container = null
10172         , color = nv.utils.defaultColor()
10173         , valueFormat = d3.format(',.2f')
10174         , showLabels = true
10175         , labelsOutside = false
10176         , labelType = "key"
10177         , labelThreshold = .02 //if slice percentage is under this, don't show label
10178         , donut = false
10179         , title = false
10180         , growOnHover = true
10181         , titleOffset = 0
10182         , labelSunbeamLayout = false
10183         , startAngle = false
10184         , padAngle = false
10185         , endAngle = false
10186         , cornerRadius = 0
10187         , donutRatio = 0.5
10188         , arcsRadius = []
10189         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
10190         ;
10191
10192     var arcs = [];
10193     var arcsOver = [];
10194
10195     //============================================================
10196     // chart function
10197     //------------------------------------------------------------
10198
10199     var renderWatch = nv.utils.renderWatch(dispatch);
10200
10201     function chart(selection) {
10202         renderWatch.reset();
10203         selection.each(function(data) {
10204             var availableWidth = width - margin.left - margin.right
10205                 , availableHeight = height - margin.top - margin.bottom
10206                 , radius = Math.min(availableWidth, availableHeight) / 2
10207                 , arcsRadiusOuter = []
10208                 , arcsRadiusInner = []
10209                 ;
10210
10211             container = d3.select(this)
10212             if (arcsRadius.length === 0) {
10213                 var outer = radius - radius / 5;
10214                 var inner = donutRatio * radius;
10215                 for (var i = 0; i < data[0].length; i++) {
10216                     arcsRadiusOuter.push(outer);
10217                     arcsRadiusInner.push(inner);
10218                 }
10219             } else {
10220                 arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
10221                 arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
10222                 donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
10223             }
10224             nv.utils.initSVG(container);
10225
10226             // Setup containers and skeleton of chart
10227             var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10228             var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10229             var gEnter = wrapEnter.append('g');
10230             var g = wrap.select('g');
10231             var g_pie = gEnter.append('g').attr('class', 'nv-pie');
10232             gEnter.append('g').attr('class', 'nv-pieLabels');
10233
10234             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10235             g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10236             g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10237
10238             //
10239             container.on('click', function(d,i) {
10240                 dispatch.chartClick({
10241                     data: d,
10242                     index: i,
10243                     pos: d3.event,
10244                     id: id
10245                 });
10246             });
10247
10248             arcs = [];
10249             arcsOver = [];
10250             for (var i = 0; i < data[0].length; i++) {
10251
10252                 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
10253                 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
10254
10255                 if (startAngle !== false) {
10256                     arc.startAngle(startAngle);
10257                     arcOver.startAngle(startAngle);
10258                 }
10259                 if (endAngle !== false) {
10260                     arc.endAngle(endAngle);
10261                     arcOver.endAngle(endAngle);
10262                 }
10263                 if (donut) {
10264                     arc.innerRadius(arcsRadiusInner[i]);
10265                     arcOver.innerRadius(arcsRadiusInner[i]);
10266                 }
10267
10268                 if (arc.cornerRadius && cornerRadius) {
10269                     arc.cornerRadius(cornerRadius);
10270                     arcOver.cornerRadius(cornerRadius);
10271                 }
10272
10273                 arcs.push(arc);
10274                 arcsOver.push(arcOver);
10275             }
10276
10277             // Setup the Pie chart and choose the data element
10278             var pie = d3.layout.pie()
10279                 .sort(null)
10280                 .value(function(d) { return d.disabled ? 0 : getY(d) });
10281
10282             // padAngle added in d3 3.5
10283             if (pie.padAngle && padAngle) {
10284                 pie.padAngle(padAngle);
10285             }
10286
10287             // if title is specified and donut, put it in the middle
10288             if (donut && title) {
10289                 g_pie.append("text").attr('class', 'nv-pie-title');
10290
10291                 wrap.select('.nv-pie-title')
10292                     .style("text-anchor", "middle")
10293                     .text(function (d) {
10294                         return title;
10295                     })
10296                     .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
10297                     .attr("dy", "0.35em") // trick to vertically center text
10298                     .attr('transform', function(d, i) {
10299                         return 'translate(0, '+ titleOffset + ')';
10300                     });
10301             }
10302
10303             var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
10304             var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
10305
10306             slices.exit().remove();
10307             pieLabels.exit().remove();
10308
10309             var ae = slices.enter().append('g');
10310             ae.attr('class', 'nv-slice');
10311             ae.on('mouseover', function(d, i) {
10312                 d3.select(this).classed('hover', true);
10313                 if (growOnHover) {
10314                     d3.select(this).select("path").transition()
10315                         .duration(70)
10316                         .attr("d", arcsOver[i]);
10317                 }
10318                 dispatch.elementMouseover({
10319                     data: d.data,
10320                     index: i,
10321                     color: d3.select(this).style("fill")
10322                 });
10323             });
10324             ae.on('mouseout', function(d, i) {
10325                 d3.select(this).classed('hover', false);
10326                 if (growOnHover) {
10327                     d3.select(this).select("path").transition()
10328                         .duration(50)
10329                         .attr("d", arcs[i]);
10330                 }
10331                 dispatch.elementMouseout({data: d.data, index: i});
10332             });
10333             ae.on('mousemove', function(d, i) {
10334                 dispatch.elementMousemove({data: d.data, index: i});
10335             });
10336             ae.on('click', function(d, i) {
10337                 dispatch.elementClick({
10338                     data: d.data,
10339                     index: i,
10340                     color: d3.select(this).style("fill")
10341                 });
10342             });
10343             ae.on('dblclick', function(d, i) {
10344                 dispatch.elementDblClick({
10345                     data: d.data,
10346                     index: i,
10347                     color: d3.select(this).style("fill")
10348                 });
10349             });
10350
10351             slices.attr('fill', function(d,i) { return color(d.data, i); });
10352             slices.attr('stroke', function(d,i) { return color(d.data, i); });
10353
10354             var paths = ae.append('path').each(function(d) {
10355                 this._current = d;
10356             });
10357
10358             slices.select('path')
10359                 .transition()
10360                 .attr('d', function (d, i) { return arcs[i](d); })
10361                 .attrTween('d', arcTween);
10362
10363             if (showLabels) {
10364                 // This does the normal label
10365                 var labelsArc = [];
10366                 for (var i = 0; i < data[0].length; i++) {
10367                     labelsArc.push(arcs[i]);
10368
10369                     if (labelsOutside) {
10370                         if (donut) {
10371                             labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
10372                             if (startAngle !== false) labelsArc[i].startAngle(startAngle);
10373                             if (endAngle !== false) labelsArc[i].endAngle(endAngle);
10374                         }
10375                     } else if (!donut) {
10376                             labelsArc[i].innerRadius(0);
10377                     }
10378                 }
10379
10380                 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
10381                     var group = d3.select(this);
10382
10383                     group.attr('transform', function (d, i) {
10384                         if (labelSunbeamLayout) {
10385                             d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
10386                             d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
10387                             var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10388                             if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
10389                                 rotateAngle -= 90;
10390                             } else {
10391                                 rotateAngle += 90;
10392                             }
10393                             return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10394                         } else {
10395                             d.outerRadius = radius + 10; // Set Outer Coordinate
10396                             d.innerRadius = radius + 15; // Set Inner Coordinate
10397                             return 'translate(' + labelsArc[i].centroid(d) + ')'
10398                         }
10399                     });
10400
10401                     group.append('rect')
10402                         .style('stroke', '#fff')
10403                         .style('fill', '#fff')
10404                         .attr("rx", 3)
10405                         .attr("ry", 3);
10406
10407                     group.append('text')
10408                         .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
10409                         .style('fill', '#000')
10410                 });
10411
10412                 var labelLocationHash = {};
10413                 var avgHeight = 14;
10414                 var avgWidth = 140;
10415                 var createHashKey = function(coordinates) {
10416                     return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
10417                 };
10418
10419                 pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
10420                     if (labelSunbeamLayout) {
10421                         d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
10422                         d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
10423                         var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10424                         if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
10425                             rotateAngle -= 90;
10426                         } else {
10427                             rotateAngle += 90;
10428                         }
10429                         return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10430                     } else {
10431                         d.outerRadius = radius + 10; // Set Outer Coordinate
10432                         d.innerRadius = radius + 15; // Set Inner Coordinate
10433
10434                         /*
10435                         Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
10436                         Each label location is hashed, and if a hash collision occurs, we assume an overlap.
10437                         Adjust the label's y-position to remove the overlap.
10438                         */
10439                         var center = labelsArc[i].centroid(d);
10440                         if (d.value) {
10441                             var hashKey = createHashKey(center);
10442                             if (labelLocationHash[hashKey]) {
10443                                 center[1] -= avgHeight;
10444                             }
10445                             labelLocationHash[createHashKey(center)] = true;
10446                         }
10447                         return 'translate(' + center + ')'
10448                     }
10449                 });
10450
10451                 pieLabels.select(".nv-label text")
10452                     .style('text-anchor', function(d,i) {
10453                         //center the text on it's origin or begin/end if orthogonal aligned
10454                         return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
10455                     })
10456                     .text(function(d, i) {
10457                         var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10458                         var label = '';
10459                         if (!d.value || percent < labelThreshold) return '';
10460
10461                         if(typeof labelType === 'function') {
10462                             label = labelType(d, i, {
10463                                 'key': getX(d.data),
10464                                 'value': getY(d.data),
10465                                 'percent': valueFormat(percent)
10466                             });
10467                         } else {
10468                             switch (labelType) {
10469                                 case 'key':
10470                                     label = getX(d.data);
10471                                     break;
10472                                 case 'value':
10473                                     label = valueFormat(getY(d.data));
10474                                     break;
10475                                 case 'percent':
10476                                     label = d3.format('%')(percent);
10477                                     break;
10478                             }
10479                         }
10480                         return label;
10481                     })
10482                 ;
10483             }
10484
10485
10486             // Computes the angle of an arc, converting from radians to degrees.
10487             function angle(d) {
10488                 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10489                 return a > 90 ? a - 180 : a;
10490             }
10491
10492             function arcTween(a, idx) {
10493                 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10494                 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10495                 if (!donut) a.innerRadius = 0;
10496                 var i = d3.interpolate(this._current, a);
10497                 this._current = i(0);
10498                 return function (t) {
10499                     return arcs[idx](i(t));
10500                 };
10501             }
10502         });
10503
10504         renderWatch.renderEnd('pie immediate');
10505         return chart;
10506     }
10507
10508     //============================================================
10509     // Expose Public Variables
10510     //------------------------------------------------------------
10511
10512     chart.dispatch = dispatch;
10513     chart.options = nv.utils.optionsFunc.bind(chart);
10514
10515     chart._options = Object.create({}, {
10516         // simple options, just get/set the necessary values
10517         arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
10518         width:      {get: function(){return width;}, set: function(_){width=_;}},
10519         height:     {get: function(){return height;}, set: function(_){height=_;}},
10520         showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
10521         title:      {get: function(){return title;}, set: function(_){title=_;}},
10522         titleOffset:    {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
10523         labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
10524         valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
10525         x:          {get: function(){return getX;}, set: function(_){getX=_;}},
10526         id:         {get: function(){return id;}, set: function(_){id=_;}},
10527         endAngle:   {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
10528         startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
10529         padAngle:   {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
10530         cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
10531         donutRatio:   {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
10532         labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
10533         labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
10534         donut:              {get: function(){return donut;}, set: function(_){donut=_;}},
10535         growOnHover:        {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
10536
10537         // depreciated after 1.7.1
10538         pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
10539             labelsOutside=_;
10540             nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
10541         }},
10542         // depreciated after 1.7.1
10543         donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
10544             labelsOutside=_;
10545             nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
10546         }},
10547         // deprecated after 1.7.1
10548         labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
10549             valueFormat=_;
10550             nv.deprecated('labelFormat','use valueFormat instead');
10551         }},
10552
10553         // options that require extra logic in the setter
10554         margin: {get: function(){return margin;}, set: function(_){
10555             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10556             margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10557             margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10558             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10559         }},
10560         y: {get: function(){return getY;}, set: function(_){
10561             getY=d3.functor(_);
10562         }},
10563         color: {get: function(){return color;}, set: function(_){
10564             color=nv.utils.getColor(_);
10565         }},
10566         labelType:          {get: function(){return labelType;}, set: function(_){
10567             labelType= _ || 'key';
10568         }}
10569     });
10570
10571     nv.utils.initOptions(chart);
10572     return chart;
10573 };
10574 nv.models.pieChart = function() {
10575     "use strict";
10576
10577     //============================================================
10578     // Public Variables with Default Settings
10579     //------------------------------------------------------------
10580
10581     var pie = nv.models.pie();
10582     var legend = nv.models.legend();
10583     var tooltip = nv.models.tooltip();
10584
10585     var margin = {top: 30, right: 20, bottom: 20, left: 20}
10586         , width = null
10587         , height = null
10588         , showLegend = true
10589         , legendPosition = "top"
10590         , color = nv.utils.defaultColor()
10591         , state = nv.utils.state()
10592         , defaultState = null
10593         , noData = null
10594         , duration = 250
10595         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
10596         ;
10597
10598     tooltip
10599         .headerEnabled(false)
10600         .duration(0)
10601         .valueFormatter(function(d, i) {
10602             return pie.valueFormat()(d, i);
10603         });
10604
10605     //============================================================
10606     // Private Variables
10607     //------------------------------------------------------------
10608
10609     var renderWatch = nv.utils.renderWatch(dispatch);
10610
10611     var stateGetter = function(data) {
10612         return function(){
10613             return {
10614                 active: data.map(function(d) { return !d.disabled })
10615             };
10616         }
10617     };
10618
10619     var stateSetter = function(data) {
10620         return function(state) {
10621             if (state.active !== undefined) {
10622                 data.forEach(function (series, i) {
10623                     series.disabled = !state.active[i];
10624                 });
10625             }
10626         }
10627     };
10628
10629     //============================================================
10630     // Chart function
10631     //------------------------------------------------------------
10632
10633     function chart(selection) {
10634         renderWatch.reset();
10635         renderWatch.models(pie);
10636
10637         selection.each(function(data) {
10638             var container = d3.select(this);
10639             nv.utils.initSVG(container);
10640
10641             var that = this;
10642             var availableWidth = nv.utils.availableWidth(width, container, margin),
10643                 availableHeight = nv.utils.availableHeight(height, container, margin);
10644
10645             chart.update = function() { container.transition().call(chart); };
10646             chart.container = this;
10647
10648             state.setter(stateSetter(data), chart.update)
10649                 .getter(stateGetter(data))
10650                 .update();
10651
10652             //set state.disabled
10653             state.disabled = data.map(function(d) { return !!d.disabled });
10654
10655             if (!defaultState) {
10656                 var key;
10657                 defaultState = {};
10658                 for (key in state) {
10659                     if (state[key] instanceof Array)
10660                         defaultState[key] = state[key].slice(0);
10661                     else
10662                         defaultState[key] = state[key];
10663                 }
10664             }
10665
10666             // Display No Data message if there's nothing to show.
10667             if (!data || !data.length) {
10668                 nv.utils.noData(chart, container);
10669                 return chart;
10670             } else {
10671                 container.selectAll('.nv-noData').remove();
10672             }
10673
10674             // Setup containers and skeleton of chart
10675             var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10676             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10677             var g = wrap.select('g');
10678
10679             gEnter.append('g').attr('class', 'nv-pieWrap');
10680             gEnter.append('g').attr('class', 'nv-legendWrap');
10681
10682             // Legend
10683             if (showLegend) {
10684                 if (legendPosition === "top") {
10685                     legend.width( availableWidth ).key(pie.x());
10686
10687                     wrap.select('.nv-legendWrap')
10688                         .datum(data)
10689                         .call(legend);
10690
10691                     if ( margin.top != legend.height()) {
10692                         margin.top = legend.height();
10693                         availableHeight = nv.utils.availableHeight(height, container, margin);
10694                     }
10695
10696                     wrap.select('.nv-legendWrap')
10697                         .attr('transform', 'translate(0,' + (-margin.top) +')');
10698                 } else if (legendPosition === "right") {
10699                     var legendWidth = nv.models.legend().width();
10700                     if (availableWidth / 2 < legendWidth) {
10701                         legendWidth = (availableWidth / 2)
10702                     }
10703                     legend.height(availableHeight).key(pie.x());
10704                     legend.width(legendWidth);
10705                     availableWidth -= legend.width();
10706
10707                     wrap.select('.nv-legendWrap')
10708                         .datum(data)
10709                         .call(legend)
10710                         .attr('transform', 'translate(' + (availableWidth) +',0)');
10711                 }
10712             }
10713             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10714
10715             // Main Chart Component(s)
10716             pie.width(availableWidth).height(availableHeight);
10717             var pieWrap = g.select('.nv-pieWrap').datum([data]);
10718             d3.transition(pieWrap).call(pie);
10719
10720             //============================================================
10721             // Event Handling/Dispatching (in chart's scope)
10722             //------------------------------------------------------------
10723
10724             legend.dispatch.on('stateChange', function(newState) {
10725                 for (var key in newState) {
10726                     state[key] = newState[key];
10727                 }
10728                 dispatch.stateChange(state);
10729                 chart.update();
10730             });
10731
10732             // Update chart from a state object passed to event handler
10733             dispatch.on('changeState', function(e) {
10734                 if (typeof e.disabled !== 'undefined') {
10735                     data.forEach(function(series,i) {
10736                         series.disabled = e.disabled[i];
10737                     });
10738                     state.disabled = e.disabled;
10739                 }
10740                 chart.update();
10741             });
10742         });
10743
10744         renderWatch.renderEnd('pieChart immediate');
10745         return chart;
10746     }
10747
10748     //============================================================
10749     // Event Handling/Dispatching (out of chart's scope)
10750     //------------------------------------------------------------
10751
10752     pie.dispatch.on('elementMouseover.tooltip', function(evt) {
10753         evt['series'] = {
10754             key: chart.x()(evt.data),
10755             value: chart.y()(evt.data),
10756             color: evt.color
10757         };
10758         tooltip.data(evt).hidden(false);
10759     });
10760
10761     pie.dispatch.on('elementMouseout.tooltip', function(evt) {
10762         tooltip.hidden(true);
10763     });
10764
10765     pie.dispatch.on('elementMousemove.tooltip', function(evt) {
10766         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
10767     });
10768
10769     //============================================================
10770     // Expose Public Variables
10771     //------------------------------------------------------------
10772
10773     // expose chart's sub-components
10774     chart.legend = legend;
10775     chart.dispatch = dispatch;
10776     chart.pie = pie;
10777     chart.tooltip = tooltip;
10778     chart.options = nv.utils.optionsFunc.bind(chart);
10779
10780     // use Object get/set functionality to map between vars and chart functions
10781     chart._options = Object.create({}, {
10782         // simple options, just get/set the necessary values
10783         noData:         {get: function(){return noData;},         set: function(_){noData=_;}},
10784         showLegend:     {get: function(){return showLegend;},     set: function(_){showLegend=_;}},
10785         legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
10786         defaultState:   {get: function(){return defaultState;},   set: function(_){defaultState=_;}},
10787
10788         // deprecated options
10789         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
10790             // deprecated after 1.7.1
10791             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
10792             tooltip.enabled(!!_);
10793         }},
10794         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
10795             // deprecated after 1.7.1
10796             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
10797             tooltip.contentGenerator(_);
10798         }},
10799
10800         // options that require extra logic in the setter
10801         color: {get: function(){return color;}, set: function(_){
10802             color = _;
10803             legend.color(color);
10804             pie.color(color);
10805         }},
10806         duration: {get: function(){return duration;}, set: function(_){
10807             duration = _;
10808             renderWatch.reset(duration);
10809         }},
10810         margin: {get: function(){return margin;}, set: function(_){
10811             margin.top    = _.top    !== undefined ? _.top    : margin.top;
10812             margin.right  = _.right  !== undefined ? _.right  : margin.right;
10813             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10814             margin.left   = _.left   !== undefined ? _.left   : margin.left;
10815         }}
10816     });
10817     nv.utils.inheritOptions(chart, pie);
10818     nv.utils.initOptions(chart);
10819     return chart;
10820 };
10821
10822 nv.models.scatter = function() {
10823     "use strict";
10824
10825     //============================================================
10826     // Public Variables with Default Settings
10827     //------------------------------------------------------------
10828
10829     var margin       = {top: 0, right: 0, bottom: 0, left: 0}
10830         , width        = null
10831         , height       = null
10832         , color        = nv.utils.defaultColor() // chooses color
10833         , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10834         , container    = null
10835         , x            = d3.scale.linear()
10836         , y            = d3.scale.linear()
10837         , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10838         , getX         = function(d) { return d.x } // accessor to get the x value
10839         , getY         = function(d) { return d.y } // accessor to get the y value
10840         , getSize      = function(d) { return d.size || 1} // accessor to get the point size
10841         , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
10842         , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10843         , forceY       = [] // List of numbers to Force into the Y scale
10844         , forceSize    = [] // List of numbers to Force into the Size scale
10845         , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
10846         , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
10847         , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10848         , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10849         , clipEdge     = false // if true, masks points within x and y scale
10850         , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
10851         , showVoronoi  = false // display the voronoi areas
10852         , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
10853         , xDomain      = null // Override x domain (skips the calculation from data)
10854         , yDomain      = null // Override y domain
10855         , xRange       = null // Override x range
10856         , yRange       = null // Override y range
10857         , sizeDomain   = null // Override point size domain
10858         , sizeRange    = null
10859         , singlePoint  = false
10860         , dispatch     = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
10861         , useVoronoi   = true
10862         , duration     = 250
10863         ;
10864
10865
10866     //============================================================
10867     // Private Variables
10868     //------------------------------------------------------------
10869
10870     var x0, y0, z0 // used to store previous scales
10871         , timeoutID
10872         , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
10873         , renderWatch = nv.utils.renderWatch(dispatch, duration)
10874         , _sizeRange_def = [16, 256]
10875         ;
10876
10877     function chart(selection) {
10878         renderWatch.reset();
10879         selection.each(function(data) {
10880             container = d3.select(this);
10881             var availableWidth = nv.utils.availableWidth(width, container, margin),
10882                 availableHeight = nv.utils.availableHeight(height, container, margin);
10883
10884             nv.utils.initSVG(container);
10885
10886             //add series index to each data point for reference
10887             data.forEach(function(series, i) {
10888                 series.values.forEach(function(point) {
10889                     point.series = i;
10890                 });
10891             });
10892
10893             // Setup Scales
10894             // remap and flatten the data for use in calculating the scales' domains
10895             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
10896                 d3.merge(
10897                     data.map(function(d) {
10898                         return d.values.map(function(d,i) {
10899                             return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
10900                         })
10901                     })
10902                 );
10903
10904             x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10905
10906             if (padData && data[0])
10907                 x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
10908             //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
10909             else
10910                 x.range(xRange || [0, availableWidth]);
10911
10912             y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10913                 .range(yRange || [availableHeight, 0]);
10914
10915             z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10916                 .range(sizeRange || _sizeRange_def);
10917
10918             // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10919             singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
10920
10921             if (x.domain()[0] === x.domain()[1])
10922                 x.domain()[0] ?
10923                     x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10924                     : x.domain([-1,1]);
10925
10926             if (y.domain()[0] === y.domain()[1])
10927                 y.domain()[0] ?
10928                     y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
10929                     : y.domain([-1,1]);
10930
10931             if ( isNaN(x.domain()[0])) {
10932                 x.domain([-1,1]);
10933             }
10934
10935             if ( isNaN(y.domain()[0])) {
10936                 y.domain([-1,1]);
10937             }
10938
10939             x0 = x0 || x;
10940             y0 = y0 || y;
10941             z0 = z0 || z;
10942
10943             // Setup containers and skeleton of chart
10944             var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10945             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
10946             var defsEnter = wrapEnter.append('defs');
10947             var gEnter = wrapEnter.append('g');
10948             var g = wrap.select('g');
10949
10950             wrap.classed('nv-single-point', singlePoint);
10951             gEnter.append('g').attr('class', 'nv-groups');
10952             gEnter.append('g').attr('class', 'nv-point-paths');
10953             wrapEnter.append('g').attr('class', 'nv-point-clips');
10954
10955             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10956
10957             defsEnter.append('clipPath')
10958                 .attr('id', 'nv-edge-clip-' + id)
10959                 .append('rect');
10960
10961             wrap.select('#nv-edge-clip-' + id + ' rect')
10962                 .attr('width', availableWidth)
10963                 .attr('height', (availableHeight > 0) ? availableHeight : 0);
10964
10965             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10966
10967             function updateInteractiveLayer() {
10968                 // Always clear needs-update flag regardless of whether or not
10969                 // we will actually do anything (avoids needless invocations).
10970                 needsUpdate = false;
10971
10972                 if (!interactive) return false;
10973
10974                 // inject series and point index for reference into voronoi
10975                 if (useVoronoi === true) {
10976                     var vertices = d3.merge(data.map(function(group, groupIndex) {
10977                             return group.values
10978                                 .map(function(point, pointIndex) {
10979                                     // *Adding noise to make duplicates very unlikely
10980                                     // *Injecting series and point index for reference
10981                                     /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10982                                      */
10983                                     var pX = getX(point,pointIndex);
10984                                     var pY = getY(point,pointIndex);
10985
10986                                     return [x(pX)+ Math.random() * 1e-4,
10987                                             y(pY)+ Math.random() * 1e-4,
10988                                         groupIndex,
10989                                         pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
10990                                 })
10991                                 .filter(function(pointArray, pointIndex) {
10992                                     return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10993                                 })
10994                         })
10995                     );
10996
10997                     if (vertices.length == 0) return false;  // No active points, we're done
10998                     if (vertices.length < 3) {
10999                         // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
11000                         vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
11001                         vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11002                         vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11003                         vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11004                     }
11005
11006                     // keep voronoi sections from going more than 10 outside of graph
11007                     // to avoid overlap with other things like legend etc
11008                     var bounds = d3.geom.polygon([
11009                         [-10,-10],
11010                         [-10,height + 10],
11011                         [width + 10,height + 10],
11012                         [width + 10,-10]
11013                     ]);
11014
11015                     var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11016                         return {
11017                             'data': bounds.clip(d),
11018                             'series': vertices[i][2],
11019                             'point': vertices[i][3]
11020                         }
11021                     });
11022
11023                     // nuke all voronoi paths on reload and recreate them
11024                     wrap.select('.nv-point-paths').selectAll('path').remove();
11025                     var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
11026                     var vPointPaths = pointPaths
11027                         .enter().append("svg:path")
11028                         .attr("d", function(d) {
11029                             if (!d || !d.data || d.data.length === 0)
11030                                 return 'M 0 0';
11031                             else
11032                                 return "M" + d.data.join(",") + "Z";
11033                         })
11034                         .attr("id", function(d,i) {
11035                             return "nv-path-"+i; })
11036                         .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
11037                         ;
11038
11039                     // good for debugging point hover issues
11040                     if (showVoronoi) {
11041                         vPointPaths.style("fill", d3.rgb(230, 230, 230))
11042                             .style('fill-opacity', 0.4)
11043                             .style('stroke-opacity', 1)
11044                             .style("stroke", d3.rgb(200,200,200));
11045                     }
11046
11047                     if (clipVoronoi) {
11048                         // voronoi sections are already set to clip,
11049                         // just create the circles with the IDs they expect
11050                         wrap.select('.nv-point-clips').selectAll('clipPath').remove();
11051                         wrap.select('.nv-point-clips').selectAll("clipPath")
11052                             .data(vertices)
11053                             .enter().append("svg:clipPath")
11054                             .attr("id", function(d, i) { return "nv-clip-"+i;})
11055                             .append("svg:circle")
11056                             .attr('cx', function(d) { return d[0]; })
11057                             .attr('cy', function(d) { return d[1]; })
11058                             .attr('r', clipRadius);
11059                     }
11060
11061                     var mouseEventCallback = function(d, mDispatch) {
11062                         if (needsUpdate) return 0;
11063                         var series = data[d.series];
11064                         if (series === undefined) return;
11065                         var point  = series.values[d.point];
11066                         point['color'] = color(series, d.series);
11067
11068                         // standardize attributes for tooltip.
11069                         point['x'] = getX(point);
11070                         point['y'] = getY(point);
11071
11072                         // can't just get box of event node since it's actually a voronoi polygon
11073                         var box = container.node().getBoundingClientRect();
11074                         var scrollTop  = window.pageYOffset || document.documentElement.scrollTop;
11075                         var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
11076
11077                         var pos = {
11078                             left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
11079                             top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
11080                         };
11081
11082                         mDispatch({
11083                             point: point,
11084                             series: series,
11085                             pos: pos,
11086                             seriesIndex: d.series,
11087                             pointIndex: d.point
11088                         });
11089                     };
11090
11091                     pointPaths
11092                         .on('click', function(d) {
11093                             mouseEventCallback(d, dispatch.elementClick);
11094                         })
11095                         .on('dblclick', function(d) {
11096                             mouseEventCallback(d, dispatch.elementDblClick);
11097                         })
11098                         .on('mouseover', function(d) {
11099                             mouseEventCallback(d, dispatch.elementMouseover);
11100                         })
11101                         .on('mouseout', function(d, i) {
11102                             mouseEventCallback(d, dispatch.elementMouseout);
11103                         });
11104
11105                 } else {
11106                     // add event handlers to points instead voronoi paths
11107                     wrap.select('.nv-groups').selectAll('.nv-group')
11108                         .selectAll('.nv-point')
11109                         //.data(dataWithPoints)
11110                         //.style('pointer-events', 'auto') // recativate events, disabled by css
11111                         .on('click', function(d,i) {
11112                             //nv.log('test', d, i);
11113                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11114                             var series = data[d.series],
11115                                 point  = series.values[i];
11116
11117                             dispatch.elementClick({
11118                                 point: point,
11119                                 series: series,
11120                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11121                                 seriesIndex: d.series,
11122                                 pointIndex: i
11123                             });
11124                         })
11125                         .on('dblclick', function(d,i) {
11126                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11127                             var series = data[d.series],
11128                                 point  = series.values[i];
11129
11130                             dispatch.elementDblClick({
11131                                 point: point,
11132                                 series: series,
11133                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11134                                 seriesIndex: d.series,
11135                                 pointIndex: i
11136                             });
11137                         })
11138                         .on('mouseover', function(d,i) {
11139                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11140                             var series = data[d.series],
11141                                 point  = series.values[i];
11142
11143                             dispatch.elementMouseover({
11144                                 point: point,
11145                                 series: series,
11146                                 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11147                                 seriesIndex: d.series,
11148                                 pointIndex: i,
11149                                 color: color(d, i)
11150                             });
11151                         })
11152                         .on('mouseout', function(d,i) {
11153                             if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11154                             var series = data[d.series],
11155                                 point  = series.values[i];
11156
11157                             dispatch.elementMouseout({
11158                                 point: point,
11159                                 series: series,
11160                                 seriesIndex: d.series,
11161                                 pointIndex: i,
11162                                 color: color(d, i)
11163                             });
11164                         });
11165                 }
11166             }
11167
11168             needsUpdate = true;
11169             var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11170                 .data(function(d) { return d }, function(d) { return d.key });
11171             groups.enter().append('g')
11172                 .style('stroke-opacity', 1e-6)
11173                 .style('fill-opacity', 1e-6);
11174             groups.exit()
11175                 .remove();
11176             groups
11177                 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11178                 .classed('hover', function(d) { return d.hover });
11179             groups.watchTransition(renderWatch, 'scatter: groups')
11180                 .style('fill', function(d,i) { return color(d, i) })
11181                 .style('stroke', function(d,i) { return color(d, i) })
11182                 .style('stroke-opacity', 1)
11183                 .style('fill-opacity', .5);
11184
11185             // create the points, maintaining their IDs from the original data set
11186             var points = groups.selectAll('path.nv-point')
11187                 .data(function(d) {
11188                     return d.values.map(
11189                         function (point, pointIndex) {
11190                             return [point, pointIndex]
11191                         }).filter(
11192                             function(pointArray, pointIndex) {
11193                                 return pointActive(pointArray[0], pointIndex)
11194                             })
11195                     });
11196             points.enter().append('path')
11197                 .style('fill', function (d) { return d.color })
11198                 .style('stroke', function (d) { return d.color })
11199                 .attr('transform', function(d) {
11200                     return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')'
11201                 })
11202                 .attr('d',
11203                     nv.utils.symbol()
11204                     .type(function(d) { return getShape(d[0]); })
11205                     .size(function(d) { return z(getSize(d[0],d[1])) })
11206             );
11207             points.exit().remove();
11208             groups.exit().selectAll('path.nv-point')
11209                 .watchTransition(renderWatch, 'scatter exit')
11210                 .attr('transform', function(d) {
11211                     return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
11212                 })
11213                 .remove();
11214             points.each(function(d) {
11215                 d3.select(this)
11216                     .classed('nv-point', true)
11217                     .classed('nv-point-' + d[1], true)
11218                     .classed('nv-noninteractive', !interactive)
11219                     .classed('hover',false)
11220                 ;
11221             });
11222             points
11223                 .watchTransition(renderWatch, 'scatter points')
11224                 .attr('transform', function(d) {
11225                     //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
11226                     return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
11227                 })
11228                 .attr('d',
11229                     nv.utils.symbol()
11230                     .type(function(d) { return getShape(d[0]); })
11231                     .size(function(d) { return z(getSize(d[0],d[1])) })
11232             );
11233
11234             // Delay updating the invisible interactive layer for smoother animation
11235             clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11236             timeoutID = setTimeout(updateInteractiveLayer, 300);
11237             //updateInteractiveLayer();
11238
11239             //store old scales for use in transitions on update
11240             x0 = x.copy();
11241             y0 = y.copy();
11242             z0 = z.copy();
11243
11244         });
11245         renderWatch.renderEnd('scatter immediate');
11246         return chart;
11247     }
11248
11249     //============================================================
11250     // Expose Public Variables
11251     //------------------------------------------------------------
11252
11253     chart.dispatch = dispatch;
11254     chart.options = nv.utils.optionsFunc.bind(chart);
11255
11256     // utility function calls provided by this chart
11257     chart._calls = new function() {
11258         this.clearHighlights = function () {
11259             nv.dom.write(function() {
11260                 container.selectAll(".nv-point.hover").classed("hover", false);
11261             });
11262             return null;
11263         };
11264         this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
11265             nv.dom.write(function() {
11266                 container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11267                     .classed("hover", isHoverOver);
11268             });
11269         };
11270     };
11271
11272     // trigger calls from events too
11273     dispatch.on('elementMouseover.point', function(d) {
11274         if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
11275     });
11276
11277     dispatch.on('elementMouseout.point', function(d) {
11278         if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
11279     });
11280
11281     chart._options = Object.create({}, {
11282         // simple options, just get/set the necessary values
11283         width:        {get: function(){return width;}, set: function(_){width=_;}},
11284         height:       {get: function(){return height;}, set: function(_){height=_;}},
11285         xScale:       {get: function(){return x;}, set: function(_){x=_;}},
11286         yScale:       {get: function(){return y;}, set: function(_){y=_;}},
11287         pointScale:   {get: function(){return z;}, set: function(_){z=_;}},
11288         xDomain:      {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
11289         yDomain:      {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
11290         pointDomain:  {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
11291         xRange:       {get: function(){return xRange;}, set: function(_){xRange=_;}},
11292         yRange:       {get: function(){return yRange;}, set: function(_){yRange=_;}},
11293         pointRange:   {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
11294         forceX:       {get: function(){return forceX;}, set: function(_){forceX=_;}},
11295         forceY:       {get: function(){return forceY;}, set: function(_){forceY=_;}},
11296         forcePoint:   {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
11297         interactive:  {get: function(){return interactive;}, set: function(_){interactive=_;}},
11298         pointActive:  {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
11299         padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
11300         padData:      {get: function(){return padData;}, set: function(_){padData=_;}},
11301         clipEdge:     {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
11302         clipVoronoi:  {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
11303         clipRadius:   {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
11304         showVoronoi:   {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
11305         id:           {get: function(){return id;}, set: function(_){id=_;}},
11306
11307
11308         // simple functor options
11309         x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
11310         y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
11311         pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
11312         pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
11313
11314         // options that require extra logic in the setter
11315         margin: {get: function(){return margin;}, set: function(_){
11316             margin.top    = _.top    !== undefined ? _.top    : margin.top;
11317             margin.right  = _.right  !== undefined ? _.right  : margin.right;
11318             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11319             margin.left   = _.left   !== undefined ? _.left   : margin.left;
11320         }},
11321         duration: {get: function(){return duration;}, set: function(_){
11322             duration = _;
11323             renderWatch.reset(duration);
11324         }},
11325         color: {get: function(){return color;}, set: function(_){
11326             color = nv.utils.getColor(_);
11327         }},
11328         useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
11329             useVoronoi = _;
11330             if (useVoronoi === false) {
11331                 clipVoronoi = false;
11332             }
11333         }}
11334     });
11335
11336     nv.utils.initOptions(chart);
11337     return chart;
11338 };
11339
11340 nv.models.scatterChart = function() {
11341     "use strict";
11342
11343     //============================================================
11344     // Public Variables with Default Settings
11345     //------------------------------------------------------------
11346
11347     var scatter      = nv.models.scatter()
11348         , xAxis        = nv.models.axis()
11349         , yAxis        = nv.models.axis()
11350         , legend       = nv.models.legend()
11351         , distX        = nv.models.distribution()
11352         , distY        = nv.models.distribution()
11353         , tooltip      = nv.models.tooltip()
11354         ;
11355
11356     var margin       = {top: 30, right: 20, bottom: 50, left: 75}
11357         , width        = null
11358         , height       = null
11359         , container    = null
11360         , color        = nv.utils.defaultColor()
11361         , x            = scatter.xScale()
11362         , y            = scatter.yScale()
11363         , showDistX    = false
11364         , showDistY    = false
11365         , showLegend   = true
11366         , showXAxis    = true
11367         , showYAxis    = true
11368         , rightAlignYAxis = false
11369         , state = nv.utils.state()
11370         , defaultState = null
11371         , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
11372         , noData       = null
11373         , duration = 250
11374         ;
11375
11376     scatter.xScale(x).yScale(y);
11377     xAxis.orient('bottom').tickPadding(10);
11378     yAxis
11379         .orient((rightAlignYAxis) ? 'right' : 'left')
11380         .tickPadding(10)
11381     ;
11382     distX.axis('x');
11383     distY.axis('y');
11384     tooltip
11385         .headerFormatter(function(d, i) {
11386             return xAxis.tickFormat()(d, i);
11387         })
11388         .valueFormatter(function(d, i) {
11389             return yAxis.tickFormat()(d, i);
11390         });
11391
11392     //============================================================
11393     // Private Variables
11394     //------------------------------------------------------------
11395
11396     var x0, y0
11397         , renderWatch = nv.utils.renderWatch(dispatch, duration);
11398
11399     var stateGetter = function(data) {
11400         return function(){
11401             return {
11402                 active: data.map(function(d) { return !d.disabled })
11403             };
11404         }
11405     };
11406
11407     var stateSetter = function(data) {
11408         return function(state) {
11409             if (state.active !== undefined)
11410                 data.forEach(function(series,i) {
11411                     series.disabled = !state.active[i];
11412                 });
11413         }
11414     };
11415
11416     function chart(selection) {
11417         renderWatch.reset();
11418         renderWatch.models(scatter);
11419         if (showXAxis) renderWatch.models(xAxis);
11420         if (showYAxis) renderWatch.models(yAxis);
11421         if (showDistX) renderWatch.models(distX);
11422         if (showDistY) renderWatch.models(distY);
11423
11424         selection.each(function(data) {
11425             var that = this;
11426
11427             container = d3.select(this);
11428             nv.utils.initSVG(container);
11429
11430             var availableWidth = nv.utils.availableWidth(width, container, margin),
11431                 availableHeight = nv.utils.availableHeight(height, container, margin);
11432
11433             chart.update = function() {
11434                 if (duration === 0)
11435                     container.call(chart);
11436                 else
11437                     container.transition().duration(duration).call(chart);
11438             };
11439             chart.container = this;
11440
11441             state
11442                 .setter(stateSetter(data), chart.update)
11443                 .getter(stateGetter(data))
11444                 .update();
11445
11446             // DEPRECATED set state.disableddisabled
11447             state.disabled = data.map(function(d) { return !!d.disabled });
11448
11449             if (!defaultState) {
11450                 var key;
11451                 defaultState = {};
11452                 for (key in state) {
11453                     if (state[key] instanceof Array)
11454                         defaultState[key] = state[key].slice(0);
11455                     else
11456                         defaultState[key] = state[key];
11457                 }
11458             }
11459
11460             // Display noData message if there's nothing to show.
11461             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11462                 nv.utils.noData(chart, container);
11463                 renderWatch.renderEnd('scatter immediate');
11464                 return chart;
11465             } else {
11466                 container.selectAll('.nv-noData').remove();
11467             }
11468
11469             // Setup Scales
11470             x = scatter.xScale();
11471             y = scatter.yScale();
11472
11473             // Setup containers and skeleton of chart
11474             var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11475             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11476             var gEnter = wrapEnter.append('g');
11477             var g = wrap.select('g');
11478
11479             // background for pointer events
11480             gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
11481
11482             gEnter.append('g').attr('class', 'nv-x nv-axis');
11483             gEnter.append('g').attr('class', 'nv-y nv-axis');
11484             gEnter.append('g').attr('class', 'nv-scatterWrap');
11485             gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
11486             gEnter.append('g').attr('class', 'nv-distWrap');
11487             gEnter.append('g').attr('class', 'nv-legendWrap');
11488
11489             if (rightAlignYAxis) {
11490                 g.select(".nv-y.nv-axis")
11491                     .attr("transform", "translate(" + availableWidth + ",0)");
11492             }
11493
11494             // Legend
11495             if (showLegend) {
11496                 var legendWidth = availableWidth;
11497                 legend.width(legendWidth);
11498
11499                 wrap.select('.nv-legendWrap')
11500                     .datum(data)
11501                     .call(legend);
11502
11503                 if ( margin.top != legend.height()) {
11504                     margin.top = legend.height();
11505                     availableHeight = nv.utils.availableHeight(height, container, margin);
11506                 }
11507
11508                 wrap.select('.nv-legendWrap')
11509                     .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
11510             }
11511
11512             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11513
11514             // Main Chart Component(s)
11515             scatter
11516                 .width(availableWidth)
11517                 .height(availableHeight)
11518                 .color(data.map(function(d,i) {
11519                     d.color = d.color || color(d, i);
11520                     return d.color;
11521                 }).filter(function(d,i) { return !data[i].disabled }));
11522
11523             wrap.select('.nv-scatterWrap')
11524                 .datum(data.filter(function(d) { return !d.disabled }))
11525                 .call(scatter);
11526
11527
11528             wrap.select('.nv-regressionLinesWrap')
11529                 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
11530
11531             var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
11532                 .data(function (d) {
11533                     return d;
11534                 });
11535
11536             regWrap.enter().append('g').attr('class', 'nv-regLines');
11537
11538             var regLine = regWrap.selectAll('.nv-regLine')
11539                 .data(function (d) {
11540                     return [d]
11541                 });
11542
11543             regLine.enter()
11544                 .append('line').attr('class', 'nv-regLine')
11545                 .style('stroke-opacity', 0);
11546
11547             // don't add lines unless we have slope and intercept to use
11548             regLine.filter(function(d) {
11549                 return d.intercept && d.slope;
11550             })
11551                 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
11552                 .attr('x1', x.range()[0])
11553                 .attr('x2', x.range()[1])
11554                 .attr('y1', function (d, i) {
11555                     return y(x.domain()[0] * d.slope + d.intercept)
11556                 })
11557                 .attr('y2', function (d, i) {
11558                     return y(x.domain()[1] * d.slope + d.intercept)
11559                 })
11560                 .style('stroke', function (d, i, j) {
11561                     return color(d, j)
11562                 })
11563                 .style('stroke-opacity', function (d, i) {
11564                     return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
11565                 });
11566
11567             // Setup Axes
11568             if (showXAxis) {
11569                 xAxis
11570                     .scale(x)
11571                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
11572                     .tickSize( -availableHeight , 0);
11573
11574                 g.select('.nv-x.nv-axis')
11575                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
11576                     .call(xAxis);
11577             }
11578
11579             if (showYAxis) {
11580                 yAxis
11581                     .scale(y)
11582                     ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
11583                     .tickSize( -availableWidth, 0);
11584
11585                 g.select('.nv-y.nv-axis')
11586                     .call(yAxis);
11587             }
11588
11589
11590             if (showDistX) {
11591                 distX
11592                     .getData(scatter.x())
11593                     .scale(x)
11594                     .width(availableWidth)
11595                     .color(data.map(function(d,i) {
11596                         return d.color || color(d, i);
11597                     }).filter(function(d,i) { return !data[i].disabled }));
11598                 gEnter.select('.nv-distWrap').append('g')
11599                     .attr('class', 'nv-distributionX');
11600                 g.select('.nv-distributionX')
11601                     .attr('transform', 'translate(0,' + y.range()[0] + ')')
11602                     .datum(data.filter(function(d) { return !d.disabled }))
11603                     .call(distX);
11604             }
11605
11606             if (showDistY) {
11607                 distY
11608                     .getData(scatter.y())
11609                     .scale(y)
11610                     .width(availableHeight)
11611                     .color(data.map(function(d,i) {
11612                         return d.color || color(d, i);
11613                     }).filter(function(d,i) { return !data[i].disabled }));
11614                 gEnter.select('.nv-distWrap').append('g')
11615                     .attr('class', 'nv-distributionY');
11616                 g.select('.nv-distributionY')
11617                     .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11618                     .datum(data.filter(function(d) { return !d.disabled }))
11619                     .call(distY);
11620             }
11621
11622             //============================================================
11623             // Event Handling/Dispatching (in chart's scope)
11624             //------------------------------------------------------------
11625
11626             legend.dispatch.on('stateChange', function(newState) {
11627                 for (var key in newState)
11628                     state[key] = newState[key];
11629                 dispatch.stateChange(state);
11630                 chart.update();
11631             });
11632
11633             // Update chart from a state object passed to event handler
11634             dispatch.on('changeState', function(e) {
11635                 if (typeof e.disabled !== 'undefined') {
11636                     data.forEach(function(series,i) {
11637                         series.disabled = e.disabled[i];
11638                     });
11639                     state.disabled = e.disabled;
11640                 }
11641                 chart.update();
11642             });
11643
11644             // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
11645             scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
11646                 tooltip.hidden(true);
11647                 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
11648                     .attr('y1', 0);
11649                 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
11650                     .attr('x2', distY.size());
11651             });
11652
11653             scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
11654                 container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
11655                     .attr('y1', evt.pos.top - availableHeight - margin.top);
11656                 container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
11657                     .attr('x2', evt.pos.left + distX.size() - margin.left);
11658                 tooltip.position(evt.pos).data(evt).hidden(false);
11659             });
11660
11661             //store old scales for use in transitions on update
11662             x0 = x.copy();
11663             y0 = y.copy();
11664
11665         });
11666
11667         renderWatch.renderEnd('scatter with line immediate');
11668         return chart;
11669     }
11670
11671     //============================================================
11672     // Expose Public Variables
11673     //------------------------------------------------------------
11674
11675     // expose chart's sub-components
11676     chart.dispatch = dispatch;
11677     chart.scatter = scatter;
11678     chart.legend = legend;
11679     chart.xAxis = xAxis;
11680     chart.yAxis = yAxis;
11681     chart.distX = distX;
11682     chart.distY = distY;
11683     chart.tooltip = tooltip;
11684
11685     chart.options = nv.utils.optionsFunc.bind(chart);
11686     chart._options = Object.create({}, {
11687         // simple options, just get/set the necessary values
11688         width:      {get: function(){return width;}, set: function(_){width=_;}},
11689         height:     {get: function(){return height;}, set: function(_){height=_;}},
11690         container:  {get: function(){return container;}, set: function(_){container=_;}},
11691         showDistX:  {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
11692         showDistY:  {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
11693         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11694         showXAxis:  {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
11695         showYAxis:  {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
11696         defaultState:     {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
11697         noData:     {get: function(){return noData;}, set: function(_){noData=_;}},
11698         duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
11699
11700         // deprecated options
11701         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
11702             // deprecated after 1.7.1
11703             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
11704             tooltip.enabled(!!_);
11705         }},
11706         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
11707             // deprecated after 1.7.1
11708             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
11709             tooltip.contentGenerator(_);
11710         }},
11711         tooltipXContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
11712             // deprecated after 1.7.1
11713             nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
11714         }},
11715         tooltipYContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
11716             // deprecated after 1.7.1
11717             nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
11718         }},
11719
11720         // options that require extra logic in the setter
11721         margin: {get: function(){return margin;}, set: function(_){
11722             margin.top    = _.top    !== undefined ? _.top    : margin.top;
11723             margin.right  = _.right  !== undefined ? _.right  : margin.right;
11724             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11725             margin.left   = _.left   !== undefined ? _.left   : margin.left;
11726         }},
11727         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
11728             rightAlignYAxis = _;
11729             yAxis.orient( (_) ? 'right' : 'left');
11730         }},
11731         color: {get: function(){return color;}, set: function(_){
11732             color = nv.utils.getColor(_);
11733             legend.color(color);
11734             distX.color(color);
11735             distY.color(color);
11736         }}
11737     });
11738
11739     nv.utils.inheritOptions(chart, scatter);
11740     nv.utils.initOptions(chart);
11741     return chart;
11742 };
11743
11744 nv.models.sparkline = function() {
11745     "use strict";
11746
11747     //============================================================
11748     // Public Variables with Default Settings
11749     //------------------------------------------------------------
11750
11751     var margin = {top: 2, right: 0, bottom: 2, left: 0}
11752         , width = 400
11753         , height = 32
11754         , container = null
11755         , animate = true
11756         , x = d3.scale.linear()
11757         , y = d3.scale.linear()
11758         , getX = function(d) { return d.x }
11759         , getY = function(d) { return d.y }
11760         , color = nv.utils.getColor(['#000'])
11761         , xDomain
11762         , yDomain
11763         , xRange
11764         , yRange
11765         ;
11766
11767     function chart(selection) {
11768         selection.each(function(data) {
11769             var availableWidth = width - margin.left - margin.right,
11770                 availableHeight = height - margin.top - margin.bottom;
11771
11772             container = d3.select(this);
11773             nv.utils.initSVG(container);
11774
11775             // Setup Scales
11776             x   .domain(xDomain || d3.extent(data, getX ))
11777                 .range(xRange || [0, availableWidth]);
11778
11779             y   .domain(yDomain || d3.extent(data, getY ))
11780                 .range(yRange || [availableHeight, 0]);
11781
11782             // Setup containers and skeleton of chart
11783             var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
11784             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
11785             var gEnter = wrapEnter.append('g');
11786             var g = wrap.select('g');
11787
11788             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
11789
11790             var paths = wrap.selectAll('path')
11791                 .data(function(d) { return [d] });
11792             paths.enter().append('path');
11793             paths.exit().remove();
11794             paths
11795                 .style('stroke', function(d,i) { return d.color || color(d, i) })
11796                 .attr('d', d3.svg.line()
11797                     .x(function(d,i) { return x(getX(d,i)) })
11798                     .y(function(d,i) { return y(getY(d,i)) })
11799             );
11800
11801             // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
11802             var points = wrap.selectAll('circle.nv-point')
11803                 .data(function(data) {
11804                     var yValues = data.map(function(d, i) { return getY(d,i); });
11805                     function pointIndex(index) {
11806                         if (index != -1) {
11807                             var result = data[index];
11808                             result.pointIndex = index;
11809                             return result;
11810                         } else {
11811                             return null;
11812                         }
11813                     }
11814                     var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
11815                         minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
11816                         currentPoint = pointIndex(yValues.length - 1);
11817                     return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
11818                 });
11819             points.enter().append('circle');
11820             points.exit().remove();
11821             points
11822                 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
11823                 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
11824                 .attr('r', 2)
11825                 .attr('class', function(d,i) {
11826                     return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
11827                             getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
11828                 });
11829         });
11830
11831         return chart;
11832     }
11833
11834     //============================================================
11835     // Expose Public Variables
11836     //------------------------------------------------------------
11837
11838     chart.options = nv.utils.optionsFunc.bind(chart);
11839
11840     chart._options = Object.create({}, {
11841         // simple options, just get/set the necessary values
11842         width:     {get: function(){return width;}, set: function(_){width=_;}},
11843         height:    {get: function(){return height;}, set: function(_){height=_;}},
11844         xDomain:   {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
11845         yDomain:   {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
11846         xRange:    {get: function(){return xRange;}, set: function(_){xRange=_;}},
11847         yRange:    {get: function(){return yRange;}, set: function(_){yRange=_;}},
11848         xScale:    {get: function(){return x;}, set: function(_){x=_;}},
11849         yScale:    {get: function(){return y;}, set: function(_){y=_;}},
11850         animate:   {get: function(){return animate;}, set: function(_){animate=_;}},
11851
11852         //functor options
11853         x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
11854         y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
11855
11856         // options that require extra logic in the setter
11857         margin: {get: function(){return margin;}, set: function(_){
11858             margin.top    = _.top    !== undefined ? _.top    : margin.top;
11859             margin.right  = _.right  !== undefined ? _.right  : margin.right;
11860             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11861             margin.left   = _.left   !== undefined ? _.left   : margin.left;
11862         }},
11863         color:  {get: function(){return color;}, set: function(_){
11864             color = nv.utils.getColor(_);
11865         }}
11866     });
11867
11868     nv.utils.initOptions(chart);
11869     return chart;
11870 };
11871
11872 nv.models.sparklinePlus = function() {
11873     "use strict";
11874
11875     //============================================================
11876     // Public Variables with Default Settings
11877     //------------------------------------------------------------
11878
11879     var sparkline = nv.models.sparkline();
11880
11881     var margin = {top: 15, right: 100, bottom: 10, left: 50}
11882         , width = null
11883         , height = null
11884         , x
11885         , y
11886         , index = []
11887         , paused = false
11888         , xTickFormat = d3.format(',r')
11889         , yTickFormat = d3.format(',.2f')
11890         , showLastValue = true
11891         , alignValue = true
11892         , rightAlignValue = false
11893         , noData = null
11894         ;
11895
11896     function chart(selection) {
11897         selection.each(function(data) {
11898             var container = d3.select(this);
11899             nv.utils.initSVG(container);
11900
11901             var availableWidth = nv.utils.availableWidth(width, container, margin),
11902                 availableHeight = nv.utils.availableHeight(height, container, margin);
11903
11904             chart.update = function() { container.call(chart); };
11905             chart.container = this;
11906
11907             // Display No Data message if there's nothing to show.
11908             if (!data || !data.length) {
11909                 nv.utils.noData(chart, container)
11910                 return chart;
11911             } else {
11912                 container.selectAll('.nv-noData').remove();
11913             }
11914
11915             var currentValue = sparkline.y()(data[data.length-1], data.length-1);
11916
11917             // Setup Scales
11918             x = sparkline.xScale();
11919             y = sparkline.yScale();
11920
11921             // Setup containers and skeleton of chart
11922             var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
11923             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
11924             var gEnter = wrapEnter.append('g');
11925             var g = wrap.select('g');
11926
11927             gEnter.append('g').attr('class', 'nv-sparklineWrap');
11928             gEnter.append('g').attr('class', 'nv-valueWrap');
11929             gEnter.append('g').attr('class', 'nv-hoverArea');
11930
11931             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11932
11933             // Main Chart Component(s)
11934             var sparklineWrap = g.select('.nv-sparklineWrap');
11935
11936             sparkline.width(availableWidth).height(availableHeight);
11937             sparklineWrap.call(sparkline);
11938
11939             if (showLastValue) {
11940                 var valueWrap = g.select('.nv-valueWrap');
11941                 var value = valueWrap.selectAll('.nv-currentValue')
11942                     .data([currentValue]);
11943
11944                 value.enter().append('text').attr('class', 'nv-currentValue')
11945                     .attr('dx', rightAlignValue ? -8 : 8)
11946                     .attr('dy', '.9em')
11947                     .style('text-anchor', rightAlignValue ? 'end' : 'start');
11948
11949                 value
11950                     .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
11951                     .attr('y', alignValue ? function (d) {
11952                         return y(d)
11953                     } : 0)
11954                     .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
11955                     .text(yTickFormat(currentValue));
11956             }
11957
11958             gEnter.select('.nv-hoverArea').append('rect')
11959                 .on('mousemove', sparklineHover)
11960                 .on('click', function() { paused = !paused })
11961                 .on('mouseout', function() { index = []; updateValueLine(); });
11962
11963             g.select('.nv-hoverArea rect')
11964                 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
11965                 .attr('width', availableWidth + margin.left + margin.right)
11966                 .attr('height', availableHeight + margin.top);
11967
11968             //index is currently global (within the chart), may or may not keep it that way
11969             function updateValueLine() {
11970                 if (paused) return;
11971
11972                 var hoverValue = g.selectAll('.nv-hoverValue').data(index);
11973
11974                 var hoverEnter = hoverValue.enter()
11975                     .append('g').attr('class', 'nv-hoverValue')
11976                     .style('stroke-opacity', 0)
11977                     .style('fill-opacity', 0);
11978
11979                 hoverValue.exit()
11980                     .transition().duration(250)
11981                     .style('stroke-opacity', 0)
11982                     .style('fill-opacity', 0)
11983                     .remove();
11984
11985                 hoverValue
11986                     .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
11987                     .transition().duration(250)
11988                     .style('stroke-opacity', 1)
11989                     .style('fill-opacity', 1);
11990
11991                 if (!index.length) return;
11992
11993                 hoverEnter.append('line')
11994                     .attr('x1', 0)
11995                     .attr('y1', -margin.top)
11996                     .attr('x2', 0)
11997                     .attr('y2', availableHeight);
11998
11999                 hoverEnter.append('text').attr('class', 'nv-xValue')
12000                     .attr('x', -6)
12001                     .attr('y', -margin.top)
12002                     .attr('text-anchor', 'end')
12003                     .attr('dy', '.9em');
12004
12005                 g.select('.nv-hoverValue .nv-xValue')
12006                     .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
12007
12008                 hoverEnter.append('text').attr('class', 'nv-yValue')
12009                     .attr('x', 6)
12010                     .attr('y', -margin.top)
12011                     .attr('text-anchor', 'start')
12012                     .attr('dy', '.9em');
12013
12014                 g.select('.nv-hoverValue .nv-yValue')
12015                     .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
12016             }
12017
12018             function sparklineHover() {
12019                 if (paused) return;
12020
12021                 var pos = d3.mouse(this)[0] - margin.left;
12022
12023                 function getClosestIndex(data, x) {
12024                     var distance = Math.abs(sparkline.x()(data[0], 0) - x);
12025                     var closestIndex = 0;
12026                     for (var i = 0; i < data.length; i++){
12027                         if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
12028                             distance = Math.abs(sparkline.x()(data[i], i) - x);
12029                             closestIndex = i;
12030                         }
12031                     }
12032                     return closestIndex;
12033                 }
12034
12035                 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12036                 updateValueLine();
12037             }
12038
12039         });
12040
12041         return chart;
12042     }
12043
12044     //============================================================
12045     // Expose Public Variables
12046     //------------------------------------------------------------
12047
12048     // expose chart's sub-components
12049     chart.sparkline = sparkline;
12050
12051     chart.options = nv.utils.optionsFunc.bind(chart);
12052
12053     chart._options = Object.create({}, {
12054         // simple options, just get/set the necessary values
12055         width:           {get: function(){return width;}, set: function(_){width=_;}},
12056         height:          {get: function(){return height;}, set: function(_){height=_;}},
12057         xTickFormat:     {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
12058         yTickFormat:     {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
12059         showLastValue:   {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
12060         alignValue:      {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
12061         rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
12062         noData:          {get: function(){return noData;}, set: function(_){noData=_;}},
12063
12064         // options that require extra logic in the setter
12065         margin: {get: function(){return margin;}, set: function(_){
12066             margin.top    = _.top    !== undefined ? _.top    : margin.top;
12067             margin.right  = _.right  !== undefined ? _.right  : margin.right;
12068             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12069             margin.left   = _.left   !== undefined ? _.left   : margin.left;
12070         }}
12071     });
12072
12073     nv.utils.inheritOptions(chart, sparkline);
12074     nv.utils.initOptions(chart);
12075
12076     return chart;
12077 };
12078
12079 nv.models.stackedArea = function() {
12080     "use strict";
12081
12082     //============================================================
12083     // Public Variables with Default Settings
12084     //------------------------------------------------------------
12085
12086     var margin = {top: 0, right: 0, bottom: 0, left: 0}
12087         , width = 960
12088         , height = 500
12089         , color = nv.utils.defaultColor() // a function that computes the color
12090         , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
12091         , container = null
12092         , getX = function(d) { return d.x } // accessor to get the x value from a data point
12093         , getY = function(d) { return d.y } // accessor to get the y value from a data point
12094         , style = 'stack'
12095         , offset = 'zero'
12096         , order = 'default'
12097         , interpolate = 'linear'  // controls the line interpolation
12098         , clipEdge = false // if true, masks lines within x and y scale
12099         , x //can be accessed via chart.xScale()
12100         , y //can be accessed via chart.yScale()
12101         , scatter = nv.models.scatter()
12102         , duration = 250
12103         , dispatch =  d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
12104         ;
12105
12106     scatter
12107         .pointSize(2.2) // default size
12108         .pointDomain([2.2, 2.2]) // all the same size by default
12109     ;
12110
12111     /************************************
12112      * offset:
12113      *   'wiggle' (stream)
12114      *   'zero' (stacked)
12115      *   'expand' (normalize to 100%)
12116      *   'silhouette' (simple centered)
12117      *
12118      * order:
12119      *   'inside-out' (stream)
12120      *   'default' (input order)
12121      ************************************/
12122
12123     var renderWatch = nv.utils.renderWatch(dispatch, duration);
12124
12125     function chart(selection) {
12126         renderWatch.reset();
12127         renderWatch.models(scatter);
12128         selection.each(function(data) {
12129             var availableWidth = width - margin.left - margin.right,
12130                 availableHeight = height - margin.top - margin.bottom;
12131
12132             container = d3.select(this);
12133             nv.utils.initSVG(container);
12134
12135             // Setup Scales
12136             x = scatter.xScale();
12137             y = scatter.yScale();
12138
12139             var dataRaw = data;
12140             // Injecting point index into each point because d3.layout.stack().out does not give index
12141             data.forEach(function(aseries, i) {
12142                 aseries.seriesIndex = i;
12143                 aseries.values = aseries.values.map(function(d, j) {
12144                     d.index = j;
12145                     d.seriesIndex = i;
12146                     return d;
12147                 });
12148             });
12149
12150             var dataFiltered = data.filter(function(series) {
12151                 return !series.disabled;
12152             });
12153
12154             data = d3.layout.stack()
12155                 .order(order)
12156                 .offset(offset)
12157                 .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
12158                 .x(getX)
12159                 .y(getY)
12160                 .out(function(d, y0, y) {
12161                     d.display = {
12162                         y: y,
12163                         y0: y0
12164                     };
12165                 })
12166             (dataFiltered);
12167
12168             // Setup containers and skeleton of chart
12169             var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
12170             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
12171             var defsEnter = wrapEnter.append('defs');
12172             var gEnter = wrapEnter.append('g');
12173             var g = wrap.select('g');
12174
12175             gEnter.append('g').attr('class', 'nv-areaWrap');
12176             gEnter.append('g').attr('class', 'nv-scatterWrap');
12177
12178             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12179             
12180             // If the user has not specified forceY, make sure 0 is included in the domain
12181             // Otherwise, use user-specified values for forceY
12182             if (scatter.forceY().length == 0) {
12183                 scatter.forceY().push(0);
12184             }
12185             
12186             scatter
12187                 .width(availableWidth)
12188                 .height(availableHeight)
12189                 .x(getX)
12190                 .y(function(d) { return d.display.y + d.display.y0 })
12191                 .forceY([0])
12192                 .color(data.map(function(d,i) {
12193                     return d.color || color(d, d.seriesIndex);
12194                 }));
12195
12196             var scatterWrap = g.select('.nv-scatterWrap')
12197                 .datum(data);
12198
12199             scatterWrap.call(scatter);
12200
12201             defsEnter.append('clipPath')
12202                 .attr('id', 'nv-edge-clip-' + id)
12203                 .append('rect');
12204
12205             wrap.select('#nv-edge-clip-' + id + ' rect')
12206                 .attr('width', availableWidth)
12207                 .attr('height', availableHeight);
12208
12209             g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
12210
12211             var area = d3.svg.area()
12212                 .x(function(d,i)  { return x(getX(d,i)) })
12213                 .y0(function(d) {
12214                     return y(d.display.y0)
12215                 })
12216                 .y1(function(d) {
12217                     return y(d.display.y + d.display.y0)
12218                 })
12219                 .interpolate(interpolate);
12220
12221             var zeroArea = d3.svg.area()
12222                 .x(function(d,i)  { return x(getX(d,i)) })
12223                 .y0(function(d) { return y(d.display.y0) })
12224                 .y1(function(d) { return y(d.display.y0) });
12225
12226             var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
12227                 .data(function(d) { return d });
12228
12229             path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
12230                 .attr('d', function(d,i){
12231                     return zeroArea(d.values, d.seriesIndex);
12232                 })
12233                 .on('mouseover', function(d,i) {
12234                     d3.select(this).classed('hover', true);
12235                     dispatch.areaMouseover({
12236                         point: d,
12237                         series: d.key,
12238                         pos: [d3.event.pageX, d3.event.pageY],
12239                         seriesIndex: d.seriesIndex
12240                     });
12241                 })
12242                 .on('mouseout', function(d,i) {
12243                     d3.select(this).classed('hover', false);
12244                     dispatch.areaMouseout({
12245                         point: d,
12246                         series: d.key,
12247                         pos: [d3.event.pageX, d3.event.pageY],
12248                         seriesIndex: d.seriesIndex
12249                     });
12250                 })
12251                 .on('click', function(d,i) {
12252                     d3.select(this).classed('hover', false);
12253                     dispatch.areaClick({
12254                         point: d,
12255                         series: d.key,
12256                         pos: [d3.event.pageX, d3.event.pageY],
12257                         seriesIndex: d.seriesIndex
12258                     });
12259                 });
12260
12261             path.exit().remove();
12262             path.style('fill', function(d,i){
12263                     return d.color || color(d, d.seriesIndex)
12264                 })
12265                 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
12266             path.watchTransition(renderWatch,'stackedArea path')
12267                 .attr('d', function(d,i) {
12268                     return area(d.values,i)
12269                 });
12270
12271             //============================================================
12272             // Event Handling/Dispatching (in chart's scope)
12273             //------------------------------------------------------------
12274
12275             scatter.dispatch.on('elementMouseover.area', function(e) {
12276                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
12277             });
12278             scatter.dispatch.on('elementMouseout.area', function(e) {
12279                 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
12280             });
12281
12282             //Special offset functions
12283             chart.d3_stackedOffset_stackPercent = function(stackData) {
12284                 var n = stackData.length,    //How many series
12285                     m = stackData[0].length,     //how many points per series
12286                     i,
12287                     j,
12288                     o,
12289                     y0 = [];
12290
12291                 for (j = 0; j < m; ++j) { //Looping through all points
12292                     for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
12293                         o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
12294                     }
12295
12296                     if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
12297                         stackData[i][j][1] /= o;
12298                     } else { //(total y value of all series at point in time i) == 0
12299                         for (i = 0; i < n; i++) {
12300                             stackData[i][j][1] = 0;
12301                         }
12302                     }
12303                 }
12304                 for (j = 0; j < m; ++j) y0[j] = 0;
12305                 return y0;
12306             };
12307
12308         });
12309
12310         renderWatch.renderEnd('stackedArea immediate');
12311         return chart;
12312     }
12313
12314     //============================================================
12315     // Global getters and setters
12316     //------------------------------------------------------------
12317
12318     chart.dispatch = dispatch;
12319     chart.scatter = scatter;
12320
12321     scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
12322     scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
12323     scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
12324
12325     chart.interpolate = function(_) {
12326         if (!arguments.length) return interpolate;
12327         interpolate = _;
12328         return chart;
12329     };
12330
12331     chart.duration = function(_) {
12332         if (!arguments.length) return duration;
12333         duration = _;
12334         renderWatch.reset(duration);
12335         scatter.duration(duration);
12336         return chart;
12337     };
12338
12339     chart.dispatch = dispatch;
12340     chart.scatter = scatter;
12341     chart.options = nv.utils.optionsFunc.bind(chart);
12342
12343     chart._options = Object.create({}, {
12344         // simple options, just get/set the necessary values
12345         width:      {get: function(){return width;}, set: function(_){width=_;}},
12346         height:     {get: function(){return height;}, set: function(_){height=_;}},
12347         clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
12348         offset:      {get: function(){return offset;}, set: function(_){offset=_;}},
12349         order:    {get: function(){return order;}, set: function(_){order=_;}},
12350         interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
12351
12352         // simple functor options
12353         x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
12354         y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
12355
12356         // options that require extra logic in the setter
12357         margin: {get: function(){return margin;}, set: function(_){
12358             margin.top    = _.top    !== undefined ? _.top    : margin.top;
12359             margin.right  = _.right  !== undefined ? _.right  : margin.right;
12360             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12361             margin.left   = _.left   !== undefined ? _.left   : margin.left;
12362         }},
12363         color:  {get: function(){return color;}, set: function(_){
12364             color = nv.utils.getColor(_);
12365         }},
12366         style: {get: function(){return style;}, set: function(_){
12367             style = _;
12368             switch (style) {
12369                 case 'stack':
12370                     chart.offset('zero');
12371                     chart.order('default');
12372                     break;
12373                 case 'stream':
12374                     chart.offset('wiggle');
12375                     chart.order('inside-out');
12376                     break;
12377                 case 'stream-center':
12378                     chart.offset('silhouette');
12379                     chart.order('inside-out');
12380                     break;
12381                 case 'expand':
12382                     chart.offset('expand');
12383                     chart.order('default');
12384                     break;
12385                 case 'stack_percent':
12386                     chart.offset(chart.d3_stackedOffset_stackPercent);
12387                     chart.order('default');
12388                     break;
12389             }
12390         }},
12391         duration: {get: function(){return duration;}, set: function(_){
12392             duration = _;
12393             renderWatch.reset(duration);
12394             scatter.duration(duration);
12395         }}
12396     });
12397
12398     nv.utils.inheritOptions(chart, scatter);
12399     nv.utils.initOptions(chart);
12400
12401     return chart;
12402 };
12403
12404 nv.models.stackedAreaChart = function() {
12405     "use strict";
12406
12407     //============================================================
12408     // Public Variables with Default Settings
12409     //------------------------------------------------------------
12410
12411     var stacked = nv.models.stackedArea()
12412         , xAxis = nv.models.axis()
12413         , yAxis = nv.models.axis()
12414         , legend = nv.models.legend()
12415         , controls = nv.models.legend()
12416         , interactiveLayer = nv.interactiveGuideline()
12417         , tooltip = nv.models.tooltip()
12418         ;
12419
12420     var margin = {top: 30, right: 25, bottom: 50, left: 60}
12421         , width = null
12422         , height = null
12423         , color = nv.utils.defaultColor()
12424         , showControls = true
12425         , showLegend = true
12426         , showXAxis = true
12427         , showYAxis = true
12428         , rightAlignYAxis = false
12429         , useInteractiveGuideline = false
12430         , x //can be accessed via chart.xScale()
12431         , y //can be accessed via chart.yScale()
12432         , state = nv.utils.state()
12433         , defaultState = null
12434         , noData = null
12435         , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
12436         , controlWidth = 250
12437         , controlOptions = ['Stacked','Stream','Expanded']
12438         , controlLabels = {}
12439         , duration = 250
12440         ;
12441
12442     state.style = stacked.style();
12443     xAxis.orient('bottom').tickPadding(7);
12444     yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
12445
12446     tooltip
12447         .headerFormatter(function(d, i) {
12448             return xAxis.tickFormat()(d, i);
12449         })
12450         .valueFormatter(function(d, i) {
12451             return yAxis.tickFormat()(d, i);
12452         });
12453
12454     interactiveLayer.tooltip
12455         .headerFormatter(function(d, i) {
12456             return xAxis.tickFormat()(d, i);
12457         })
12458         .valueFormatter(function(d, i) {
12459             return yAxis.tickFormat()(d, i);
12460         });
12461
12462     var oldYTickFormat = null,
12463         oldValueFormatter = null;
12464
12465     controls.updateState(false);
12466
12467     //============================================================
12468     // Private Variables
12469     //------------------------------------------------------------
12470
12471     var renderWatch = nv.utils.renderWatch(dispatch);
12472     var style = stacked.style();
12473
12474     var stateGetter = function(data) {
12475         return function(){
12476             return {
12477                 active: data.map(function(d) { return !d.disabled }),
12478                 style: stacked.style()
12479             };
12480         }
12481     };
12482
12483     var stateSetter = function(data) {
12484         return function(state) {
12485             if (state.style !== undefined)
12486                 style = state.style;
12487             if (state.active !== undefined)
12488                 data.forEach(function(series,i) {
12489                     series.disabled = !state.active[i];
12490                 });
12491         }
12492     };
12493
12494     var percentFormatter = d3.format('%');
12495
12496     function chart(selection) {
12497         renderWatch.reset();
12498         renderWatch.models(stacked);
12499         if (showXAxis) renderWatch.models(xAxis);
12500         if (showYAxis) renderWatch.models(yAxis);
12501
12502         selection.each(function(data) {
12503             var container = d3.select(this),
12504                 that = this;
12505             nv.utils.initSVG(container);
12506
12507             var availableWidth = nv.utils.availableWidth(width, container, margin),
12508                 availableHeight = nv.utils.availableHeight(height, container, margin);
12509
12510             chart.update = function() { container.transition().duration(duration).call(chart); };
12511             chart.container = this;
12512
12513             state
12514                 .setter(stateSetter(data), chart.update)
12515                 .getter(stateGetter(data))
12516                 .update();
12517
12518             // DEPRECATED set state.disabled
12519             state.disabled = data.map(function(d) { return !!d.disabled });
12520
12521             if (!defaultState) {
12522                 var key;
12523                 defaultState = {};
12524                 for (key in state) {
12525                     if (state[key] instanceof Array)
12526                         defaultState[key] = state[key].slice(0);
12527                     else
12528                         defaultState[key] = state[key];
12529                 }
12530             }
12531
12532             // Display No Data message if there's nothing to show.
12533             if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12534                 nv.utils.noData(chart, container)
12535                 return chart;
12536             } else {
12537                 container.selectAll('.nv-noData').remove();
12538             }
12539
12540             // Setup Scales
12541             x = stacked.xScale();
12542             y = stacked.yScale();
12543
12544             // Setup containers and skeleton of chart
12545             var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
12546             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
12547             var g = wrap.select('g');
12548
12549             gEnter.append("rect").style("opacity",0);
12550             gEnter.append('g').attr('class', 'nv-x nv-axis');
12551             gEnter.append('g').attr('class', 'nv-y nv-axis');
12552             gEnter.append('g').attr('class', 'nv-stackedWrap');
12553             gEnter.append('g').attr('class', 'nv-legendWrap');
12554             gEnter.append('g').attr('class', 'nv-controlsWrap');
12555             gEnter.append('g').attr('class', 'nv-interactive');
12556
12557             g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
12558
12559             // Legend
12560             if (showLegend) {
12561                 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
12562
12563                 legend.width(legendWidth);
12564                 g.select('.nv-legendWrap').datum(data).call(legend);
12565
12566                 if ( margin.top != legend.height()) {
12567                     margin.top = legend.height();
12568                     availableHeight = nv.utils.availableHeight(height, container, margin);
12569                 }
12570
12571                 g.select('.nv-legendWrap')
12572                     .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
12573             }
12574
12575             // Controls
12576             if (showControls) {
12577                 var controlsData = [
12578                     {
12579                         key: controlLabels.stacked || 'Stacked',
12580                         metaKey: 'Stacked',
12581                         disabled: stacked.style() != 'stack',
12582                         style: 'stack'
12583                     },
12584                     {
12585                         key: controlLabels.stream || 'Stream',
12586                         metaKey: 'Stream',
12587                         disabled: stacked.style() != 'stream',
12588                         style: 'stream'
12589                     },
12590                     {
12591                         key: controlLabels.expanded || 'Expanded',
12592                         metaKey: 'Expanded',
12593                         disabled: stacked.style() != 'expand',
12594                         style: 'expand'
12595                     },
12596                     {
12597                         key: controlLabels.stack_percent || 'Stack %',
12598                         metaKey: 'Stack_Percent',
12599                         disabled: stacked.style() != 'stack_percent',
12600                         style: 'stack_percent'
12601                     }
12602                 ];
12603
12604                 controlWidth = (controlOptions.length/3) * 260;
12605                 controlsData = controlsData.filter(function(d) {
12606                     return controlOptions.indexOf(d.metaKey) !== -1;
12607                 });
12608
12609                 controls
12610                     .width( controlWidth )
12611                     .color(['#444', '#444', '#444']);
12612
12613                 g.select('.nv-controlsWrap')
12614                     .datum(controlsData)
12615                     .call(controls);
12616
12617                 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
12618                     margin.top = Math.max(controls.height(), legend.height());
12619                     availableHeight = nv.utils.availableHeight(height, container, margin);
12620                 }
12621
12622                 g.select('.nv-controlsWrap')
12623                     .attr('transform', 'translate(0,' + (-margin.top) +')');
12624             }
12625
12626             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12627
12628             if (rightAlignYAxis) {
12629                 g.select(".nv-y.nv-axis")
12630                     .attr("transform", "translate(" + availableWidth + ",0)");
12631             }
12632
12633             //Set up interactive layer
12634             if (useInteractiveGuideline) {
12635                 interactiveLayer
12636                     .width(availableWidth)
12637                     .height(availableHeight)
12638                     .margin({left: margin.left, top: margin.top})
12639                     .svgContainer(container)
12640                     .xScale(x);
12641                 wrap.select(".nv-interactive").call(interactiveLayer);
12642             }
12643
12644             stacked
12645                 .width(availableWidth)
12646                 .height(availableHeight);
12647
12648             var stackedWrap = g.select('.nv-stackedWrap')
12649                 .datum(data);
12650
12651             stackedWrap.transition().call(stacked);
12652
12653             // Setup Axes
12654             if (showXAxis) {
12655                 xAxis.scale(x)
12656                     ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
12657                     .tickSize( -availableHeight, 0);
12658
12659                 g.select('.nv-x.nv-axis')
12660                     .attr('transform', 'translate(0,' + availableHeight + ')');
12661
12662                 g.select('.nv-x.nv-axis')
12663                     .transition().duration(0)
12664                     .call(xAxis);
12665             }
12666
12667             if (showYAxis) {
12668                 var ticks;
12669                 if (stacked.offset() === 'wiggle') {
12670                     ticks = 0;
12671                 }
12672                 else {
12673                     ticks = nv.utils.calcTicksY(availableHeight/36, data);
12674                 }
12675                 yAxis.scale(y)
12676                     ._ticks(ticks)
12677                     .tickSize(-availableWidth, 0);
12678
12679                     if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
12680                         var currentFormat = yAxis.tickFormat();
12681
12682                         if ( !oldYTickFormat || currentFormat !== percentFormatter )
12683                             oldYTickFormat = currentFormat;
12684
12685                         //Forces the yAxis to use percentage in 'expand' mode.
12686                         yAxis.tickFormat(percentFormatter);
12687                     }
12688                     else {
12689                         if (oldYTickFormat) {
12690                             yAxis.tickFormat(oldYTickFormat);
12691                             oldYTickFormat = null;
12692                         }
12693                     }
12694
12695                 g.select('.nv-y.nv-axis')
12696                     .transition().duration(0)
12697                     .call(yAxis);
12698             }
12699
12700             //============================================================
12701             // Event Handling/Dispatching (in chart's scope)
12702             //------------------------------------------------------------
12703
12704             stacked.dispatch.on('areaClick.toggle', function(e) {
12705                 if (data.filter(function(d) { return !d.disabled }).length === 1)
12706                     data.forEach(function(d) {
12707                         d.disabled = false;
12708                     });
12709                 else
12710                     data.forEach(function(d,i) {
12711                         d.disabled = (i != e.seriesIndex);
12712                     });
12713
12714                 state.disabled = data.map(function(d) { return !!d.disabled });
12715                 dispatch.stateChange(state);
12716
12717                 chart.update();
12718             });
12719
12720             legend.dispatch.on('stateChange', function(newState) {
12721                 for (var key in newState)
12722                     state[key] = newState[key];
12723                 dispatch.stateChange(state);
12724                 chart.update();
12725             });
12726
12727             controls.dispatch.on('legendClick', function(d,i) {
12728                 if (!d.disabled) return;
12729
12730                 controlsData = controlsData.map(function(s) {
12731                     s.disabled = true;
12732                     return s;
12733                 });
12734                 d.disabled = false;
12735
12736                 stacked.style(d.style);
12737
12738
12739                 state.style = stacked.style();
12740                 dispatch.stateChange(state);
12741
12742                 chart.update();
12743             });
12744
12745             interactiveLayer.dispatch.on('elementMousemove', function(e) {
12746                 stacked.clearHighlights();
12747                 var singlePoint, pointIndex, pointXLocation, allData = [];
12748                 data
12749                     .filter(function(series, i) {
12750                         series.seriesIndex = i;
12751                         return !series.disabled;
12752                     })
12753                     .forEach(function(series,i) {
12754                         pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
12755                         var point = series.values[pointIndex];
12756                         var pointYValue = chart.y()(point, pointIndex);
12757                         if (pointYValue != null) {
12758                             stacked.highlightPoint(i, pointIndex, true);
12759                         }
12760                         if (typeof point === 'undefined') return;
12761                         if (typeof singlePoint === 'undefined') singlePoint = point;
12762                         if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
12763
12764                         //If we are in 'expand' mode, use the stacked percent value instead of raw value.
12765                         var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
12766                         allData.push({
12767                             key: series.key,
12768                             value: tooltipValue,
12769                             color: color(series,series.seriesIndex),
12770                             stackedValue: point.display
12771                         });
12772                     });
12773
12774                 allData.reverse();
12775
12776                 //Highlight the tooltip entry based on which stack the mouse is closest to.
12777                 if (allData.length > 2) {
12778                     var yValue = chart.yScale().invert(e.mouseY);
12779                     var yDistMax = Infinity, indexToHighlight = null;
12780                     allData.forEach(function(series,i) {
12781
12782                         //To handle situation where the stacked area chart is negative, we need to use absolute values
12783                         //when checking if the mouse Y value is within the stack area.
12784                         yValue = Math.abs(yValue);
12785                         var stackedY0 = Math.abs(series.stackedValue.y0);
12786                         var stackedY = Math.abs(series.stackedValue.y);
12787                         if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
12788                         {
12789                             indexToHighlight = i;
12790                             return;
12791                         }
12792                     });
12793                     if (indexToHighlight != null)
12794                         allData[indexToHighlight].highlight = true;
12795                 }
12796
12797                 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
12798
12799                 var valueFormatter = interactiveLayer.tooltip.valueFormatter();
12800                 // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
12801                 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
12802                     if ( !oldValueFormatter ) {
12803                         oldValueFormatter = valueFormatter;
12804                     }
12805                     //Forces the tooltip to use percentage in 'expand' mode.
12806                     valueFormatter = d3.format(".1%");
12807                 }
12808                 else {
12809                     if (oldValueFormatter) {
12810                         valueFormatter = oldValueFormatter;
12811                         oldValueFormatter = null;
12812                     }
12813                 }
12814
12815                 interactiveLayer.tooltip
12816                     .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
12817                     .chartContainer(that.parentNode)
12818                     .valueFormatter(valueFormatter)
12819                     .data(
12820                     {
12821                         value: xValue,
12822                         series: allData
12823                     }
12824                 )();
12825
12826                 interactiveLayer.renderGuideLine(pointXLocation);
12827
12828             });
12829
12830             interactiveLayer.dispatch.on("elementMouseout",function(e) {
12831                 stacked.clearHighlights();
12832             });
12833
12834             // Update chart from a state object passed to event handler
12835             dispatch.on('changeState', function(e) {
12836
12837                 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
12838                     data.forEach(function(series,i) {
12839                         series.disabled = e.disabled[i];
12840                     });
12841
12842                     state.disabled = e.disabled;
12843                 }
12844
12845                 if (typeof e.style !== 'undefined') {
12846                     stacked.style(e.style);
12847                     style = e.style;
12848                 }
12849
12850                 chart.update();
12851             });
12852
12853         });
12854
12855         renderWatch.renderEnd('stacked Area chart immediate');
12856         return chart;
12857     }
12858
12859     //============================================================
12860     // Event Handling/Dispatching (out of chart's scope)
12861     //------------------------------------------------------------
12862
12863     stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
12864         evt.point['x'] = stacked.x()(evt.point);
12865         evt.point['y'] = stacked.y()(evt.point);
12866         tooltip.data(evt).position(evt.pos).hidden(false);
12867     });
12868
12869     stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
12870         tooltip.hidden(true)
12871     });
12872
12873     //============================================================
12874     // Expose Public Variables
12875     //------------------------------------------------------------
12876
12877     // expose chart's sub-components
12878     chart.dispatch = dispatch;
12879     chart.stacked = stacked;
12880     chart.legend = legend;
12881     chart.controls = controls;
12882     chart.xAxis = xAxis;
12883     chart.yAxis = yAxis;
12884     chart.interactiveLayer = interactiveLayer;
12885     chart.tooltip = tooltip;
12886
12887     chart.dispatch = dispatch;
12888     chart.options = nv.utils.optionsFunc.bind(chart);
12889
12890     chart._options = Object.create({}, {
12891         // simple options, just get/set the necessary values
12892         width:      {get: function(){return width;}, set: function(_){width=_;}},
12893         height:     {get: function(){return height;}, set: function(_){height=_;}},
12894         showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
12895         showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
12896         showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
12897         defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
12898         noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
12899         showControls:    {get: function(){return showControls;}, set: function(_){showControls=_;}},
12900         controlLabels:    {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
12901         controlOptions:    {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
12902
12903         // deprecated options
12904         tooltips:    {get: function(){return tooltip.enabled();}, set: function(_){
12905             // deprecated after 1.7.1
12906             nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
12907             tooltip.enabled(!!_);
12908         }},
12909         tooltipContent:    {get: function(){return tooltip.contentGenerator();}, set: function(_){
12910             // deprecated after 1.7.1
12911             nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
12912             tooltip.contentGenerator(_);
12913         }},
12914
12915         // options that require extra logic in the setter
12916         margin: {get: function(){return margin;}, set: function(_){
12917             margin.top    = _.top    !== undefined ? _.top    : margin.top;
12918             margin.right  = _.right  !== undefined ? _.right  : margin.right;
12919             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12920             margin.left   = _.left   !== undefined ? _.left   : margin.left;
12921         }},
12922         duration: {get: function(){return duration;}, set: function(_){
12923             duration = _;
12924             renderWatch.reset(duration);
12925             stacked.duration(duration);
12926             xAxis.duration(duration);
12927             yAxis.duration(duration);
12928         }},
12929         color:  {get: function(){return color;}, set: function(_){
12930             color = nv.utils.getColor(_);
12931             legend.color(color);
12932             stacked.color(color);
12933         }},
12934         rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
12935             rightAlignYAxis = _;
12936             yAxis.orient( rightAlignYAxis ? 'right' : 'left');
12937         }},
12938         useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
12939             useInteractiveGuideline = !!_;
12940             chart.interactive(!_);
12941             chart.useVoronoi(!_);
12942             stacked.scatter.interactive(!_);
12943         }}
12944     });
12945
12946     nv.utils.inheritOptions(chart, stacked);
12947     nv.utils.initOptions(chart);
12948
12949     return chart;
12950 };
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
12952 nv.models.sunburst = function() {
12953     "use strict";
12954
12955     //============================================================
12956     // Public Variables with Default Settings
12957     //------------------------------------------------------------
12958
12959     var margin = {top: 0, right: 0, bottom: 0, left: 0}
12960         , width = null
12961         , height = null
12962         , mode = "count"
12963         , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }}
12964         , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
12965         , container = null
12966         , color = nv.utils.defaultColor()
12967         , duration = 500
12968         , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd')
12969         ;
12970
12971     var x = d3.scale.linear().range([0, 2 * Math.PI]);
12972     var y = d3.scale.sqrt();
12973
12974     var partition = d3.layout.partition()
12975         .sort(null)
12976         .value(function(d) { return 1; });
12977
12978     var arc = d3.svg.arc()
12979         .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
12980         .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
12981         .innerRadius(function(d) { return Math.max(0, y(d.y)); })
12982         .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
12983
12984     // Keep track of the current and previous node being displayed as the root.
12985     var node, prevNode;
12986     // Keep track of the root node
12987     var rootNode;
12988
12989     //============================================================
12990     // chart function
12991     //------------------------------------------------------------
12992
12993     var renderWatch = nv.utils.renderWatch(dispatch);
12994
12995     function chart(selection) {
12996         renderWatch.reset();
12997         selection.each(function(data) {
12998             container = d3.select(this);
12999             var availableWidth = nv.utils.availableWidth(width, container, margin);
13000             var availableHeight = nv.utils.availableHeight(height, container, margin);
13001             var radius = Math.min(availableWidth, availableHeight) / 2;
13002             var path;
13003
13004             nv.utils.initSVG(container);
13005
13006             // Setup containers and skeleton of chart
13007             var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data);
13008             var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id);
13009
13010             var g = wrapEnter.selectAll('nv-sunburst');
13011
13012             wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
13013
13014             container.on('click', function (d, i) {
13015                 dispatch.chartClick({
13016                     data: d,
13017                     index: i,
13018                     pos: d3.event,
13019                     id: id
13020                 });
13021             });
13022
13023             y.range([0, radius]);
13024
13025             node = node || data;
13026             rootNode = data[0];
13027             partition.value(modes[mode] || modes["count"]);
13028             path = g.data(partition.nodes).enter()
13029                 .append("path")
13030                 .attr("d", arc)
13031                 .style("fill", function (d) {
13032                     return color((d.children ? d : d.parent).name);
13033                 })
13034                 .style("stroke", "#FFF")
13035                 .on("click", function(d) {
13036                     if (prevNode !== node && node !== d) prevNode = node;
13037                     node = d;
13038                     path.transition()
13039                         .duration(duration)
13040                         .attrTween("d", arcTweenZoom(d));
13041                 })
13042                 .each(stash)
13043                 .on("dblclick", function(d) {
13044                     if (prevNode.parent == d) {
13045                         path.transition()
13046                             .duration(duration)
13047                             .attrTween("d", arcTweenZoom(rootNode));
13048                     }
13049                 })
13050                 .each(stash)
13051                 .on('mouseover', function(d,i){
13052                     d3.select(this).classed('hover', true).style('opacity', 0.8);
13053                     dispatch.elementMouseover({
13054                         data: d,
13055                         color: d3.select(this).style("fill")
13056                     });
13057                 })
13058                 .on('mouseout', function(d,i){
13059                     d3.select(this).classed('hover', false).style('opacity', 1);
13060                     dispatch.elementMouseout({
13061                         data: d
13062                     });
13063                 })
13064                 .on('mousemove', function(d,i){
13065                     dispatch.elementMousemove({
13066                         data: d
13067                     });
13068                 });
13069
13070
13071
13072             // Setup for switching data: stash the old values for transition.
13073             function stash(d) {
13074                 d.x0 = d.x;
13075                 d.dx0 = d.dx;
13076             }
13077
13078             // When switching data: interpolate the arcs in data space.
13079             function arcTweenData(a, i) {
13080                 var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
13081
13082                 function tween(t) {
13083                     var b = oi(t);
13084                     a.x0 = b.x;
13085                     a.dx0 = b.dx;
13086                     return arc(b);
13087                 }
13088
13089                 if (i == 0) {
13090                     // If we are on the first arc, adjust the x domain to match the root node
13091                     // at the current zoom level. (We only need to do this once.)
13092                     var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
13093                     return function (t) {
13094                         x.domain(xd(t));
13095                         return tween(t);
13096                     };
13097                 } else {
13098                     return tween;
13099                 }
13100             }
13101
13102             // When zooming: interpolate the scales.
13103             function arcTweenZoom(d) {
13104                 var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
13105                     yd = d3.interpolate(y.domain(), [d.y, 1]),
13106                     yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
13107                 return function (d, i) {
13108                     return i
13109                         ? function (t) {
13110                         return arc(d);
13111                     }
13112                         : function (t) {
13113                         x.domain(xd(t));
13114                         y.domain(yd(t)).range(yr(t));
13115                         return arc(d);
13116                     };
13117                 };
13118             }
13119
13120         });
13121
13122         renderWatch.renderEnd('sunburst immediate');
13123         return chart;
13124     }
13125
13126     //============================================================
13127     // Expose Public Variables
13128     //------------------------------------------------------------
13129
13130     chart.dispatch = dispatch;
13131     chart.options = nv.utils.optionsFunc.bind(chart);
13132
13133     chart._options = Object.create({}, {
13134         // simple options, just get/set the necessary values
13135         width:      {get: function(){return width;}, set: function(_){width=_;}},
13136         height:     {get: function(){return height;}, set: function(_){height=_;}},
13137         mode:       {get: function(){return mode;}, set: function(_){mode=_;}},
13138         id:         {get: function(){return id;}, set: function(_){id=_;}},
13139         duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
13140
13141         // options that require extra logic in the setter
13142         margin: {get: function(){return margin;}, set: function(_){
13143             margin.top    = _.top    != undefined ? _.top    : margin.top;
13144             margin.right  = _.right  != undefined ? _.right  : margin.right;
13145             margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
13146             margin.left   = _.left   != undefined ? _.left   : margin.left;
13147         }},
13148         color: {get: function(){return color;}, set: function(_){
13149             color=nv.utils.getColor(_);
13150         }}
13151     });
13152
13153     nv.utils.initOptions(chart);
13154     return chart;
13155 };
13156 nv.models.sunburstChart = function() {
13157     "use strict";
13158
13159     //============================================================
13160     // Public Variables with Default Settings
13161     //------------------------------------------------------------
13162
13163     var sunburst = nv.models.sunburst();
13164     var tooltip = nv.models.tooltip();
13165
13166     var margin = {top: 30, right: 20, bottom: 20, left: 20}
13167         , width = null
13168         , height = null
13169         , color = nv.utils.defaultColor()
13170         , id = Math.round(Math.random() * 100000)
13171         , defaultState = null
13172         , noData = null
13173         , duration = 250
13174         , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
13175         ;
13176
13177     //============================================================
13178     // Private Variables
13179     //------------------------------------------------------------
13180
13181     var renderWatch = nv.utils.renderWatch(dispatch);
13182     tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) {
13183         return d;
13184     });
13185
13186     //============================================================
13187     // Chart function
13188     //------------------------------------------------------------
13189
13190     function chart(selection) {
13191         renderWatch.reset();
13192         renderWatch.models(sunburst);
13193
13194         selection.each(function(data) {
13195             var container = d3.select(this);
13196             nv.utils.initSVG(container);
13197
13198             var that = this;
13199             var availableWidth = nv.utils.availableWidth(width, container, margin),
13200                 availableHeight = nv.utils.availableHeight(height, container, margin);
13201
13202             chart.update = function() {
13203                 if (duration === 0)
13204                     container.call(chart);
13205                 else
13206                     container.transition().duration(duration).call(chart)
13207             };
13208             chart.container = this;
13209
13210             // Display No Data message if there's nothing to show.
13211             if (!data || !data.length) {
13212                 nv.utils.noData(chart, container);
13213                 return chart;
13214             } else {
13215                 container.selectAll('.nv-noData').remove();
13216             }
13217
13218             // Setup containers and skeleton of chart
13219             var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data);
13220             var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g');
13221             var g = wrap.select('g');
13222
13223             gEnter.append('g').attr('class', 'nv-sunburstWrap');
13224
13225             wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13226
13227             // Main Chart Component(s)
13228             sunburst.width(availableWidth).height(availableHeight);
13229             var sunWrap = g.select('.nv-sunburstWrap').datum(data);
13230             d3.transition(sunWrap).call(sunburst);
13231
13232         });
13233
13234         renderWatch.renderEnd('sunburstChart immediate');
13235         return chart;
13236     }
13237
13238     //============================================================
13239     // Event Handling/Dispatching (out of chart's scope)
13240     //------------------------------------------------------------
13241
13242     sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
13243         evt['series'] = {
13244             key: evt.data.name,
13245             value: evt.data.size,
13246             color: evt.color
13247         };
13248         tooltip.data(evt).hidden(false);
13249     });
13250
13251     sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
13252         tooltip.hidden(true);
13253     });
13254
13255     sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
13256         tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
13257     });
13258
13259     //============================================================
13260     // Expose Public Variables
13261     //------------------------------------------------------------
13262
13263     // expose chart's sub-components
13264     chart.dispatch = dispatch;
13265     chart.sunburst = sunburst;
13266     chart.tooltip = tooltip;
13267     chart.options = nv.utils.optionsFunc.bind(chart);
13268
13269     // use Object get/set functionality to map between vars and chart functions
13270     chart._options = Object.create({}, {
13271         // simple options, just get/set the necessary values
13272         noData:         {get: function(){return noData;},         set: function(_){noData=_;}},
13273         defaultState:   {get: function(){return defaultState;},   set: function(_){defaultState=_;}},
13274
13275         // options that require extra logic in the setter
13276         color: {get: function(){return color;}, set: function(_){
13277             color = _;
13278             sunburst.color(color);
13279         }},
13280         duration: {get: function(){return duration;}, set: function(_){
13281             duration = _;
13282             renderWatch.reset(duration);
13283             sunburst.duration(duration);
13284         }},
13285         margin: {get: function(){return margin;}, set: function(_){
13286             margin.top    = _.top    !== undefined ? _.top    : margin.top;
13287             margin.right  = _.right  !== undefined ? _.right  : margin.right;
13288             margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
13289             margin.left   = _.left   !== undefined ? _.left   : margin.left;
13290         }}
13291     });
13292     nv.utils.inheritOptions(chart, sunburst);
13293     nv.utils.initOptions(chart);
13294     return chart;
13295 };
13296
13297 nv.version = "1.8.1";
13298 })();