]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/static/js/nv.d3.js
Add the full version of nvd3.
[xonotic/xonstat.git] / xonstat / static / js / nv.d3.js
1 (function(){
2
3 var nv = window.nv || {};
4
5
6 nv.version = '1.1.10b';
7 nv.dev = true //set false when in production
8
9 window.nv = nv;
10
11 nv.tooltip = {}; // For the tooltip system
12 nv.utils = {}; // Utility subsystem
13 nv.models = {}; //stores all the possible models/components
14 nv.charts = {}; //stores all the ready to use charts
15 nv.graphs = []; //stores all the graphs currently on the page
16 nv.logs = {}; //stores some statistics and potential error messages
17
18 nv.dispatch = d3.dispatch('render_start', 'render_end');
19
20 // *************************************************************************
21 //  Development render timers - disabled if dev = false
22
23 if (nv.dev) {
24   nv.dispatch.on('render_start', function(e) {
25     nv.logs.startTime = +new Date();
26   });
27
28   nv.dispatch.on('render_end', function(e) {
29     nv.logs.endTime = +new Date();
30     nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
31     nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
32   });
33 }
34
35 // ********************************************
36 //  Public Core NV functions
37
38 // Logs all arguments, and returns the last so you can test things in place
39 // Note: in IE8 console.log is an object not a function, and if modernizr is used
40 // then calling Function.prototype.bind with with anything other than a function
41 // causes a TypeError to be thrown.
42 nv.log = function() {
43   if (nv.dev && console.log && console.log.apply)
44     console.log.apply(console, arguments)
45   else if (nv.dev && typeof console.log == "function" && Function.prototype.bind) {
46     var log = Function.prototype.bind.call(console.log, console);
47     log.apply(console, arguments);
48   }
49   return arguments[arguments.length - 1];
50 };
51
52
53 nv.render = function render(step) {
54   step = step || 1; // number of graphs to generate in each timeout loop
55
56   nv.render.active = true;
57   nv.dispatch.render_start();
58
59   setTimeout(function() {
60     var chart, graph;
61
62     for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
63       chart = graph.generate();
64       if (typeof graph.callback == typeof(Function)) graph.callback(chart);
65       nv.graphs.push(chart);
66     }
67
68     nv.render.queue.splice(0, i);
69
70     if (nv.render.queue.length) setTimeout(arguments.callee, 0);
71     else { nv.render.active = false; nv.dispatch.render_end(); }
72   }, 0);
73 };
74
75 nv.render.active = false;
76 nv.render.queue = [];
77
78 nv.addGraph = function(obj) {
79   if (typeof arguments[0] === typeof(Function))
80     obj = {generate: arguments[0], callback: arguments[1]};
81
82   nv.render.queue.push(obj);
83
84   if (!nv.render.active) nv.render();
85 };
86
87 nv.identity = function(d) { return d; };
88
89 nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
90
91 function daysInMonth(month,year) {
92   return (new Date(year, month+1, 0)).getDate();
93 }
94
95 function d3_time_range(floor, step, number) {
96   return function(t0, t1, dt) {
97     var time = floor(t0), times = [];
98     if (time < t0) step(time);
99     if (dt > 1) {
100       while (time < t1) {
101         var date = new Date(+time);
102         if ((number(date) % dt === 0)) times.push(date);
103         step(time);
104       }
105     } else {
106       while (time < t1) { times.push(new Date(+time)); step(time); }
107     }
108     return times;
109   };
110 }
111
112 d3.time.monthEnd = function(date) {
113   return new Date(date.getFullYear(), date.getMonth(), 0);
114 };
115
116 d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
117     date.setUTCDate(date.getUTCDate() + 1);
118     date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
119   }, function(date) {
120     return date.getMonth();
121   }
122 );
123
124 /* Utility class to handle creation of an interactive layer.
125 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
126 containing the X-coordinate. It can also render a vertical line where the mouse is located.
127
128 dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
129 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
130 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
131 */
132 nv.interactiveGuideline = function() {
133         "use strict";
134         var tooltip = nv.models.tooltip();
135         //Public settings
136         var width = null
137         , height = null
138     //Please pass in the bounding chart's top and left margins
139     //This is important for calculating the correct mouseX/Y positions.
140         , margin = {left: 0, top: 0}
141         , xScale = d3.scale.linear()
142         , yScale = d3.scale.linear()
143         , dispatch = d3.dispatch('elementMousemove', 'elementMouseout')
144         , showGuideLine = true
145         , svgContainer = null  
146     //Must pass in the bounding chart's <svg> container.
147     //The mousemove event is attached to this container.
148         ;
149
150         //Private variables
151         var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1  //Check user-agent for Microsoft Internet Explorer.
152         ;
153
154
155         function layer(selection) {
156                 selection.each(function(data) {
157                                 var container = d3.select(this);
158                                 
159                                 var availableWidth = (width || 960), availableHeight = (height || 400);
160
161                                 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
162                                 var wrapEnter = wrap.enter()
163                                                                 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
164                                                                 
165                                 
166                                 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
167                                 
168                                 if (!svgContainer) {
169                                         return;
170                                 }
171
172                 function mouseHandler() {
173                       var d3mouse = d3.mouse(this);
174                       var mouseX = d3mouse[0];
175                       var mouseY = d3mouse[1];
176                       var subtractMargin = true;
177                       var mouseOutAnyReason = false;
178                       if (isMSIE) {
179                          /*
180                             D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
181                             d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
182                             over a rect in IE 10.
183                             However, d3.event.offsetX/Y also returns the mouse coordinates
184                             relative to the triggering <rect>. So we use offsetX/Y on IE.  
185                          */
186                          mouseX = d3.event.offsetX;
187                          mouseY = d3.event.offsetY;
188
189                          /*
190                             On IE, if you attach a mouse event listener to the <svg> container,
191                             it will actually trigger it for all the child elements (like <path>, <circle>, etc).
192                             When this happens on IE, the offsetX/Y is set to where ever the child element
193                             is located.
194                             As a result, we do NOT need to subtract margins to figure out the mouse X/Y
195                             position under this scenario. Removing the line below *will* cause 
196                             the interactive layer to not work right on IE.
197                          */
198                          if(d3.event.target.tagName !== "svg")
199                             subtractMargin = false;
200
201                          if (d3.event.target.className.baseVal.match("nv-legend"))
202                                 mouseOutAnyReason = true;
203                           
204                       }
205
206                       if(subtractMargin) {
207                          mouseX -= margin.left;
208                          mouseY -= margin.top;
209                       }
210
211                       /* If mouseX/Y is outside of the chart's bounds,
212                       trigger a mouseOut event.
213                       */
214                       if (mouseX < 0 || mouseY < 0 
215                         || mouseX > availableWidth || mouseY > availableHeight
216                         || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
217                         || mouseOutAnyReason
218                         ) 
219                       {
220                                 if (isMSIE) {
221                                         if (d3.event.relatedTarget 
222                                                 && d3.event.relatedTarget.ownerSVGElement === undefined
223                                                 && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
224                                                 return;
225                                         }
226                                 }
227                             dispatch.elementMouseout({
228                                mouseX: mouseX,
229                                mouseY: mouseY
230                             });
231                             layer.renderGuideLine(null); //hide the guideline
232                             return;
233                       }
234                       
235                       var pointXValue = xScale.invert(mouseX);
236                       dispatch.elementMousemove({
237                             mouseX: mouseX,
238                             mouseY: mouseY,
239                             pointXValue: pointXValue
240                       });
241                 }
242
243                                 svgContainer
244                                       .on("mousemove",mouseHandler, true)
245                                       .on("mouseout",mouseHandler,true)
246                                       ;
247
248                                  //Draws a vertical guideline at the given X postion.
249                                 layer.renderGuideLine = function(x) {
250                                         if (!showGuideLine) return;
251                                         var line = wrap.select(".nv-interactiveGuideLine")
252                                               .selectAll("line")
253                                               .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
254
255                                         line.enter()
256                                                 .append("line")
257                                                 .attr("class", "nv-guideline")
258                                                 .attr("x1", function(d) { return d;})
259                                                 .attr("x2", function(d) { return d;})
260                                                 .attr("y1", availableHeight)
261                                                 .attr("y2",0)
262                                                 ;
263                                         line.exit().remove();
264
265                                 }
266                 });
267         }
268
269         layer.dispatch = dispatch;
270         layer.tooltip = tooltip;
271
272         layer.margin = function(_) {
273             if (!arguments.length) return margin;
274             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
275             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
276             return layer;
277     };
278
279         layer.width = function(_) {
280                 if (!arguments.length) return width;
281                 width = _;
282                 return layer;
283         };
284
285         layer.height = function(_) {
286                 if (!arguments.length) return height;
287                 height = _;
288                 return layer;
289         };
290
291         layer.xScale = function(_) {
292                 if (!arguments.length) return xScale;
293                 xScale = _;
294                 return layer;
295         };
296
297         layer.showGuideLine = function(_) {
298                 if (!arguments.length) return showGuideLine;
299                 showGuideLine = _;
300                 return layer;
301         };
302
303         layer.svgContainer = function(_) {
304                 if (!arguments.length) return svgContainer;
305                 svgContainer = _;
306                 return layer;
307         };
308
309
310         return layer;
311 };
312
313 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
314 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
315
316 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. 
317 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
318 because 28 is closer to 30 than 10.
319
320 Unit tests can be found in: interactiveBisectTest.html
321
322 Has the following known issues:
323    * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
324    * Won't work if there are duplicate x coordinate values.
325 */
326 nv.interactiveBisect = function (values, searchVal, xAccessor) {
327           "use strict";
328       if (! values instanceof Array) return null;
329       if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
330
331       var bisect = d3.bisector(xAccessor).left;
332       var index = d3.max([0, bisect(values,searchVal) - 1]);
333       var currentValue = xAccessor(values[index], index);
334       if (typeof currentValue === 'undefined') currentValue = index;
335
336       if (currentValue === searchVal) return index;  //found exact match
337
338       var nextIndex = d3.min([index+1, values.length - 1]);
339       var nextValue = xAccessor(values[nextIndex], nextIndex);
340       if (typeof nextValue === 'undefined') nextValue = nextIndex;
341
342       if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
343           return index;
344       else
345           return nextIndex
346 };/* Tooltip rendering model for nvd3 charts.
347 window.nv.models.tooltip is the updated,new way to render tooltips.
348
349 window.nv.tooltip.show is the old tooltip code.
350 window.nv.tooltip.* also has various helper methods.
351 */
352 (function() {
353   "use strict";
354   window.nv.tooltip = {};
355
356   /* Model which can be instantiated to handle tooltip rendering.
357     Example usage: 
358     var tip = nv.models.tooltip().gravity('w').distance(23)
359                 .data(myDataObject);
360
361         tip();    //just invoke the returned function to render tooltip.
362   */
363   window.nv.models.tooltip = function() {
364         var content = null    //HTML contents of the tooltip.  If null, the content is generated via the data variable.
365         ,   data = null     /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
366         Format of data:
367         {
368             key: "Date",
369             value: "August 2009", 
370             series: [
371                     {
372                         key: "Series 1",
373                         value: "Value 1",
374                         color: "#000"
375                     },
376                     {
377                         key: "Series 2",
378                         value: "Value 2",
379                         color: "#00f"
380                     }
381             ]
382
383         }
384
385         */
386         ,   gravity = 'w'   //Can be 'n','s','e','w'. Determines how tooltip is positioned.
387         ,   distance = 50   //Distance to offset tooltip from the mouse location.
388         ,   snapDistance = 25   //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
389         ,   fixedTop = null //If not null, this fixes the top position of the tooltip.
390         ,   classes = null  //Attaches additional CSS classes to the tooltip DIV that is created.
391         ,   chartContainer = null   //Parent DIV, of the SVG Container that holds the chart.
392         ,   position = {left: null, top: null}      //Relative position of the tooltip inside chartContainer.
393         ,   enabled = true  //True -> tooltips are rendered. False -> don't render tooltips.
394         //Generates a unique id when you create a new tooltip() object
395         ,   id = "nvtooltip-" + Math.floor(Math.random() * 100000)
396         ;
397
398         //CSS class to specify whether element should not have mouse events.
399         var  nvPointerEventsClass = "nv-pointer-events-none";
400
401         //Format function for the tooltip values column
402         var valueFormatter = function(d,i) {
403             return d;
404         };
405
406         //Format function for the tooltip header value.
407         var headerFormatter = function(d) {
408             return d;
409         };
410
411         //By default, the tooltip model renders a beautiful table inside a DIV.
412         //You can override this function if a custom tooltip is desired.
413         var contentGenerator = function(d) {
414             if (content != null) return content;
415
416             if (d == null) return '';
417
418             var html = "<table><thead><tr><td colspan='3'><strong class='x-value'>" + headerFormatter(d.value) + "</strong></td></tr></thead><tbody>";
419             if (d.series instanceof Array) {
420                 d.series.forEach(function(item, i) {
421                     html += "<tr>";
422                     html += "<td class='legend-color-guide'><div style='background-color: " + item.color + ";'></div></td>";
423                     html += "<td class='key'>" + item.key + ":</td>";
424                     html += "<td class='value'>" + valueFormatter(item.value,i) + "</td></tr>"; 
425                 });
426             }
427             html += "</tbody></table>";
428             return html;
429         };
430
431         var dataSeriesExists = function(d) {
432             if (d && d.series && d.series.length > 0) return true;
433
434             return false;
435         };
436
437         //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
438         function convertViewBoxRatio() {
439             if (chartContainer) {
440               var svg = d3.select(chartContainer);
441               if (svg.node().tagName !== "svg") {
442                  svg = svg.select("svg");
443               }
444               var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
445               if (viewBox) {
446                 viewBox = viewBox.split(' ');
447                 var ratio = parseInt(svg.style('width')) / viewBox[2];
448                 
449                 position.left = position.left * ratio;
450                 position.top  = position.top * ratio;
451               }
452             }
453         }
454
455         //Creates new tooltip container, or uses existing one on DOM.
456         function getTooltipContainer(newContent) {
457             var body;
458             if (chartContainer)
459                 body = d3.select(chartContainer);
460             else
461                 body = d3.select("body");
462
463             var container = body.select(".nvtooltip");
464             if (container.node() === null) {
465                 //Create new tooltip div if it doesn't exist on DOM.
466                 container = body.append("div")
467                     .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
468                     .attr("id",id)
469                     ;
470             }
471         
472
473             container.node().innerHTML = newContent;
474             container.style("top",0).style("left",0).style("opacity",0);
475             container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
476             container.classed(nvPointerEventsClass,true);
477             return container.node();
478         }
479
480         
481
482         //Draw the tooltip onto the DOM.
483         function nvtooltip() {
484             if (!enabled) return;
485             if (!dataSeriesExists(data)) return;
486
487             convertViewBoxRatio();
488
489             var left = position.left;
490             var top = (fixedTop != null) ? fixedTop : position.top;
491             var container = getTooltipContainer(contentGenerator(data));
492
493             if (chartContainer) {
494                 var svgComp = chartContainer.getElementsByTagName("svg")[0];
495                 var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
496                 var svgOffset = {left:0,top:0};
497                 if (svgComp) {
498                     var svgBound = svgComp.getBoundingClientRect();
499                     var chartBound = chartContainer.getBoundingClientRect();
500                     svgOffset.top = Math.abs(svgBound.top - chartBound.top);
501                     svgOffset.left = Math.abs(svgBound.left - chartBound.left);
502                 }
503                 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
504                 //You need to also add any offset between the <svg> element and its containing <div>
505                 //Finally, add any offset of the containing <div> on the whole page.
506                 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
507                 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
508             }
509
510             if (snapDistance && snapDistance > 0) {
511                 top = Math.floor(top/snapDistance) * snapDistance;
512             }
513
514             nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
515             return nvtooltip;
516         };
517
518         nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
519         
520         nvtooltip.content = function(_) {
521             if (!arguments.length) return content;
522             content = _;
523             return nvtooltip;
524         };
525
526         nvtooltip.contentGenerator = function(_) {
527             if (!arguments.length) return contentGenerator;
528             if (typeof _ === 'function') {
529                 contentGenerator = _;
530             }
531             return nvtooltip;
532         };
533
534         nvtooltip.data = function(_) {
535             if (!arguments.length) return data;
536             data = _;
537             return nvtooltip;
538         };
539
540         nvtooltip.gravity = function(_) {
541             if (!arguments.length) return gravity;
542             gravity = _;
543             return nvtooltip;
544         };
545
546         nvtooltip.distance = function(_) {
547             if (!arguments.length) return distance;
548             distance = _;
549             return nvtooltip;
550         };
551
552         nvtooltip.snapDistance = function(_) {
553             if (!arguments.length) return snapDistance;
554             snapDistance = _;
555             return nvtooltip;
556         };
557
558         nvtooltip.classes = function(_) {
559             if (!arguments.length) return classes;
560             classes = _;
561             return nvtooltip;
562         };
563
564         nvtooltip.chartContainer = function(_) {
565             if (!arguments.length) return chartContainer;
566             chartContainer = _;
567             return nvtooltip;
568         };
569
570         nvtooltip.position = function(_) {
571             if (!arguments.length) return position;
572             position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
573             position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
574             return nvtooltip;
575         };
576
577         nvtooltip.fixedTop = function(_) {
578             if (!arguments.length) return fixedTop;
579             fixedTop = _;
580             return nvtooltip;
581         };
582
583         nvtooltip.enabled = function(_) {
584             if (!arguments.length) return enabled;
585             enabled = _;
586             return nvtooltip;
587         };
588
589         nvtooltip.valueFormatter = function(_) {
590             if (!arguments.length) return valueFormatter;
591             if (typeof _ === 'function') {
592                 valueFormatter = _;
593             }
594             return nvtooltip;
595         };
596
597         nvtooltip.headerFormatter = function(_) {
598             if (!arguments.length) return headerFormatter;
599             if (typeof _ === 'function') {
600                 headerFormatter = _;
601             }
602             return nvtooltip;
603         };
604
605         //id() is a read-only function. You can't use it to set the id.
606         nvtooltip.id = function() {
607             return id;
608         };
609
610
611         return nvtooltip;
612   };
613
614
615   //Original tooltip.show function. Kept for backward compatibility.
616   // pos = [left,top]
617   nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
618       
619         //Create new tooltip div if it doesn't exist on DOM.
620         var   container = document.createElement('div');
621         container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
622
623         var body = parentContainer;
624         if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
625             //If the parent element is an SVG element, place tooltip in the <body> element.
626             body = document.getElementsByTagName('body')[0];
627         }
628    
629         container.style.left = 0;
630         container.style.top = 0;
631         container.style.opacity = 0;
632         container.innerHTML = content;
633         body.appendChild(container);
634         
635         //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
636         if (parentContainer) {
637            pos[0] = pos[0] - parentContainer.scrollLeft;
638            pos[1] = pos[1] - parentContainer.scrollTop;
639         }
640         nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
641   };
642
643   //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
644   nv.tooltip.findFirstNonSVGParent = function(Elem) {
645             while(Elem.tagName.match(/^g|svg$/i) !== null) {
646                 Elem = Elem.parentNode;
647             }
648             return Elem;
649   };
650
651   //Finds the total offsetTop of a given DOM element.
652   //Looks up the entire ancestry of an element, up to the first relatively positioned element.
653   nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
654                 var offsetTop = initialTop;
655                 
656                 do {
657                     if( !isNaN( Elem.offsetTop ) ) {
658                         offsetTop += (Elem.offsetTop);
659                     }
660                 } while( Elem = Elem.offsetParent );
661                 return offsetTop;
662   };
663
664   //Finds the total offsetLeft of a given DOM element.
665   //Looks up the entire ancestry of an element, up to the first relatively positioned element.
666   nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
667                 var offsetLeft = initialLeft;
668                 
669                 do {
670                     if( !isNaN( Elem.offsetLeft ) ) {
671                         offsetLeft += (Elem.offsetLeft);
672                     }
673                 } while( Elem = Elem.offsetParent );
674                 return offsetLeft;
675   };
676
677   //Global utility function to render a tooltip on the DOM.
678   //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
679   //gravity = how to orient the tooltip
680   //dist = how far away from the mouse to place tooltip
681   //container = tooltip DIV
682   nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
683
684             var height = parseInt(container.offsetHeight),
685                 width = parseInt(container.offsetWidth),
686                 windowWidth = nv.utils.windowSize().width,
687                 windowHeight = nv.utils.windowSize().height,
688                 scrollTop = window.pageYOffset,
689                 scrollLeft = window.pageXOffset,
690                 left, top;
691
692             windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
693             windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
694
695             gravity = gravity || 's';
696             dist = dist || 20;
697
698             var tooltipTop = function ( Elem ) {
699                 return nv.tooltip.findTotalOffsetTop(Elem, top);
700             };
701
702             var tooltipLeft = function ( Elem ) {
703                 return nv.tooltip.findTotalOffsetLeft(Elem,left);
704             };
705
706             switch (gravity) {
707               case 'e':
708                 left = pos[0] - width - dist;
709                 top = pos[1] - (height / 2);
710                 var tLeft = tooltipLeft(container);
711                 var tTop = tooltipTop(container);
712                 if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
713                 if (tTop < scrollTop) top = scrollTop - tTop + top;
714                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
715                 break;
716               case 'w':
717                 left = pos[0] + dist;
718                 top = pos[1] - (height / 2);
719                 var tLeft = tooltipLeft(container);
720                 var tTop = tooltipTop(container);
721                 if (tLeft + width > windowWidth) left = pos[0] - width - dist;
722                 if (tTop < scrollTop) top = scrollTop + 5;
723                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
724                 break;
725               case 'n':
726                 left = pos[0] - (width / 2) - 5;
727                 top = pos[1] + dist;
728                 var tLeft = tooltipLeft(container);
729                 var tTop = tooltipTop(container);
730                 if (tLeft < scrollLeft) left = scrollLeft + 5;
731                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
732                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
733                 break;
734               case 's':
735                 left = pos[0] - (width / 2);
736                 top = pos[1] - height - dist;
737                 var tLeft = tooltipLeft(container);
738                 var tTop = tooltipTop(container);
739                 if (tLeft < scrollLeft) left = scrollLeft + 5;
740                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
741                 if (scrollTop > tTop) top = scrollTop;
742                 break;
743               case 'none':
744                 left = pos[0];
745                 top = pos[1] - dist;
746                 var tLeft = tooltipLeft(container);
747                 var tTop = tooltipTop(container);
748                 break;
749             }
750
751
752             container.style.left = left+'px';
753             container.style.top = top+'px';
754             container.style.opacity = 1;
755             container.style.position = 'absolute'; 
756
757             return container;
758     };
759
760     //Global utility function to remove tooltips from the DOM.
761     nv.tooltip.cleanup = function() {
762
763               // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
764               var tooltips = document.getElementsByClassName('nvtooltip');
765               var purging = [];
766               while(tooltips.length) {
767                 purging.push(tooltips[0]);
768                 tooltips[0].style.transitionDelay = '0 !important';
769                 tooltips[0].style.opacity = 0;
770                 tooltips[0].className = 'nvtooltip-pending-removal';
771               }
772
773               setTimeout(function() {
774
775                   while (purging.length) {
776                      var removeMe = purging.pop();
777                       removeMe.parentNode.removeChild(removeMe);
778                   }
779             }, 500);
780     };
781
782 })();
783
784 nv.utils.windowSize = function() {
785     // Sane defaults
786     var size = {width: 640, height: 480};
787
788     // Earlier IE uses Doc.body
789     if (document.body && document.body.offsetWidth) {
790         size.width = document.body.offsetWidth;
791         size.height = document.body.offsetHeight;
792     }
793
794     // IE can use depending on mode it is in
795     if (document.compatMode=='CSS1Compat' &&
796         document.documentElement &&
797         document.documentElement.offsetWidth ) {
798         size.width = document.documentElement.offsetWidth;
799         size.height = document.documentElement.offsetHeight;
800     }
801
802     // Most recent browsers use
803     if (window.innerWidth && window.innerHeight) {
804         size.width = window.innerWidth;
805         size.height = window.innerHeight;
806     }
807     return (size);
808 };
809
810
811
812 // Easy way to bind multiple functions to window.onresize
813 // TODO: give a way to remove a function after its bound, other than removing all of them
814 nv.utils.windowResize = function(fun){
815   if (fun === undefined) return;
816   var oldresize = window.onresize;
817
818   window.onresize = function(e) {
819     if (typeof oldresize == 'function') oldresize(e);
820     fun(e);
821   }
822 }
823
824 // Backwards compatible way to implement more d3-like coloring of graphs.
825 // If passed an array, wrap it in a function which implements the old default
826 // behavior
827 nv.utils.getColor = function(color) {
828     if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
829
830     if( Object.prototype.toString.call( color ) === '[object Array]' )
831         return function(d, i) { return d.color || color[i % color.length]; };
832     else
833         return color;
834         //can't really help it if someone passes rubbish as color
835 }
836
837 // Default color chooser uses the index of an object as before.
838 nv.utils.defaultColor = function() {
839     var colors = d3.scale.category20().range();
840     return function(d, i) { return d.color || colors[i % colors.length] };
841 }
842
843
844 // Returns a color function that takes the result of 'getKey' for each series and
845 // looks for a corresponding color from the dictionary,
846 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
847   getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
848   defaultColors = defaultColors || d3.scale.category20().range(); //default color function
849
850   var defIndex = defaultColors.length; //current default color (going in reverse)
851
852   return function(series, index) {
853     var key = getKey(series);
854
855     if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
856
857     if (typeof dictionary[key] !== "undefined")
858       return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
859     else
860       return defaultColors[--defIndex]; // no match in dictionary, use default color
861   }
862 }
863
864
865
866 // From the PJAX example on d3js.org, while this is not really directly needed
867 // it's a very cool method for doing pjax, I may expand upon it a little bit,
868 // open to suggestions on anything that may be useful
869 nv.utils.pjax = function(links, content) {
870   d3.selectAll(links).on("click", function() {
871     history.pushState(this.href, this.textContent, this.href);
872     load(this.href);
873     d3.event.preventDefault();
874   });
875
876   function load(href) {
877     d3.html(href, function(fragment) {
878       var target = d3.select(content).node();
879       target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
880       nv.utils.pjax(links, content);
881     });
882   }
883
884   d3.select(window).on("popstate", function() {
885     if (d3.event.state) load(d3.event.state);
886   });
887 }
888
889 /* For situations where we want to approximate the width in pixels for an SVG:text element.
890 Most common instance is when the element is in a display:none; container. 
891 Forumla is : text.length * font-size * constant_factor
892 */
893 nv.utils.calcApproxTextWidth = function (svgTextElem) {
894     if (svgTextElem instanceof d3.selection) {
895         var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
896         var textLength = svgTextElem.text().length;
897
898         return textLength * fontSize * 0.5; 
899     }
900     return 0;
901 };
902
903 /* Numbers that are undefined, null or NaN, convert them to zeros.
904 */
905 nv.utils.NaNtoZero = function(n) {
906     if (typeof n !== 'number' 
907         || isNaN(n) 
908         || n === null
909         || n === Infinity) return 0;
910
911     return n;
912 };
913
914 /*
915 Snippet of code you can insert into each nv.models.* to give you the ability to
916 do things like:
917 chart.options({
918   showXAxis: true,
919   tooltips: true
920 });
921
922 To enable in the chart:
923 chart.options = nv.utils.optionsFunc.bind(chart);
924 */
925 nv.utils.optionsFunc = function(args) {
926     if (args) {
927       d3.map(args).forEach((function(key,value) {
928         if (typeof this[key] === "function") {
929            this[key](value);
930         }
931       }).bind(this));
932     }
933     return this;
934 };nv.models.axis = function() {
935   "use strict";
936   //============================================================
937   // Public Variables with Default Settings
938   //------------------------------------------------------------
939
940   var axis = d3.svg.axis()
941     ;
942
943   var margin = {top: 0, right: 0, bottom: 0, left: 0}
944     , width = 75 //only used for tickLabel currently
945     , height = 60 //only used for tickLabel currently
946     , scale = d3.scale.linear()
947     , axisLabelText = null
948     , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
949     , highlightZero = true
950     , rotateLabels = 0
951     , rotateYLabel = true
952     , staggerLabels = false
953     , isOrdinal = false
954     , ticks = null
955     ;
956
957   axis
958     .scale(scale)
959     .orient('bottom')
960     .tickFormat(function(d) { return d })
961     ;
962
963   //============================================================
964
965
966   //============================================================
967   // Private Variables
968   //------------------------------------------------------------
969
970   var scale0;
971
972   //============================================================
973
974
975   function chart(selection) {
976     selection.each(function(data) {
977       var container = d3.select(this);
978
979
980       //------------------------------------------------------------
981       // Setup containers and skeleton of chart
982
983       var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
984       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
985       var gEnter = wrapEnter.append('g');
986       var g = wrap.select('g')
987
988       //------------------------------------------------------------
989
990
991       if (ticks !== null)
992         axis.ticks(ticks);
993       else if (axis.orient() == 'top' || axis.orient() == 'bottom')
994         axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
995
996
997       //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
998
999
1000       g.transition().call(axis);
1001
1002       scale0 = scale0 || axis.scale();
1003
1004       var fmt = axis.tickFormat();
1005       if (fmt == null) {
1006         fmt = scale0.tickFormat();
1007       }
1008
1009       var axisLabel = g.selectAll('text.nv-axislabel')
1010           .data([axisLabelText || null]);
1011       axisLabel.exit().remove();
1012       switch (axis.orient()) {
1013         case 'top':
1014           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1015           var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1016           axisLabel
1017               .attr('text-anchor', 'middle')
1018               .attr('y', 0)
1019               .attr('x', w/2);
1020           if (showMaxMin) {
1021             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1022                            .data(scale.domain());
1023             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1024             axisMaxMin.exit().remove();
1025             axisMaxMin
1026                 .attr('transform', function(d,i) {
1027                   return 'translate(' + scale(d) + ',0)'
1028                 })
1029               .select('text')
1030                 .attr('dy', '0em')
1031                 .attr('y', -axis.tickPadding())
1032                 .attr('text-anchor', 'middle')
1033                 .text(function(d,i) {
1034                   var v = fmt(d);
1035                   return ('' + v).match('NaN') ? '' : v;
1036                 });
1037             axisMaxMin.transition()
1038                 .attr('transform', function(d,i) {
1039                   return 'translate(' + scale.range()[i] + ',0)'
1040                 });
1041           }
1042           break;
1043         case 'bottom':
1044           var xLabelMargin = 36;
1045           var maxTextWidth = 30;
1046           var xTicks = g.selectAll('g').select("text");
1047           if (rotateLabels%360) {
1048             //Calculate the longest xTick width
1049             xTicks.each(function(d,i){
1050               var width = this.getBBox().width;
1051               if(width > maxTextWidth) maxTextWidth = width;
1052             });
1053             //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1054             var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1055             var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1056             //Rotate all xTicks
1057             xTicks
1058               .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1059               .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1060           }
1061           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1062           var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1063           axisLabel
1064               .attr('text-anchor', 'middle')
1065               .attr('y', xLabelMargin)
1066               .attr('x', w/2);
1067           if (showMaxMin) {
1068           //if (showMaxMin && !isOrdinal) {
1069             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1070                            //.data(scale.domain())
1071                            .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1072             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1073             axisMaxMin.exit().remove();
1074             axisMaxMin
1075                 .attr('transform', function(d,i) {
1076                   return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1077                 })
1078               .select('text')
1079                 .attr('dy', '.71em')
1080                 .attr('y', axis.tickPadding())
1081                 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1082                 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1083                 .text(function(d,i) {
1084                   var v = fmt(d);
1085                   return ('' + v).match('NaN') ? '' : v;
1086                 });
1087             axisMaxMin.transition()
1088                 .attr('transform', function(d,i) {
1089                   //return 'translate(' + scale.range()[i] + ',0)'
1090                   //return 'translate(' + scale(d) + ',0)'
1091                   return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1092                 });
1093           }
1094           if (staggerLabels)
1095             xTicks
1096                 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1097
1098           break;
1099         case 'right':
1100           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1101           axisLabel
1102               .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1103               .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1104               .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
1105               .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1106           if (showMaxMin) {
1107             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1108                            .data(scale.domain());
1109             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1110                 .style('opacity', 0);
1111             axisMaxMin.exit().remove();
1112             axisMaxMin
1113                 .attr('transform', function(d,i) {
1114                   return 'translate(0,' + scale(d) + ')'
1115                 })
1116               .select('text')
1117                 .attr('dy', '.32em')
1118                 .attr('y', 0)
1119                 .attr('x', axis.tickPadding())
1120                 .style('text-anchor', 'start')
1121                 .text(function(d,i) {
1122                   var v = fmt(d);
1123                   return ('' + v).match('NaN') ? '' : v;
1124                 });
1125             axisMaxMin.transition()
1126                 .attr('transform', function(d,i) {
1127                   return 'translate(0,' + scale.range()[i] + ')'
1128                 })
1129               .select('text')
1130                 .style('opacity', 1);
1131           }
1132           break;
1133         case 'left':
1134           /*
1135           //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1136           var yTicks = g.selectAll('g').select("text");
1137           yTicks.each(function(d,i){
1138             var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
1139             if(labelPadding > width) width = labelPadding;
1140           });
1141           */
1142           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1143           axisLabel
1144               .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1145               .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1146               .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1147               .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1148           if (showMaxMin) {
1149             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1150                            .data(scale.domain());
1151             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1152                 .style('opacity', 0);
1153             axisMaxMin.exit().remove();
1154             axisMaxMin
1155                 .attr('transform', function(d,i) {
1156                   return 'translate(0,' + scale0(d) + ')'
1157                 })
1158               .select('text')
1159                 .attr('dy', '.32em')
1160                 .attr('y', 0)
1161                 .attr('x', -axis.tickPadding())
1162                 .attr('text-anchor', 'end')
1163                 .text(function(d,i) {
1164                   var v = fmt(d);
1165                   return ('' + v).match('NaN') ? '' : v;
1166                 });
1167             axisMaxMin.transition()
1168                 .attr('transform', function(d,i) {
1169                   return 'translate(0,' + scale.range()[i] + ')'
1170                 })
1171               .select('text')
1172                 .style('opacity', 1);
1173           }
1174           break;
1175       }
1176       axisLabel
1177           .text(function(d) { return d });
1178
1179
1180       if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1181         //check if max and min overlap other values, if so, hide the values that overlap
1182         g.selectAll('g') // the g's wrapping each tick
1183             .each(function(d,i) {
1184               d3.select(this).select('text').attr('opacity', 1);
1185               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!
1186                 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1187                   d3.select(this).attr('opacity', 0);
1188                 
1189                 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1190               }
1191             });
1192
1193         //if Max and Min = 0 only show min, Issue #281
1194         if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1195           wrap.selectAll('g.nv-axisMaxMin')
1196             .style('opacity', function(d,i) { return !i ? 1 : 0 });
1197
1198       }
1199
1200       if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1201         var maxMinRange = [];
1202         wrap.selectAll('g.nv-axisMaxMin')
1203             .each(function(d,i) {
1204               try {
1205                   if (i) // i== 1, max position
1206                       maxMinRange.push(scale(d) - this.getBBox().width - 4)  //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
1207                   else // i==0, min position
1208                       maxMinRange.push(scale(d) + this.getBBox().width + 4)
1209               }catch (err) {
1210                   if (i) // i== 1, max position
1211                       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)
1212                   else // i==0, min position
1213                       maxMinRange.push(scale(d) + 4)
1214               }
1215             });
1216         g.selectAll('g') // the g's wrapping each tick
1217             .each(function(d,i) {
1218               if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1219                 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1220                   d3.select(this).remove();
1221                 else
1222                   d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1223               }
1224             });
1225       }
1226
1227
1228       //highlight zero line ... Maybe should not be an option and should just be in CSS?
1229       if (highlightZero)
1230         g.selectAll('.tick')
1231           .filter(function(d) { return !parseFloat(Math.round(d.__data__*100000)/1000000) && (d.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1232             .classed('zero', true);
1233
1234       //store old scales for use in transitions on update
1235       scale0 = scale.copy();
1236
1237     });
1238
1239     return chart;
1240   }
1241
1242
1243   //============================================================
1244   // Expose Public Variables
1245   //------------------------------------------------------------
1246
1247   // expose chart's sub-components
1248   chart.axis = axis;
1249
1250   d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1251   d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1252
1253   chart.options = nv.utils.optionsFunc.bind(chart);
1254   
1255   chart.margin = function(_) {
1256     if(!arguments.length) return margin;
1257     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
1258     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
1259     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1260     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
1261     return chart;
1262   }
1263
1264   chart.width = function(_) {
1265     if (!arguments.length) return width;
1266     width = _;
1267     return chart;
1268   };
1269
1270   chart.ticks = function(_) {
1271     if (!arguments.length) return ticks;
1272     ticks = _;
1273     return chart;
1274   };
1275
1276   chart.height = function(_) {
1277     if (!arguments.length) return height;
1278     height = _;
1279     return chart;
1280   };
1281
1282   chart.axisLabel = function(_) {
1283     if (!arguments.length) return axisLabelText;
1284     axisLabelText = _;
1285     return chart;
1286   }
1287
1288   chart.showMaxMin = function(_) {
1289     if (!arguments.length) return showMaxMin;
1290     showMaxMin = _;
1291     return chart;
1292   }
1293
1294   chart.highlightZero = function(_) {
1295     if (!arguments.length) return highlightZero;
1296     highlightZero = _;
1297     return chart;
1298   }
1299
1300   chart.scale = function(_) {
1301     if (!arguments.length) return scale;
1302     scale = _;
1303     axis.scale(scale);
1304     isOrdinal = typeof scale.rangeBands === 'function';
1305     d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1306     return chart;
1307   }
1308
1309   chart.rotateYLabel = function(_) {
1310     if(!arguments.length) return rotateYLabel;
1311     rotateYLabel = _;
1312     return chart;
1313   }
1314
1315   chart.rotateLabels = function(_) {
1316     if(!arguments.length) return rotateLabels;
1317     rotateLabels = _;
1318     return chart;
1319   }
1320
1321   chart.staggerLabels = function(_) {
1322     if (!arguments.length) return staggerLabels;
1323     staggerLabels = _;
1324     return chart;
1325   };
1326
1327   //============================================================
1328
1329
1330   return chart;
1331 }
1332
1333 // Chart design based on the recommendations of Stephen Few. Implementation
1334 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1335 // http://projects.instantcognition.com/protovis/bulletchart/
1336
1337 nv.models.bullet = function() {
1338   "use strict";
1339   //============================================================
1340   // Public Variables with Default Settings
1341   //------------------------------------------------------------
1342
1343   var margin = {top: 0, right: 0, bottom: 0, left: 0}
1344     , orient = 'left' // TODO top & bottom
1345     , reverse = false
1346     , ranges = function(d) { return d.ranges }
1347     , markers = function(d) { return d.markers }
1348     , measures = function(d) { return d.measures }
1349     , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1350     , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
1351     , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
1352     , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1353     , width = 380
1354     , height = 30
1355     , tickFormat = null
1356     , color = nv.utils.getColor(['#1f77b4'])
1357     , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1358     ;
1359
1360   //============================================================
1361
1362
1363   function chart(selection) {
1364     selection.each(function(d, i) {
1365       var availableWidth = width - margin.left - margin.right,
1366           availableHeight = height - margin.top - margin.bottom,
1367           container = d3.select(this);
1368
1369       var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1370           markerz = markers.call(this, d, i).slice().sort(d3.descending),
1371           measurez = measures.call(this, d, i).slice().sort(d3.descending),
1372           rangeLabelz = rangeLabels.call(this, d, i).slice(),
1373           markerLabelz = markerLabels.call(this, d, i).slice(),
1374           measureLabelz = measureLabels.call(this, d, i).slice();
1375
1376
1377       //------------------------------------------------------------
1378       // Setup Scales
1379
1380       // Compute the new x-scale.
1381       var x1 = d3.scale.linear()
1382           .domain( d3.extent(d3.merge([forceX, rangez])) )
1383           .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1384
1385       // Retrieve the old x-scale, if this is an update.
1386       var x0 = this.__chart__ || d3.scale.linear()
1387           .domain([0, Infinity])
1388           .range(x1.range());
1389
1390       // Stash the new scale.
1391       this.__chart__ = x1;
1392
1393
1394       var rangeMin = d3.min(rangez), //rangez[2]
1395           rangeMax = d3.max(rangez), //rangez[0]
1396           rangeAvg = rangez[1];
1397
1398       //------------------------------------------------------------
1399
1400
1401       //------------------------------------------------------------
1402       // Setup containers and skeleton of chart
1403
1404       var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1405       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1406       var gEnter = wrapEnter.append('g');
1407       var g = wrap.select('g');
1408
1409       gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1410       gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1411       gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1412       gEnter.append('rect').attr('class', 'nv-measure');
1413       gEnter.append('path').attr('class', 'nv-markerTriangle');
1414
1415       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1416
1417       //------------------------------------------------------------
1418
1419
1420
1421       var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1422           w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1423       var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1424           xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1425
1426
1427       g.select('rect.nv-rangeMax')
1428           .attr('height', availableHeight)
1429           .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1430           .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1431           .datum(rangeMax > 0 ? rangeMax : rangeMin)
1432           /*
1433           .attr('x', rangeMin < 0 ?
1434                          rangeMax > 0 ?
1435                              x1(rangeMin)
1436                            : x1(rangeMax)
1437                        : x1(0))
1438                       */
1439
1440       g.select('rect.nv-rangeAvg')
1441           .attr('height', availableHeight)
1442           .attr('width', w1(rangeAvg))
1443           .attr('x', xp1(rangeAvg))
1444           .datum(rangeAvg)
1445           /*
1446           .attr('width', rangeMax <= 0 ?
1447                              x1(rangeMax) - x1(rangeAvg)
1448                            : x1(rangeAvg) - x1(rangeMin))
1449           .attr('x', rangeMax <= 0 ?
1450                          x1(rangeAvg)
1451                        : x1(rangeMin))
1452                       */
1453
1454       g.select('rect.nv-rangeMin')
1455           .attr('height', availableHeight)
1456           .attr('width', w1(rangeMax))
1457           .attr('x', xp1(rangeMax))
1458           .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1459           .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1460           .datum(rangeMax > 0 ? rangeMin : rangeMax)
1461           /*
1462           .attr('width', rangeMax <= 0 ?
1463                              x1(rangeAvg) - x1(rangeMin)
1464                            : x1(rangeMax) - x1(rangeAvg))
1465           .attr('x', rangeMax <= 0 ?
1466                          x1(rangeMin)
1467                        : x1(rangeAvg))
1468                       */
1469
1470       g.select('rect.nv-measure')
1471           .style('fill', color)
1472           .attr('height', availableHeight / 3)
1473           .attr('y', availableHeight / 3)
1474           .attr('width', measurez < 0 ?
1475                              x1(0) - x1(measurez[0])
1476                            : x1(measurez[0]) - x1(0))
1477           .attr('x', xp1(measurez))
1478           .on('mouseover', function() {
1479               dispatch.elementMouseover({
1480                 value: measurez[0],
1481                 label: measureLabelz[0] || 'Current',
1482                 pos: [x1(measurez[0]), availableHeight/2]
1483               })
1484           })
1485           .on('mouseout', function() {
1486               dispatch.elementMouseout({
1487                 value: measurez[0],
1488                 label: measureLabelz[0] || 'Current'
1489               })
1490           })
1491
1492       var h3 =  availableHeight / 6;
1493       if (markerz[0]) {
1494         g.selectAll('path.nv-markerTriangle')
1495             .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1496             .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1497             .on('mouseover', function() {
1498               dispatch.elementMouseover({
1499                 value: markerz[0],
1500                 label: markerLabelz[0] || 'Previous',
1501                 pos: [x1(markerz[0]), availableHeight/2]
1502               })
1503             })
1504             .on('mouseout', function() {
1505               dispatch.elementMouseout({
1506                 value: markerz[0],
1507                 label: markerLabelz[0] || 'Previous'
1508               })
1509             });
1510       } else {
1511         g.selectAll('path.nv-markerTriangle').remove();
1512       }
1513
1514
1515       wrap.selectAll('.nv-range')
1516           .on('mouseover', function(d,i) {
1517             var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1518
1519             dispatch.elementMouseover({
1520               value: d,
1521               label: label,
1522               pos: [x1(d), availableHeight/2]
1523             })
1524           })
1525           .on('mouseout', function(d,i) {
1526             var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1527
1528             dispatch.elementMouseout({
1529               value: d,
1530               label: label
1531             })
1532           })
1533
1534 /* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
1535       // Update the range rects.
1536       var range = g.selectAll('rect.nv-range')
1537           .data(rangez);
1538
1539       range.enter().append('rect')
1540           .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
1541           .attr('width', w0)
1542           .attr('height', availableHeight)
1543           .attr('x', reverse ? x0 : 0)
1544           .on('mouseover', function(d,i) { 
1545               dispatch.elementMouseover({
1546                 value: d,
1547                 label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
1548                 pos: [x1(d), availableHeight/2]
1549               })
1550           })
1551           .on('mouseout', function(d,i) { 
1552               dispatch.elementMouseout({
1553                 value: d,
1554                 label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
1555               })
1556           })
1557
1558       d3.transition(range)
1559           .attr('x', reverse ? x1 : 0)
1560           .attr('width', w1)
1561           .attr('height', availableHeight);
1562
1563
1564       // Update the measure rects.
1565       var measure = g.selectAll('rect.nv-measure')
1566           .data(measurez);
1567
1568       measure.enter().append('rect')
1569           .attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
1570           .style('fill', function(d,i) { return color(d,i ) })
1571           .attr('width', w0)
1572           .attr('height', availableHeight / 3)
1573           .attr('x', reverse ? x0 : 0)
1574           .attr('y', availableHeight / 3)
1575           .on('mouseover', function(d) { 
1576               dispatch.elementMouseover({
1577                 value: d,
1578                 label: 'Current', //TODO: make these labels a variable
1579                 pos: [x1(d), availableHeight/2]
1580               })
1581           })
1582           .on('mouseout', function(d) { 
1583               dispatch.elementMouseout({
1584                 value: d,
1585                 label: 'Current' //TODO: make these labels a variable
1586               })
1587           })
1588
1589       d3.transition(measure)
1590           .attr('width', w1)
1591           .attr('height', availableHeight / 3)
1592           .attr('x', reverse ? x1 : 0)
1593           .attr('y', availableHeight / 3);
1594
1595
1596
1597       // Update the marker lines.
1598       var marker = g.selectAll('path.nv-markerTriangle')
1599           .data(markerz);
1600
1601       var h3 =  availableHeight / 6;
1602       marker.enter().append('path')
1603           .attr('class', 'nv-markerTriangle')
1604           .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
1605           .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1606           .on('mouseover', function(d,i) {
1607               dispatch.elementMouseover({
1608                 value: d,
1609                 label: 'Previous',
1610                 pos: [x1(d), availableHeight/2]
1611               })
1612           })
1613           .on('mouseout', function(d,i) {
1614               dispatch.elementMouseout({
1615                 value: d,
1616                 label: 'Previous'
1617               })
1618           });
1619
1620       d3.transition(marker)
1621           .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
1622
1623       marker.exit().remove();
1624 */
1625
1626     });
1627
1628     // d3.timer.flush();  // Not needed?
1629
1630     return chart;
1631   }
1632
1633
1634   //============================================================
1635   // Expose Public Variables
1636   //------------------------------------------------------------
1637
1638   chart.dispatch = dispatch;
1639
1640   chart.options = nv.utils.optionsFunc.bind(chart);
1641   
1642   // left, right, top, bottom
1643   chart.orient = function(_) {
1644     if (!arguments.length) return orient;
1645     orient = _;
1646     reverse = orient == 'right' || orient == 'bottom';
1647     return chart;
1648   };
1649
1650   // ranges (bad, satisfactory, good)
1651   chart.ranges = function(_) {
1652     if (!arguments.length) return ranges;
1653     ranges = _;
1654     return chart;
1655   };
1656
1657   // markers (previous, goal)
1658   chart.markers = function(_) {
1659     if (!arguments.length) return markers;
1660     markers = _;
1661     return chart;
1662   };
1663
1664   // measures (actual, forecast)
1665   chart.measures = function(_) {
1666     if (!arguments.length) return measures;
1667     measures = _;
1668     return chart;
1669   };
1670
1671   chart.forceX = function(_) {
1672     if (!arguments.length) return forceX;
1673     forceX = _;
1674     return chart;
1675   };
1676
1677   chart.width = function(_) {
1678     if (!arguments.length) return width;
1679     width = _;
1680     return chart;
1681   };
1682
1683   chart.height = function(_) {
1684     if (!arguments.length) return height;
1685     height = _;
1686     return chart;
1687   };
1688
1689   chart.margin = function(_) {
1690     if (!arguments.length) return margin;
1691     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
1692     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
1693     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1694     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
1695     return chart;
1696   };
1697
1698   chart.tickFormat = function(_) {
1699     if (!arguments.length) return tickFormat;
1700     tickFormat = _;
1701     return chart;
1702   };
1703
1704   chart.color = function(_) {
1705     if (!arguments.length) return color;
1706     color = nv.utils.getColor(_);
1707     return chart;
1708   };
1709
1710   //============================================================
1711
1712
1713   return chart;
1714 };
1715
1716
1717
1718 // Chart design based on the recommendations of Stephen Few. Implementation
1719 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1720 // http://projects.instantcognition.com/protovis/bulletchart/
1721 nv.models.bulletChart = function() {
1722   "use strict";
1723   //============================================================
1724   // Public Variables with Default Settings
1725   //------------------------------------------------------------
1726
1727   var bullet = nv.models.bullet()
1728     ;
1729
1730   var orient = 'left' // TODO top & bottom
1731     , reverse = false
1732     , margin = {top: 5, right: 40, bottom: 20, left: 120}
1733     , ranges = function(d) { return d.ranges }
1734     , markers = function(d) { return d.markers }
1735     , measures = function(d) { return d.measures }
1736     , width = null
1737     , height = 55
1738     , tickFormat = null
1739     , tooltips = true
1740     , tooltip = function(key, x, y, e, graph) {
1741         return '<h3>' + x + '</h3>' +
1742                '<p>' + y + '</p>'
1743       }
1744     , noData = 'No Data Available.'
1745     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
1746     ;
1747
1748   //============================================================
1749
1750
1751   //============================================================
1752   // Private Variables
1753   //------------------------------------------------------------
1754
1755   var showTooltip = function(e, offsetElement) {
1756     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
1757         top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
1758         content = tooltip(e.key, e.label, e.value, e, chart);
1759
1760     nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
1761   };
1762
1763   //============================================================
1764
1765
1766   function chart(selection) {
1767     selection.each(function(d, i) {
1768       var container = d3.select(this);
1769
1770       var availableWidth = (width  || parseInt(container.style('width')) || 960)
1771                              - margin.left - margin.right,
1772           availableHeight = height - margin.top - margin.bottom,
1773           that = this;
1774
1775
1776       chart.update = function() { chart(selection) };
1777       chart.container = this;
1778
1779       //------------------------------------------------------------
1780       // Display No Data message if there's nothing to show.
1781
1782       if (!d || !ranges.call(this, d, i)) {
1783         var noDataText = container.selectAll('.nv-noData').data([noData]);
1784
1785         noDataText.enter().append('text')
1786           .attr('class', 'nvd3 nv-noData')
1787           .attr('dy', '-.7em')
1788           .style('text-anchor', 'middle');
1789
1790         noDataText
1791           .attr('x', margin.left + availableWidth / 2)
1792           .attr('y', 18 + margin.top + availableHeight / 2)
1793           .text(function(d) { return d });
1794
1795         return chart;
1796       } else {
1797         container.selectAll('.nv-noData').remove();
1798       }
1799
1800       //------------------------------------------------------------
1801
1802
1803
1804       var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1805           markerz = markers.call(this, d, i).slice().sort(d3.descending),
1806           measurez = measures.call(this, d, i).slice().sort(d3.descending);
1807
1808
1809       //------------------------------------------------------------
1810       // Setup containers and skeleton of chart
1811
1812       var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
1813       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
1814       var gEnter = wrapEnter.append('g');
1815       var g = wrap.select('g');
1816
1817       gEnter.append('g').attr('class', 'nv-bulletWrap');
1818       gEnter.append('g').attr('class', 'nv-titles');
1819
1820       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1821
1822       //------------------------------------------------------------
1823
1824
1825       // Compute the new x-scale.
1826       var x1 = d3.scale.linear()
1827           .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
1828           .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1829
1830       // Retrieve the old x-scale, if this is an update.
1831       var x0 = this.__chart__ || d3.scale.linear()
1832           .domain([0, Infinity])
1833           .range(x1.range());
1834
1835       // Stash the new scale.
1836       this.__chart__ = x1;
1837
1838       /*
1839       // Derive width-scales from the x-scales.
1840       var w0 = bulletWidth(x0),
1841           w1 = bulletWidth(x1);
1842
1843       function bulletWidth(x) {
1844         var x0 = x(0);
1845         return function(d) {
1846           return Math.abs(x(d) - x(0));
1847         };
1848       }
1849
1850       function bulletTranslate(x) {
1851         return function(d) {
1852           return 'translate(' + x(d) + ',0)';
1853         };
1854       }
1855       */
1856
1857       var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1858           w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1859
1860
1861       var title = gEnter.select('.nv-titles').append('g')
1862           .attr('text-anchor', 'end')
1863           .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
1864       title.append('text')
1865           .attr('class', 'nv-title')
1866           .text(function(d) { return d.title; });
1867
1868       title.append('text')
1869           .attr('class', 'nv-subtitle')
1870           .attr('dy', '1em')
1871           .text(function(d) { return d.subtitle; });
1872
1873
1874
1875       bullet
1876         .width(availableWidth)
1877         .height(availableHeight)
1878
1879       var bulletWrap = g.select('.nv-bulletWrap');
1880
1881       d3.transition(bulletWrap).call(bullet);
1882
1883
1884
1885       // Compute the tick format.
1886       var format = tickFormat || x1.tickFormat( availableWidth / 100 );
1887
1888       // Update the tick groups.
1889       var tick = g.selectAll('g.nv-tick')
1890           .data(x1.ticks( availableWidth / 50 ), function(d) {
1891             return this.textContent || format(d);
1892           });
1893
1894       // Initialize the ticks with the old scale, x0.
1895       var tickEnter = tick.enter().append('g')
1896           .attr('class', 'nv-tick')
1897           .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
1898           .style('opacity', 1e-6);
1899
1900       tickEnter.append('line')
1901           .attr('y1', availableHeight)
1902           .attr('y2', availableHeight * 7 / 6);
1903
1904       tickEnter.append('text')
1905           .attr('text-anchor', 'middle')
1906           .attr('dy', '1em')
1907           .attr('y', availableHeight * 7 / 6)
1908           .text(format);
1909
1910
1911       // Transition the updating ticks to the new scale, x1.
1912       var tickUpdate = d3.transition(tick)
1913           .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1914           .style('opacity', 1);
1915
1916       tickUpdate.select('line')
1917           .attr('y1', availableHeight)
1918           .attr('y2', availableHeight * 7 / 6);
1919
1920       tickUpdate.select('text')
1921           .attr('y', availableHeight * 7 / 6);
1922
1923       // Transition the exiting ticks to the new scale, x1.
1924       d3.transition(tick.exit())
1925           .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1926           .style('opacity', 1e-6)
1927           .remove();
1928
1929
1930       //============================================================
1931       // Event Handling/Dispatching (in chart's scope)
1932       //------------------------------------------------------------
1933
1934       dispatch.on('tooltipShow', function(e) {
1935         e.key = d.title;
1936         if (tooltips) showTooltip(e, that.parentNode);
1937       });
1938
1939       //============================================================
1940
1941     });
1942
1943     d3.timer.flush();
1944
1945     return chart;
1946   }
1947
1948
1949   //============================================================
1950   // Event Handling/Dispatching (out of chart's scope)
1951   //------------------------------------------------------------
1952
1953   bullet.dispatch.on('elementMouseover.tooltip', function(e) {
1954     dispatch.tooltipShow(e);
1955   });
1956
1957   bullet.dispatch.on('elementMouseout.tooltip', function(e) {
1958     dispatch.tooltipHide(e);
1959   });
1960
1961   dispatch.on('tooltipHide', function() {
1962     if (tooltips) nv.tooltip.cleanup();
1963   });
1964
1965   //============================================================
1966
1967
1968   //============================================================
1969   // Expose Public Variables
1970   //------------------------------------------------------------
1971
1972   chart.dispatch = dispatch;
1973   chart.bullet = bullet;
1974
1975   d3.rebind(chart, bullet, 'color');
1976
1977   chart.options = nv.utils.optionsFunc.bind(chart);
1978   
1979   // left, right, top, bottom
1980   chart.orient = function(x) {
1981     if (!arguments.length) return orient;
1982     orient = x;
1983     reverse = orient == 'right' || orient == 'bottom';
1984     return chart;
1985   };
1986
1987   // ranges (bad, satisfactory, good)
1988   chart.ranges = function(x) {
1989     if (!arguments.length) return ranges;
1990     ranges = x;
1991     return chart;
1992   };
1993
1994   // markers (previous, goal)
1995   chart.markers = function(x) {
1996     if (!arguments.length) return markers;
1997     markers = x;
1998     return chart;
1999   };
2000
2001   // measures (actual, forecast)
2002   chart.measures = function(x) {
2003     if (!arguments.length) return measures;
2004     measures = x;
2005     return chart;
2006   };
2007
2008   chart.width = function(x) {
2009     if (!arguments.length) return width;
2010     width = x;
2011     return chart;
2012   };
2013
2014   chart.height = function(x) {
2015     if (!arguments.length) return height;
2016     height = x;
2017     return chart;
2018   };
2019
2020   chart.margin = function(_) {
2021     if (!arguments.length) return margin;
2022     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2023     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2024     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2025     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2026     return chart;
2027   };
2028
2029   chart.tickFormat = function(x) {
2030     if (!arguments.length) return tickFormat;
2031     tickFormat = x;
2032     return chart;
2033   };
2034
2035   chart.tooltips = function(_) {
2036     if (!arguments.length) return tooltips;
2037     tooltips = _;
2038     return chart;
2039   };
2040
2041   chart.tooltipContent = function(_) {
2042     if (!arguments.length) return tooltip;
2043     tooltip = _;
2044     return chart;
2045   };
2046
2047   chart.noData = function(_) {
2048     if (!arguments.length) return noData;
2049     noData = _;
2050     return chart;
2051   };
2052
2053   //============================================================
2054
2055
2056   return chart;
2057 };
2058
2059
2060
2061 nv.models.cumulativeLineChart = function() {
2062   "use strict";
2063   //============================================================
2064   // Public Variables with Default Settings
2065   //------------------------------------------------------------
2066
2067   var lines = nv.models.line()
2068     , xAxis = nv.models.axis()
2069     , yAxis = nv.models.axis()
2070     , legend = nv.models.legend()
2071     , controls = nv.models.legend()
2072     , interactiveLayer = nv.interactiveGuideline()
2073     ;
2074
2075   var margin = {top: 30, right: 30, bottom: 50, left: 60}
2076     , color = nv.utils.defaultColor()
2077     , width = null
2078     , height = null
2079     , showLegend = true
2080     , showXAxis = true
2081     , showYAxis = true
2082     , rightAlignYAxis = false
2083     , tooltips = true
2084     , showControls = true
2085     , useInteractiveGuideline = false
2086     , rescaleY = true
2087     , tooltip = function(key, x, y, e, graph) {
2088         return '<h3>' + key + '</h3>' +
2089                '<p>' +  y + ' at ' + x + '</p>'
2090       }
2091     , x //can be accessed via chart.xScale()
2092     , y //can be accessed via chart.yScale()
2093     , id = lines.id()
2094     , state = { index: 0, rescaleY: rescaleY }
2095     , defaultState = null
2096     , noData = 'No Data Available.'
2097     , average = function(d) { return d.average }
2098     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
2099     , transitionDuration = 250
2100     ;
2101
2102   xAxis
2103     .orient('bottom')
2104     .tickPadding(7)
2105     ;
2106   yAxis
2107     .orient((rightAlignYAxis) ? 'right' : 'left')
2108     ;
2109
2110   //============================================================
2111   controls.updateState(false);
2112
2113   //============================================================
2114   // Private Variables
2115   //------------------------------------------------------------
2116
2117    var dx = d3.scale.linear()
2118      , index = {i: 0, x: 0}
2119      ;
2120
2121   var showTooltip = function(e, offsetElement) {
2122     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2123         top = e.pos[1] + ( offsetElement.offsetTop || 0),
2124         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2125         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2126         content = tooltip(e.series.key, x, y, e, chart);
2127
2128     nv.tooltip.show([left, top], content, null, null, offsetElement);
2129   };
2130
2131 /*
2132   //Moved to see if we can get better behavior to fix issue #315
2133   var indexDrag = d3.behavior.drag()
2134                     .on('dragstart', dragStart)
2135                     .on('drag', dragMove)
2136                     .on('dragend', dragEnd);
2137
2138   function dragStart(d,i) {
2139     d3.select(chart.container)
2140         .style('cursor', 'ew-resize');
2141   }
2142
2143   function dragMove(d,i) {
2144     d.x += d3.event.dx;
2145     d.i = Math.round(dx.invert(d.x));
2146
2147     d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
2148     chart.update();
2149   }
2150
2151   function dragEnd(d,i) {
2152     d3.select(chart.container)
2153         .style('cursor', 'auto');
2154     chart.update();
2155   }
2156 */
2157
2158   //============================================================
2159
2160
2161   function chart(selection) {
2162     selection.each(function(data) {
2163       var container = d3.select(this).classed('nv-chart-' + id, true),
2164           that = this;
2165
2166       var availableWidth = (width  || parseInt(container.style('width')) || 960)
2167                              - margin.left - margin.right,
2168           availableHeight = (height || parseInt(container.style('height')) || 400)
2169                              - margin.top - margin.bottom;
2170
2171
2172       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
2173       chart.container = this;
2174
2175       //set state.disabled
2176       state.disabled = data.map(function(d) { return !!d.disabled });
2177
2178       if (!defaultState) {
2179         var key;
2180         defaultState = {};
2181         for (key in state) {
2182           if (state[key] instanceof Array)
2183             defaultState[key] = state[key].slice(0);
2184           else
2185             defaultState[key] = state[key];
2186         }
2187       }
2188
2189       var indexDrag = d3.behavior.drag()
2190                         .on('dragstart', dragStart)
2191                         .on('drag', dragMove)
2192                         .on('dragend', dragEnd);
2193
2194
2195       function dragStart(d,i) {
2196         d3.select(chart.container)
2197             .style('cursor', 'ew-resize');
2198       }
2199
2200       function dragMove(d,i) {
2201         index.x = d3.event.x;
2202         index.i = Math.round(dx.invert(index.x));
2203         updateZero();
2204       }
2205
2206       function dragEnd(d,i) {
2207         d3.select(chart.container)
2208             .style('cursor', 'auto');
2209
2210         // update state and send stateChange with new index
2211         state.index = index.i;
2212         dispatch.stateChange(state);
2213       }
2214
2215       //------------------------------------------------------------
2216       // Display No Data message if there's nothing to show.
2217
2218       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2219         var noDataText = container.selectAll('.nv-noData').data([noData]);
2220
2221         noDataText.enter().append('text')
2222           .attr('class', 'nvd3 nv-noData')
2223           .attr('dy', '-.7em')
2224           .style('text-anchor', 'middle');
2225
2226         noDataText
2227           .attr('x', margin.left + availableWidth / 2)
2228           .attr('y', margin.top + availableHeight / 2)
2229           .text(function(d) { return d });
2230
2231         return chart;
2232       } else {
2233         container.selectAll('.nv-noData').remove();
2234       }
2235
2236       //------------------------------------------------------------
2237
2238
2239       //------------------------------------------------------------
2240       // Setup Scales
2241
2242       x = lines.xScale();
2243       y = lines.yScale();
2244
2245
2246       if (!rescaleY) {
2247         var seriesDomains = data
2248           .filter(function(series) { return !series.disabled })
2249           .map(function(series,i) {
2250             var initialDomain = d3.extent(series.values, lines.y());
2251
2252             //account for series being disabled when losing 95% or more
2253             if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2254
2255             return [
2256               (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2257               (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2258             ];
2259           });
2260
2261         var completeDomain = [
2262           d3.min(seriesDomains, function(d) { return d[0] }),
2263           d3.max(seriesDomains, function(d) { return d[1] })
2264         ]
2265
2266         lines.yDomain(completeDomain);
2267       } else {
2268         lines.yDomain(null);
2269       }
2270
2271
2272       dx  .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2273           .range([0, availableWidth])
2274           .clamp(true);
2275
2276       //------------------------------------------------------------
2277
2278
2279       var data = indexify(index.i, data);
2280
2281
2282       //------------------------------------------------------------
2283       // Setup containers and skeleton of chart
2284       var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2285       var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2286       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2287       var g = wrap.select('g');
2288
2289       gEnter.append('g').attr('class', 'nv-interactive');
2290       gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2291       gEnter.append('g').attr('class', 'nv-y nv-axis');
2292       gEnter.append('g').attr('class', 'nv-background');
2293       gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2294       gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2295       gEnter.append('g').attr('class', 'nv-legendWrap');
2296       gEnter.append('g').attr('class', 'nv-controlsWrap');
2297       
2298
2299       //------------------------------------------------------------
2300       // Legend
2301
2302       if (showLegend) {
2303         legend.width(availableWidth);
2304
2305         g.select('.nv-legendWrap')
2306             .datum(data)
2307             .call(legend);
2308
2309         if ( margin.top != legend.height()) {
2310           margin.top = legend.height();
2311           availableHeight = (height || parseInt(container.style('height')) || 400)
2312                              - margin.top - margin.bottom;
2313         }
2314
2315         g.select('.nv-legendWrap')
2316             .attr('transform', 'translate(0,' + (-margin.top) +')')
2317       }
2318
2319       //------------------------------------------------------------
2320
2321
2322       //------------------------------------------------------------
2323       // Controls
2324
2325       if (showControls) {
2326         var controlsData = [
2327           { key: 'Re-scale y-axis', disabled: !rescaleY }
2328         ];
2329
2330         controls.width(140).color(['#444', '#444', '#444']);
2331         g.select('.nv-controlsWrap')
2332             .datum(controlsData)
2333             .attr('transform', 'translate(0,' + (-margin.top) +')')
2334             .call(controls);
2335       }
2336
2337       //------------------------------------------------------------
2338
2339
2340       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2341
2342       if (rightAlignYAxis) {
2343           g.select(".nv-y.nv-axis")
2344               .attr("transform", "translate(" + availableWidth + ",0)");
2345       }
2346
2347       // Show error if series goes below 100%
2348       var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2349
2350       wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2351       if (tempDisabled.length) {
2352         wrap.append('text').attr('class', 'tempDisabled')
2353             .attr('x', availableWidth / 2)
2354             .attr('y', '-.71em')
2355             .style('text-anchor', 'end')
2356             .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2357       }
2358
2359       //------------------------------------------------------------
2360       // Main Chart Component(s)
2361       
2362       //------------------------------------------------------------
2363       //Set up interactive layer
2364       if (useInteractiveGuideline) {
2365         interactiveLayer
2366           .width(availableWidth)
2367           .height(availableHeight)
2368           .margin({left:margin.left,top:margin.top})
2369           .svgContainer(container)
2370           .xScale(x);
2371         wrap.select(".nv-interactive").call(interactiveLayer);
2372       }
2373
2374       gEnter.select('.nv-background')
2375         .append('rect');
2376
2377       g.select('.nv-background rect')
2378           .attr('width', availableWidth)
2379           .attr('height', availableHeight);
2380
2381       lines
2382         //.x(function(d) { return d.x })
2383         .y(function(d) { return d.display.y })
2384         .width(availableWidth)
2385         .height(availableHeight)
2386         .color(data.map(function(d,i) {
2387           return d.color || color(d, i);
2388         }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2389
2390
2391
2392       var linesWrap = g.select('.nv-linesWrap')
2393           .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
2394
2395       //d3.transition(linesWrap).call(lines);
2396       linesWrap.call(lines);
2397
2398       /*Handle average lines [AN-612] ----------------------------*/
2399
2400       //Store a series index number in the data array.
2401       data.forEach(function(d,i) {
2402             d.seriesIndex = i;
2403       });
2404
2405       var avgLineData = data.filter(function(d) {
2406           return !d.disabled && !!average(d);
2407       });
2408
2409       var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2410               .data(avgLineData, function(d) { return d.key; });
2411
2412       var getAvgLineY = function(d) {
2413           //If average lines go off the svg element, clamp them to the svg bounds.
2414           var yVal = y(average(d));
2415           if (yVal < 0) return 0;
2416           if (yVal > availableHeight) return availableHeight;
2417           return yVal;
2418       };
2419
2420       avgLines.enter()
2421               .append('line')
2422               .style('stroke-width',2)
2423               .style('stroke-dasharray','10,10')
2424               .style('stroke',function (d,i) {
2425                   return lines.color()(d,d.seriesIndex);
2426               })
2427               .attr('x1',0)
2428               .attr('x2',availableWidth)
2429               .attr('y1', getAvgLineY)
2430               .attr('y2', getAvgLineY);
2431
2432       avgLines
2433               .style('stroke-opacity',function(d){
2434                   //If average lines go offscreen, make them transparent
2435                   var yVal = y(average(d));
2436                   if (yVal < 0 || yVal > availableHeight) return 0;
2437                   return 1;
2438               })
2439               .attr('x1',0)
2440               .attr('x2',availableWidth)
2441               .attr('y1', getAvgLineY)
2442               .attr('y2', getAvgLineY);
2443
2444       avgLines.exit().remove();
2445
2446       //Create index line -----------------------------------------
2447
2448       var indexLine = linesWrap.selectAll('.nv-indexLine')
2449           .data([index]);
2450       indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2451           .attr('width', 3)
2452           .attr('x', -2)
2453           .attr('fill', 'red')
2454           .attr('fill-opacity', .5)
2455           .style("pointer-events","all")
2456           .call(indexDrag)
2457
2458       indexLine
2459           .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2460           .attr('height', availableHeight)
2461
2462       //------------------------------------------------------------
2463
2464
2465       //------------------------------------------------------------
2466       // Setup Axes
2467
2468       if (showXAxis) {
2469         xAxis
2470           .scale(x)
2471           //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
2472           .ticks( Math.min(data[0].values.length,availableWidth/70) )
2473           .tickSize(-availableHeight, 0);
2474
2475         g.select('.nv-x.nv-axis')
2476             .attr('transform', 'translate(0,' + y.range()[0] + ')');
2477         d3.transition(g.select('.nv-x.nv-axis'))
2478             .call(xAxis);
2479       }
2480
2481
2482       if (showYAxis) {
2483         yAxis
2484           .scale(y)
2485           .ticks( availableHeight / 36 )
2486           .tickSize( -availableWidth, 0);
2487
2488         d3.transition(g.select('.nv-y.nv-axis'))
2489             .call(yAxis);
2490       }
2491       //------------------------------------------------------------
2492
2493
2494       //============================================================
2495       // Event Handling/Dispatching (in chart's scope)
2496       //------------------------------------------------------------
2497
2498
2499       function updateZero() {
2500         indexLine
2501           .data([index]);
2502         
2503         //When dragging the index line, turn off line transitions.
2504         // Then turn them back on when done dragging.
2505         var oldDuration = chart.transitionDuration();
2506         chart.transitionDuration(0);
2507         chart.update();
2508         chart.transitionDuration(oldDuration);
2509       }
2510
2511       g.select('.nv-background rect')
2512           .on('click', function() {
2513             index.x = d3.mouse(this)[0];
2514             index.i = Math.round(dx.invert(index.x));
2515
2516             // update state and send stateChange with new index
2517             state.index = index.i;
2518             dispatch.stateChange(state);
2519
2520             updateZero();
2521           });
2522
2523       lines.dispatch.on('elementClick', function(e) {
2524         index.i = e.pointIndex;
2525         index.x = dx(index.i);
2526
2527         // update state and send stateChange with new index
2528         state.index = index.i;
2529         dispatch.stateChange(state);
2530
2531         updateZero();
2532       });
2533
2534       controls.dispatch.on('legendClick', function(d,i) { 
2535         d.disabled = !d.disabled;
2536         rescaleY = !d.disabled;
2537
2538         state.rescaleY = rescaleY;
2539         dispatch.stateChange(state);
2540         chart.update();
2541       });
2542
2543
2544       legend.dispatch.on('stateChange', function(newState) {
2545         state.disabled = newState.disabled; 
2546         dispatch.stateChange(state);
2547         chart.update();
2548       });
2549
2550       interactiveLayer.dispatch.on('elementMousemove', function(e) {
2551           lines.clearHighlights();
2552           var singlePoint, pointIndex, pointXLocation, allData = [];
2553           data
2554           .filter(function(series, i) { 
2555             series.seriesIndex = i;
2556             return !series.disabled; 
2557           })
2558           .forEach(function(series,i) {
2559               pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2560               lines.highlightPoint(i, pointIndex, true);
2561               var point = series.values[pointIndex];
2562               if (typeof point === 'undefined') return;
2563               if (typeof singlePoint === 'undefined') singlePoint = point;
2564               if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2565               allData.push({
2566                   key: series.key,
2567                   value: chart.y()(point, pointIndex),
2568                   color: color(series,series.seriesIndex)
2569               });
2570           });
2571
2572           var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
2573           interactiveLayer.tooltip
2574                   .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2575                   .chartContainer(that.parentNode)
2576                   .enabled(tooltips)
2577                   .valueFormatter(function(d,i) {
2578                      return yAxis.tickFormat()(d);
2579                   })
2580                   .data(
2581                       {
2582                         value: xValue,
2583                         series: allData
2584                       }
2585                   )();
2586
2587           interactiveLayer.renderGuideLine(pointXLocation);
2588
2589       });
2590
2591       interactiveLayer.dispatch.on("elementMouseout",function(e) {
2592           dispatch.tooltipHide();
2593           lines.clearHighlights();
2594       });
2595
2596       dispatch.on('tooltipShow', function(e) {
2597         if (tooltips) showTooltip(e, that.parentNode);
2598       });
2599
2600
2601       // Update chart from a state object passed to event handler
2602       dispatch.on('changeState', function(e) {
2603
2604         if (typeof e.disabled !== 'undefined') {
2605           data.forEach(function(series,i) {
2606             series.disabled = e.disabled[i];
2607           });
2608
2609           state.disabled = e.disabled;
2610         }
2611
2612
2613         if (typeof e.index !== 'undefined') {
2614           index.i = e.index;
2615           index.x = dx(index.i);
2616
2617           state.index = e.index;
2618
2619           indexLine
2620             .data([index]);
2621         }
2622
2623
2624         if (typeof e.rescaleY !== 'undefined') {
2625           rescaleY = e.rescaleY;
2626         }
2627
2628         chart.update();
2629       });
2630
2631       //============================================================
2632
2633     });
2634
2635     return chart;
2636   }
2637
2638
2639   //============================================================
2640   // Event Handling/Dispatching (out of chart's scope)
2641   //------------------------------------------------------------
2642
2643   lines.dispatch.on('elementMouseover.tooltip', function(e) {
2644     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
2645     dispatch.tooltipShow(e);
2646   });
2647
2648   lines.dispatch.on('elementMouseout.tooltip', function(e) {
2649     dispatch.tooltipHide(e);
2650   });
2651
2652   dispatch.on('tooltipHide', function() {
2653     if (tooltips) nv.tooltip.cleanup();
2654   });
2655
2656   //============================================================
2657
2658
2659   //============================================================
2660   // Expose Public Variables
2661   //------------------------------------------------------------
2662
2663   // expose chart's sub-components
2664   chart.dispatch = dispatch;
2665   chart.lines = lines;
2666   chart.legend = legend;
2667   chart.xAxis = xAxis;
2668   chart.yAxis = yAxis;
2669   chart.interactiveLayer = interactiveLayer;
2670
2671   d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi',  'id');
2672
2673   chart.options = nv.utils.optionsFunc.bind(chart);
2674   
2675   chart.margin = function(_) {
2676     if (!arguments.length) return margin;
2677     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2678     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2679     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2680     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2681     return chart;
2682   };
2683
2684   chart.width = function(_) {
2685     if (!arguments.length) return width;
2686     width = _;
2687     return chart;
2688   };
2689
2690   chart.height = function(_) {
2691     if (!arguments.length) return height;
2692     height = _;
2693     return chart;
2694   };
2695
2696   chart.color = function(_) {
2697     if (!arguments.length) return color;
2698     color = nv.utils.getColor(_);
2699     legend.color(color);
2700     return chart;
2701   };
2702
2703   chart.rescaleY = function(_) {
2704     if (!arguments.length) return rescaleY;
2705     rescaleY = _
2706     return rescaleY;
2707   };
2708
2709   chart.showControls = function(_) {
2710     if (!arguments.length) return showControls;
2711     showControls = _;
2712     return chart;
2713   };
2714
2715   chart.useInteractiveGuideline = function(_) {
2716     if(!arguments.length) return useInteractiveGuideline;
2717     useInteractiveGuideline = _;
2718     if (_ === true) {
2719        chart.interactive(false);
2720        chart.useVoronoi(false);
2721     }
2722     return chart;
2723   };
2724
2725   chart.showLegend = function(_) {
2726     if (!arguments.length) return showLegend;
2727     showLegend = _;
2728     return chart;
2729   };
2730
2731   chart.showXAxis = function(_) {
2732     if (!arguments.length) return showXAxis;
2733     showXAxis = _;
2734     return chart;
2735   };
2736
2737   chart.showYAxis = function(_) {
2738     if (!arguments.length) return showYAxis;
2739     showYAxis = _;
2740     return chart;
2741   };
2742
2743   chart.rightAlignYAxis = function(_) {
2744     if(!arguments.length) return rightAlignYAxis;
2745     rightAlignYAxis = _;
2746     yAxis.orient( (_) ? 'right' : 'left');
2747     return chart;
2748   };
2749
2750   chart.tooltips = function(_) {
2751     if (!arguments.length) return tooltips;
2752     tooltips = _;
2753     return chart;
2754   };
2755
2756   chart.tooltipContent = function(_) {
2757     if (!arguments.length) return tooltip;
2758     tooltip = _;
2759     return chart;
2760   };
2761
2762   chart.state = function(_) {
2763     if (!arguments.length) return state;
2764     state = _;
2765     return chart;
2766   };
2767
2768   chart.defaultState = function(_) {
2769     if (!arguments.length) return defaultState;
2770     defaultState = _;
2771     return chart;
2772   };
2773
2774   chart.noData = function(_) {
2775     if (!arguments.length) return noData;
2776     noData = _;
2777     return chart;
2778   };
2779
2780   chart.average = function(_) {
2781      if(!arguments.length) return average;
2782      average = _;
2783      return chart;
2784   };
2785
2786   chart.transitionDuration = function(_) {
2787     if (!arguments.length) return transitionDuration;
2788     transitionDuration = _;
2789     return chart;
2790   };
2791
2792   //============================================================
2793
2794
2795   //============================================================
2796   // Functions
2797   //------------------------------------------------------------
2798
2799   /* Normalize the data according to an index point. */
2800   function indexify(idx, data) {
2801     return data.map(function(line, i) {
2802       if (!line.values) {
2803          return line;
2804       }
2805       var v = lines.y()(line.values[idx], idx);
2806
2807       //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2808       if (v < -.95) {
2809         //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2810         line.tempDisabled = true;
2811         return line;
2812       }
2813
2814       line.tempDisabled = false;
2815
2816       line.values = line.values.map(function(point, pointIndex) {
2817         point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
2818         return point;
2819       })
2820
2821       return line;
2822     })
2823   }
2824
2825   //============================================================
2826
2827
2828   return chart;
2829 }
2830 //TODO: consider deprecating by adding necessary features to multiBar model
2831 nv.models.discreteBar = function() {
2832   "use strict";
2833   //============================================================
2834   // Public Variables with Default Settings
2835   //------------------------------------------------------------
2836
2837   var margin = {top: 0, right: 0, bottom: 0, left: 0}
2838     , width = 960
2839     , height = 500
2840     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2841     , x = d3.scale.ordinal()
2842     , y = d3.scale.linear()
2843     , getX = function(d) { return d.x }
2844     , getY = function(d) { return d.y }
2845     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2846     , color = nv.utils.defaultColor()
2847     , showValues = false
2848     , valueFormat = d3.format(',.2f')
2849     , xDomain
2850     , yDomain
2851     , xRange
2852     , yRange
2853     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2854     , rectClass = 'discreteBar'
2855     ;
2856
2857   //============================================================
2858
2859
2860   //============================================================
2861   // Private Variables
2862   //------------------------------------------------------------
2863
2864   var x0, y0;
2865
2866   //============================================================
2867
2868
2869   function chart(selection) {
2870     selection.each(function(data) {
2871       var availableWidth = width - margin.left - margin.right,
2872           availableHeight = height - margin.top - margin.bottom,
2873           container = d3.select(this);
2874
2875
2876       //add series index to each data point for reference
2877       data = data.map(function(series, i) {
2878         series.values = series.values.map(function(point) {
2879           point.series = i;
2880           return point;
2881         });
2882         return series;
2883       });
2884
2885
2886       //------------------------------------------------------------
2887       // Setup Scales
2888
2889       // remap and flatten the data for use in calculating the scales' domains
2890       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2891             data.map(function(d) {
2892               return d.values.map(function(d,i) {
2893                 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2894               })
2895             });
2896
2897       x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2898           .rangeBands(xRange || [0, availableWidth], .1);
2899
2900       y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
2901
2902
2903       // If showValues, pad the Y axis range to account for label height
2904       if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
2905       else y.range(yRange || [availableHeight, 0]);
2906
2907       //store old scales if they exist
2908       x0 = x0 || x;
2909       y0 = y0 || y.copy().range([y(0),y(0)]);
2910
2911       //------------------------------------------------------------
2912
2913
2914       //------------------------------------------------------------
2915       // Setup containers and skeleton of chart
2916
2917       var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
2918       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
2919       var gEnter = wrapEnter.append('g');
2920       var g = wrap.select('g');
2921
2922       gEnter.append('g').attr('class', 'nv-groups');
2923
2924       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2925
2926       //------------------------------------------------------------
2927
2928
2929
2930       //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
2931       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
2932           .data(function(d) { return d }, function(d) { return d.key });
2933       groups.enter().append('g')
2934           .style('stroke-opacity', 1e-6)
2935           .style('fill-opacity', 1e-6);
2936       groups.exit()
2937           .transition()
2938           .style('stroke-opacity', 1e-6)
2939           .style('fill-opacity', 1e-6)
2940           .remove();
2941       groups
2942           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
2943           .classed('hover', function(d) { return d.hover });
2944       groups
2945           .transition()
2946           .style('stroke-opacity', 1)
2947           .style('fill-opacity', .75);
2948
2949
2950       var bars = groups.selectAll('g.nv-bar')
2951           .data(function(d) { return d.values });
2952
2953       bars.exit().remove();
2954
2955
2956       var barsEnter = bars.enter().append('g')
2957           .attr('transform', function(d,i,j) {
2958               return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' 
2959           })
2960           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
2961             d3.select(this).classed('hover', true);
2962             dispatch.elementMouseover({
2963               value: getY(d,i),
2964               point: d,
2965               series: data[d.series],
2966               pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
2967               pointIndex: i,
2968               seriesIndex: d.series,
2969               e: d3.event
2970             });
2971           })
2972           .on('mouseout', function(d,i) {
2973             d3.select(this).classed('hover', false);
2974             dispatch.elementMouseout({
2975               value: getY(d,i),
2976               point: d,
2977               series: data[d.series],
2978               pointIndex: i,
2979               seriesIndex: d.series,
2980               e: d3.event
2981             });
2982           })
2983           .on('click', function(d,i) {
2984             dispatch.elementClick({
2985               value: getY(d,i),
2986               point: d,
2987               series: data[d.series],
2988               pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
2989               pointIndex: i,
2990               seriesIndex: d.series,
2991               e: d3.event
2992             });
2993             d3.event.stopPropagation();
2994           })
2995           .on('dblclick', function(d,i) {
2996             dispatch.elementDblClick({
2997               value: getY(d,i),
2998               point: d,
2999               series: data[d.series],
3000               pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3001               pointIndex: i,
3002               seriesIndex: d.series,
3003               e: d3.event
3004             });
3005             d3.event.stopPropagation();
3006           });
3007
3008       barsEnter.append('rect')
3009           .attr('height', 0)
3010           .attr('width', x.rangeBand() * .9 / data.length )
3011
3012       if (showValues) {
3013         barsEnter.append('text')
3014           .attr('text-anchor', 'middle')
3015           ;
3016
3017         bars.select('text')
3018           .text(function(d,i) { return valueFormat(getY(d,i)) })
3019           .transition()
3020           .attr('x', x.rangeBand() * .9 / 2)
3021           .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3022           
3023           ;
3024       } else {
3025         bars.selectAll('text').remove();
3026       }
3027
3028       bars
3029           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3030           .style('fill', function(d,i) { return d.color || color(d,i) })
3031           .style('stroke', function(d,i) { return d.color || color(d,i) })
3032         .select('rect')
3033           .attr('class', rectClass)
3034           .transition()
3035           .attr('width', x.rangeBand() * .9 / data.length);
3036       bars.transition()
3037         //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3038           .attr('transform', function(d,i) {
3039             var left = x(getX(d,i)) + x.rangeBand() * .05,
3040                 top = getY(d,i) < 0 ?
3041                         y(0) :
3042                         y(0) - y(getY(d,i)) < 1 ?
3043                           y(0) - 1 : //make 1 px positive bars show up above y=0
3044                           y(getY(d,i));
3045
3046               return 'translate(' + left + ', ' + top + ')'
3047           })
3048         .select('rect')
3049           .attr('height', function(d,i) {
3050             return  Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3051           });
3052
3053
3054       //store old scales for use in transitions on update
3055       x0 = x.copy();
3056       y0 = y.copy();
3057
3058     });
3059
3060     return chart;
3061   }
3062
3063
3064   //============================================================
3065   // Expose Public Variables
3066   //------------------------------------------------------------
3067
3068   chart.dispatch = dispatch;
3069
3070   chart.options = nv.utils.optionsFunc.bind(chart);
3071   
3072   chart.x = function(_) {
3073     if (!arguments.length) return getX;
3074     getX = _;
3075     return chart;
3076   };
3077
3078   chart.y = function(_) {
3079     if (!arguments.length) return getY;
3080     getY = _;
3081     return chart;
3082   };
3083
3084   chart.margin = function(_) {
3085     if (!arguments.length) return margin;
3086     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3087     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3088     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3089     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3090     return chart;
3091   };
3092
3093   chart.width = function(_) {
3094     if (!arguments.length) return width;
3095     width = _;
3096     return chart;
3097   };
3098
3099   chart.height = function(_) {
3100     if (!arguments.length) return height;
3101     height = _;
3102     return chart;
3103   };
3104
3105   chart.xScale = function(_) {
3106     if (!arguments.length) return x;
3107     x = _;
3108     return chart;
3109   };
3110
3111   chart.yScale = function(_) {
3112     if (!arguments.length) return y;
3113     y = _;
3114     return chart;
3115   };
3116
3117   chart.xDomain = function(_) {
3118     if (!arguments.length) return xDomain;
3119     xDomain = _;
3120     return chart;
3121   };
3122
3123   chart.yDomain = function(_) {
3124     if (!arguments.length) return yDomain;
3125     yDomain = _;
3126     return chart;
3127   };
3128
3129   chart.xRange = function(_) {
3130     if (!arguments.length) return xRange;
3131     xRange = _;
3132     return chart;
3133   };
3134
3135   chart.yRange = function(_) {
3136     if (!arguments.length) return yRange;
3137     yRange = _;
3138     return chart;
3139   };
3140
3141   chart.forceY = function(_) {
3142     if (!arguments.length) return forceY;
3143     forceY = _;
3144     return chart;
3145   };
3146
3147   chart.color = function(_) {
3148     if (!arguments.length) return color;
3149     color = nv.utils.getColor(_);
3150     return chart;
3151   };
3152
3153   chart.id = function(_) {
3154     if (!arguments.length) return id;
3155     id = _;
3156     return chart;
3157   };
3158
3159   chart.showValues = function(_) {
3160     if (!arguments.length) return showValues;
3161     showValues = _;
3162     return chart;
3163   };
3164
3165   chart.valueFormat= function(_) {
3166     if (!arguments.length) return valueFormat;
3167     valueFormat = _;
3168     return chart;
3169   };
3170
3171   chart.rectClass= function(_) {
3172     if (!arguments.length) return rectClass;
3173     rectClass = _;
3174     return chart;
3175   };
3176   //============================================================
3177
3178
3179   return chart;
3180 }
3181
3182 nv.models.discreteBarChart = function() {
3183   "use strict";
3184   //============================================================
3185   // Public Variables with Default Settings
3186   //------------------------------------------------------------
3187
3188   var discretebar = nv.models.discreteBar()
3189     , xAxis = nv.models.axis()
3190     , yAxis = nv.models.axis()
3191     ;
3192
3193   var margin = {top: 15, right: 10, bottom: 50, left: 60}
3194     , width = null
3195     , height = null
3196     , color = nv.utils.getColor()
3197     , showXAxis = true
3198     , showYAxis = true
3199     , rightAlignYAxis = false
3200     , staggerLabels = false
3201     , tooltips = true
3202     , tooltip = function(key, x, y, e, graph) {
3203         return '<h3>' + x + '</h3>' +
3204                '<p>' +  y + '</p>'
3205       }
3206     , x
3207     , y
3208     , noData = "No Data Available."
3209     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
3210     , transitionDuration = 250
3211     ;
3212
3213   xAxis
3214     .orient('bottom')
3215     .highlightZero(false)
3216     .showMaxMin(false)
3217     .tickFormat(function(d) { return d })
3218     ;
3219   yAxis
3220     .orient((rightAlignYAxis) ? 'right' : 'left')
3221     .tickFormat(d3.format(',.1f'))
3222     ;
3223
3224   //============================================================
3225
3226
3227   //============================================================
3228   // Private Variables
3229   //------------------------------------------------------------
3230
3231   var showTooltip = function(e, offsetElement) {
3232     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3233         top = e.pos[1] + ( offsetElement.offsetTop || 0),
3234         x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3235         y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3236         content = tooltip(e.series.key, x, y, e, chart);
3237
3238     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3239   };
3240
3241   //============================================================
3242
3243
3244   function chart(selection) {
3245     selection.each(function(data) {
3246       var container = d3.select(this),
3247           that = this;
3248
3249       var availableWidth = (width  || parseInt(container.style('width')) || 960)
3250                              - margin.left - margin.right,
3251           availableHeight = (height || parseInt(container.style('height')) || 400)
3252                              - margin.top - margin.bottom;
3253
3254
3255       chart.update = function() { 
3256         dispatch.beforeUpdate(); 
3257         container.transition().duration(transitionDuration).call(chart); 
3258       };
3259       chart.container = this;
3260
3261
3262       //------------------------------------------------------------
3263       // Display No Data message if there's nothing to show.
3264
3265       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3266         var noDataText = container.selectAll('.nv-noData').data([noData]);
3267
3268         noDataText.enter().append('text')
3269           .attr('class', 'nvd3 nv-noData')
3270           .attr('dy', '-.7em')
3271           .style('text-anchor', 'middle');
3272
3273         noDataText
3274           .attr('x', margin.left + availableWidth / 2)
3275           .attr('y', margin.top + availableHeight / 2)
3276           .text(function(d) { return d });
3277
3278         return chart;
3279       } else {
3280         container.selectAll('.nv-noData').remove();
3281       }
3282
3283       //------------------------------------------------------------
3284
3285
3286       //------------------------------------------------------------
3287       // Setup Scales
3288
3289       x = discretebar.xScale();
3290       y = discretebar.yScale().clamp(true);
3291
3292       //------------------------------------------------------------
3293
3294
3295       //------------------------------------------------------------
3296       // Setup containers and skeleton of chart
3297
3298       var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3299       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3300       var defsEnter = gEnter.append('defs');
3301       var g = wrap.select('g');
3302
3303       gEnter.append('g').attr('class', 'nv-x nv-axis');
3304       gEnter.append('g').attr('class', 'nv-y nv-axis');
3305       gEnter.append('g').attr('class', 'nv-barsWrap');
3306
3307       g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3308
3309       if (rightAlignYAxis) {
3310           g.select(".nv-y.nv-axis")
3311               .attr("transform", "translate(" + availableWidth + ",0)");
3312       }
3313
3314       //------------------------------------------------------------
3315
3316
3317       //------------------------------------------------------------
3318       // Main Chart Component(s)
3319
3320       discretebar
3321         .width(availableWidth)
3322         .height(availableHeight);
3323
3324
3325       var barsWrap = g.select('.nv-barsWrap')
3326           .datum(data.filter(function(d) { return !d.disabled }))
3327
3328       barsWrap.transition().call(discretebar);
3329
3330       //------------------------------------------------------------
3331
3332
3333
3334       defsEnter.append('clipPath')
3335           .attr('id', 'nv-x-label-clip-' + discretebar.id())
3336         .append('rect');
3337
3338       g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3339           .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3340           .attr('height', 16)
3341           .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3342
3343
3344       //------------------------------------------------------------
3345       // Setup Axes
3346
3347       if (showXAxis) {
3348           xAxis
3349             .scale(x)
3350             .ticks( availableWidth / 100 )
3351             .tickSize(-availableHeight, 0);
3352
3353           g.select('.nv-x.nv-axis')
3354               .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3355           //d3.transition(g.select('.nv-x.nv-axis'))
3356           g.select('.nv-x.nv-axis').transition()
3357               .call(xAxis);
3358
3359
3360           var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3361
3362           if (staggerLabels) {
3363             xTicks
3364                 .selectAll('text')
3365                 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3366           }
3367       }
3368
3369       if (showYAxis) {
3370           yAxis
3371             .scale(y)
3372             .ticks( availableHeight / 36 )
3373             .tickSize( -availableWidth, 0);
3374
3375           g.select('.nv-y.nv-axis').transition()
3376               .call(yAxis);
3377       }
3378
3379       //------------------------------------------------------------
3380
3381
3382       //============================================================
3383       // Event Handling/Dispatching (in chart's scope)
3384       //------------------------------------------------------------
3385
3386       dispatch.on('tooltipShow', function(e) {
3387         if (tooltips) showTooltip(e, that.parentNode);
3388       });
3389
3390       //============================================================
3391
3392
3393     });
3394
3395     return chart;
3396   }
3397
3398   //============================================================
3399   // Event Handling/Dispatching (out of chart's scope)
3400   //------------------------------------------------------------
3401
3402   discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3403     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
3404     dispatch.tooltipShow(e);
3405   });
3406
3407   discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3408     dispatch.tooltipHide(e);
3409   });
3410
3411   dispatch.on('tooltipHide', function() {
3412     if (tooltips) nv.tooltip.cleanup();
3413   });
3414
3415   //============================================================
3416
3417
3418   //============================================================
3419   // Expose Public Variables
3420   //------------------------------------------------------------
3421
3422   // expose chart's sub-components
3423   chart.dispatch = dispatch;
3424   chart.discretebar = discretebar;
3425   chart.xAxis = xAxis;
3426   chart.yAxis = yAxis;
3427
3428   d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3429
3430   chart.options = nv.utils.optionsFunc.bind(chart);
3431   
3432   chart.margin = function(_) {
3433     if (!arguments.length) return margin;
3434     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3435     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3436     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3437     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3438     return chart;
3439   };
3440
3441   chart.width = function(_) {
3442     if (!arguments.length) return width;
3443     width = _;
3444     return chart;
3445   };
3446
3447   chart.height = function(_) {
3448     if (!arguments.length) return height;
3449     height = _;
3450     return chart;
3451   };
3452
3453   chart.color = function(_) {
3454     if (!arguments.length) return color;
3455     color = nv.utils.getColor(_);
3456     discretebar.color(color);
3457     return chart;
3458   };
3459
3460   chart.showXAxis = function(_) {
3461     if (!arguments.length) return showXAxis;
3462     showXAxis = _;
3463     return chart;
3464   };
3465
3466   chart.showYAxis = function(_) {
3467     if (!arguments.length) return showYAxis;
3468     showYAxis = _;
3469     return chart;
3470   };
3471
3472   chart.rightAlignYAxis = function(_) {
3473     if(!arguments.length) return rightAlignYAxis;
3474     rightAlignYAxis = _;
3475     yAxis.orient( (_) ? 'right' : 'left');
3476     return chart;
3477   };
3478
3479   chart.staggerLabels = function(_) {
3480     if (!arguments.length) return staggerLabels;
3481     staggerLabels = _;
3482     return chart;
3483   };
3484
3485   chart.tooltips = function(_) {
3486     if (!arguments.length) return tooltips;
3487     tooltips = _;
3488     return chart;
3489   };
3490
3491   chart.tooltipContent = function(_) {
3492     if (!arguments.length) return tooltip;
3493     tooltip = _;
3494     return chart;
3495   };
3496
3497   chart.noData = function(_) {
3498     if (!arguments.length) return noData;
3499     noData = _;
3500     return chart;
3501   };
3502
3503   chart.transitionDuration = function(_) {
3504     if (!arguments.length) return transitionDuration;
3505     transitionDuration = _;
3506     return chart;
3507   };
3508
3509   //============================================================
3510
3511
3512   return chart;
3513 }
3514
3515 nv.models.distribution = function() {
3516   "use strict";
3517   //============================================================
3518   // Public Variables with Default Settings
3519   //------------------------------------------------------------
3520
3521   var margin = {top: 0, right: 0, bottom: 0, left: 0}
3522     , width = 400 //technically width or height depending on x or y....
3523     , size = 8
3524     , axis = 'x' // 'x' or 'y'... horizontal or vertical
3525     , getData = function(d) { return d[axis] }  // defaults d.x or d.y
3526     , color = nv.utils.defaultColor()
3527     , scale = d3.scale.linear()
3528     , domain
3529     ;
3530
3531   //============================================================
3532
3533
3534   //============================================================
3535   // Private Variables
3536   //------------------------------------------------------------
3537
3538   var scale0;
3539
3540   //============================================================
3541
3542
3543   function chart(selection) {
3544     selection.each(function(data) {
3545       var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3546           naxis = axis == 'x' ? 'y' : 'x',
3547           container = d3.select(this);
3548
3549
3550       //------------------------------------------------------------
3551       // Setup Scales
3552
3553       scale0 = scale0 || scale;
3554
3555       //------------------------------------------------------------
3556
3557
3558       //------------------------------------------------------------
3559       // Setup containers and skeleton of chart
3560
3561       var wrap = container.selectAll('g.nv-distribution').data([data]);
3562       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3563       var gEnter = wrapEnter.append('g');
3564       var g = wrap.select('g');
3565
3566       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
3567
3568       //------------------------------------------------------------
3569
3570
3571       var distWrap = g.selectAll('g.nv-dist')
3572           .data(function(d) { return d }, function(d) { return d.key });
3573
3574       distWrap.enter().append('g');
3575       distWrap
3576           .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
3577           .style('stroke', function(d,i) { return color(d, i) });
3578
3579       var dist = distWrap.selectAll('line.nv-dist' + axis)
3580           .data(function(d) { return d.values })
3581       dist.enter().append('line')
3582           .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
3583           .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
3584       distWrap.exit().selectAll('line.nv-dist' + axis)
3585           .transition()
3586           .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3587           .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3588           .style('stroke-opacity', 0)
3589           .remove();
3590       dist
3591           .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
3592           .attr(naxis + '1', 0)
3593           .attr(naxis + '2', size);
3594       dist
3595           .transition()
3596           .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3597           .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3598
3599
3600       scale0 = scale.copy();
3601
3602     });
3603
3604     return chart;
3605   }
3606
3607
3608   //============================================================
3609   // Expose Public Variables
3610   //------------------------------------------------------------
3611   chart.options = nv.utils.optionsFunc.bind(chart);
3612   
3613   chart.margin = function(_) {
3614     if (!arguments.length) return margin;
3615     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3616     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3617     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3618     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3619     return chart;
3620   };
3621
3622   chart.width = function(_) {
3623     if (!arguments.length) return width;
3624     width = _;
3625     return chart;
3626   };
3627
3628   chart.axis = function(_) {
3629     if (!arguments.length) return axis;
3630     axis = _;
3631     return chart;
3632   };
3633
3634   chart.size = function(_) {
3635     if (!arguments.length) return size;
3636     size = _;
3637     return chart;
3638   };
3639
3640   chart.getData = function(_) {
3641     if (!arguments.length) return getData;
3642     getData = d3.functor(_);
3643     return chart;
3644   };
3645
3646   chart.scale = function(_) {
3647     if (!arguments.length) return scale;
3648     scale = _;
3649     return chart;
3650   };
3651
3652   chart.color = function(_) {
3653     if (!arguments.length) return color;
3654     color = nv.utils.getColor(_);
3655     return chart;
3656   };
3657   //============================================================
3658
3659
3660   return chart;
3661 }
3662 //TODO: consider deprecating and using multibar with single series for this
3663 nv.models.historicalBar = function() {
3664   "use strict";
3665   //============================================================
3666   // Public Variables with Default Settings
3667   //------------------------------------------------------------
3668
3669   var margin = {top: 0, right: 0, bottom: 0, left: 0}
3670     , width = 960
3671     , height = 500
3672     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3673     , x = d3.scale.linear()
3674     , y = d3.scale.linear()
3675     , getX = function(d) { return d.x }
3676     , getY = function(d) { return d.y }
3677     , forceX = []
3678     , forceY = [0]
3679     , padData = false
3680     , clipEdge = true
3681     , color = nv.utils.defaultColor()
3682     , xDomain
3683     , yDomain
3684     , xRange
3685     , yRange
3686     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
3687     , interactive = true
3688     ;
3689
3690   //============================================================
3691
3692
3693   function chart(selection) {
3694     selection.each(function(data) {
3695       var availableWidth = width - margin.left - margin.right,
3696           availableHeight = height - margin.top - margin.bottom,
3697           container = d3.select(this);
3698
3699
3700       //------------------------------------------------------------
3701       // Setup Scales
3702
3703       x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
3704
3705       if (padData)
3706         x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
3707       else
3708         x.range(xRange || [0, availableWidth]);
3709
3710       y   .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
3711           .range(yRange || [availableHeight, 0]);
3712
3713       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
3714
3715       if (x.domain()[0] === x.domain()[1])
3716         x.domain()[0] ?
3717             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3718           : x.domain([-1,1]);
3719
3720       if (y.domain()[0] === y.domain()[1])
3721         y.domain()[0] ?
3722             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3723           : y.domain([-1,1]);
3724
3725       //------------------------------------------------------------
3726
3727
3728       //------------------------------------------------------------
3729       // Setup containers and skeleton of chart
3730
3731       var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
3732       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
3733       var defsEnter = wrapEnter.append('defs');
3734       var gEnter = wrapEnter.append('g');
3735       var g = wrap.select('g');
3736
3737       gEnter.append('g').attr('class', 'nv-bars');
3738
3739       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3740
3741       //------------------------------------------------------------
3742
3743
3744       container
3745           .on('click', function(d,i) {
3746             dispatch.chartClick({
3747                 data: d,
3748                 index: i,
3749                 pos: d3.event,
3750                 id: id
3751             });
3752           });
3753
3754
3755       defsEnter.append('clipPath')
3756           .attr('id', 'nv-chart-clip-path-' + id)
3757         .append('rect');
3758
3759       wrap.select('#nv-chart-clip-path-' + id + ' rect')
3760           .attr('width', availableWidth)
3761           .attr('height', availableHeight);
3762
3763       g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3764
3765
3766
3767       var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3768           .data(function(d) { return d }, function(d,i) {return getX(d,i)});
3769
3770       bars.exit().remove();
3771
3772
3773       var barsEnter = bars.enter().append('rect')
3774           //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3775           .attr('x', 0 )
3776           .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
3777           .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
3778           .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) 
3779           .on('mouseover', function(d,i) {
3780             if (!interactive) return;
3781             d3.select(this).classed('hover', true);
3782             dispatch.elementMouseover({
3783                 point: d,
3784                 series: data[0],
3785                 pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3786                 pointIndex: i,
3787                 seriesIndex: 0,
3788                 e: d3.event
3789             });
3790
3791           })
3792           .on('mouseout', function(d,i) {
3793                 if (!interactive) return;
3794                 d3.select(this).classed('hover', false);
3795                 dispatch.elementMouseout({
3796                     point: d,
3797                     series: data[0],
3798                     pointIndex: i,
3799                     seriesIndex: 0,
3800                     e: d3.event
3801                 });
3802           })
3803           .on('click', function(d,i) {
3804                 if (!interactive) return;
3805                 dispatch.elementClick({
3806                     //label: d[label],
3807                     value: getY(d,i),
3808                     data: d,
3809                     index: i,
3810                     pos: [x(getX(d,i)), y(getY(d,i))],
3811                     e: d3.event,
3812                     id: id
3813                 });
3814               d3.event.stopPropagation();
3815           })
3816           .on('dblclick', function(d,i) {
3817               if (!interactive) return;
3818               dispatch.elementDblClick({
3819                   //label: d[label],
3820                   value: getY(d,i),
3821                   data: d,
3822                   index: i,
3823                   pos: [x(getX(d,i)), y(getY(d,i))],
3824                   e: d3.event,
3825                   id: id
3826               });
3827               d3.event.stopPropagation();
3828           });
3829
3830       bars
3831           .attr('fill', function(d,i) { return color(d, i); })
3832           .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3833           .transition()
3834           .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) 
3835            //TODO: better width calculations that don't assume always uniform data spacing;w
3836           .attr('width', (availableWidth / data[0].values.length) * .9 );
3837
3838
3839       bars.transition()
3840           .attr('y', function(d,i) {
3841             var rval = getY(d,i) < 0 ?
3842                     y(0) :
3843                     y(0) - y(getY(d,i)) < 1 ?
3844                       y(0) - 1 :
3845                       y(getY(d,i));
3846             return nv.utils.NaNtoZero(rval);
3847           })
3848           .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
3849
3850     });
3851
3852     return chart;
3853   }
3854
3855   //Create methods to allow outside functions to highlight a specific bar.
3856   chart.highlightPoint = function(pointIndex, isHoverOver) {
3857       d3.select(".nv-historicalBar-" + id)
3858         .select(".nv-bars .nv-bar-0-" + pointIndex)
3859               .classed("hover", isHoverOver)
3860                ;
3861   };
3862
3863   chart.clearHighlights = function() {
3864       d3.select(".nv-historicalBar-" + id)
3865         .select(".nv-bars .nv-bar.hover")
3866               .classed("hover", false)
3867                ;
3868   };
3869   //============================================================
3870   // Expose Public Variables
3871   //------------------------------------------------------------
3872
3873   chart.dispatch = dispatch;
3874
3875   chart.options = nv.utils.optionsFunc.bind(chart);
3876   
3877   chart.x = function(_) {
3878     if (!arguments.length) return getX;
3879     getX = _;
3880     return chart;
3881   };
3882
3883   chart.y = function(_) {
3884     if (!arguments.length) return getY;
3885     getY = _;
3886     return chart;
3887   };
3888
3889   chart.margin = function(_) {
3890     if (!arguments.length) return margin;
3891     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3892     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3893     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3894     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3895     return chart;
3896   };
3897
3898   chart.width = function(_) {
3899     if (!arguments.length) return width;
3900     width = _;
3901     return chart;
3902   };
3903
3904   chart.height = function(_) {
3905     if (!arguments.length) return height;
3906     height = _;
3907     return chart;
3908   };
3909
3910   chart.xScale = function(_) {
3911     if (!arguments.length) return x;
3912     x = _;
3913     return chart;
3914   };
3915
3916   chart.yScale = function(_) {
3917     if (!arguments.length) return y;
3918     y = _;
3919     return chart;
3920   };
3921
3922   chart.xDomain = function(_) {
3923     if (!arguments.length) return xDomain;
3924     xDomain = _;
3925     return chart;
3926   };
3927
3928   chart.yDomain = function(_) {
3929     if (!arguments.length) return yDomain;
3930     yDomain = _;
3931     return chart;
3932   };
3933
3934   chart.xRange = function(_) {
3935     if (!arguments.length) return xRange;
3936     xRange = _;
3937     return chart;
3938   };
3939
3940   chart.yRange = function(_) {
3941     if (!arguments.length) return yRange;
3942     yRange = _;
3943     return chart;
3944   };
3945
3946   chart.forceX = function(_) {
3947     if (!arguments.length) return forceX;
3948     forceX = _;
3949     return chart;
3950   };
3951
3952   chart.forceY = function(_) {
3953     if (!arguments.length) return forceY;
3954     forceY = _;
3955     return chart;
3956   };
3957
3958   chart.padData = function(_) {
3959     if (!arguments.length) return padData;
3960     padData = _;
3961     return chart;
3962   };
3963
3964   chart.clipEdge = function(_) {
3965     if (!arguments.length) return clipEdge;
3966     clipEdge = _;
3967     return chart;
3968   };
3969
3970   chart.color = function(_) {
3971     if (!arguments.length) return color;
3972     color = nv.utils.getColor(_);
3973     return chart;
3974   };
3975
3976   chart.id = function(_) {
3977     if (!arguments.length) return id;
3978     id = _;
3979     return chart;
3980   };
3981
3982   chart.interactive = function(_) {
3983     if(!arguments.length) return interactive;
3984     interactive = false;
3985     return chart;
3986   };
3987
3988   //============================================================
3989
3990
3991   return chart;
3992 }
3993
3994 nv.models.historicalBarChart = function() {
3995   "use strict";
3996   //============================================================
3997   // Public Variables with Default Settings
3998   //------------------------------------------------------------
3999
4000   var bars = nv.models.historicalBar()
4001     , xAxis = nv.models.axis()
4002     , yAxis = nv.models.axis()
4003     , legend = nv.models.legend()
4004     ;
4005
4006
4007   var margin = {top: 30, right: 90, bottom: 50, left: 90}
4008     , color = nv.utils.defaultColor()
4009     , width = null
4010     , height = null
4011     , showLegend = false
4012     , showXAxis = true
4013     , showYAxis = true
4014     , rightAlignYAxis = false
4015     , tooltips = true
4016     , tooltip = function(key, x, y, e, graph) {
4017         return '<h3>' + key + '</h3>' +
4018                '<p>' +  y + ' at ' + x + '</p>'
4019       }
4020     , x
4021     , y
4022     , state = {}
4023     , defaultState = null
4024     , noData = 'No Data Available.'
4025     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4026     , transitionDuration = 250
4027     ;
4028
4029   xAxis
4030     .orient('bottom')
4031     .tickPadding(7)
4032     ;
4033   yAxis
4034     .orient( (rightAlignYAxis) ? 'right' : 'left')
4035     ;
4036
4037   //============================================================
4038
4039
4040   //============================================================
4041   // Private Variables
4042   //------------------------------------------------------------
4043
4044   var showTooltip = function(e, offsetElement) {
4045
4046     // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4047     if (offsetElement) {
4048       var svg = d3.select(offsetElement).select('svg');
4049       var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4050       if (viewBox) {
4051         viewBox = viewBox.split(' ');
4052         var ratio = parseInt(svg.style('width')) / viewBox[2];
4053         e.pos[0] = e.pos[0] * ratio;
4054         e.pos[1] = e.pos[1] * ratio;
4055       }
4056     }
4057
4058     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4059         top = e.pos[1] + ( offsetElement.offsetTop || 0),
4060         x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4061         y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4062         content = tooltip(e.series.key, x, y, e, chart);
4063
4064     nv.tooltip.show([left, top], content, null, null, offsetElement);
4065   };
4066
4067   //============================================================
4068
4069
4070   function chart(selection) {
4071     selection.each(function(data) {
4072       var container = d3.select(this),
4073           that = this;
4074
4075       var availableWidth = (width  || parseInt(container.style('width')) || 960)
4076                              - margin.left - margin.right,
4077           availableHeight = (height || parseInt(container.style('height')) || 400)
4078                              - margin.top - margin.bottom;
4079
4080
4081       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4082       chart.container = this;
4083
4084       //set state.disabled
4085       state.disabled = data.map(function(d) { return !!d.disabled });
4086
4087       if (!defaultState) {
4088         var key;
4089         defaultState = {};
4090         for (key in state) {
4091           if (state[key] instanceof Array)
4092             defaultState[key] = state[key].slice(0);
4093           else
4094             defaultState[key] = state[key];
4095         }
4096       }
4097
4098       //------------------------------------------------------------
4099       // Display noData message if there's nothing to show.
4100
4101       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4102         var noDataText = container.selectAll('.nv-noData').data([noData]);
4103
4104         noDataText.enter().append('text')
4105           .attr('class', 'nvd3 nv-noData')
4106           .attr('dy', '-.7em')
4107           .style('text-anchor', 'middle');
4108
4109         noDataText
4110           .attr('x', margin.left + availableWidth / 2)
4111           .attr('y', margin.top + availableHeight / 2)
4112           .text(function(d) { return d });
4113
4114         return chart;
4115       } else {
4116         container.selectAll('.nv-noData').remove();
4117       }
4118
4119       //------------------------------------------------------------
4120
4121
4122       //------------------------------------------------------------
4123       // Setup Scales
4124
4125       x = bars.xScale();
4126       y = bars.yScale();
4127
4128       //------------------------------------------------------------
4129
4130
4131       //------------------------------------------------------------
4132       // Setup containers and skeleton of chart
4133
4134       var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4135       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4136       var g = wrap.select('g');
4137
4138       gEnter.append('g').attr('class', 'nv-x nv-axis');
4139       gEnter.append('g').attr('class', 'nv-y nv-axis');
4140       gEnter.append('g').attr('class', 'nv-barsWrap');
4141       gEnter.append('g').attr('class', 'nv-legendWrap');
4142
4143       //------------------------------------------------------------
4144
4145
4146       //------------------------------------------------------------
4147       // Legend
4148
4149       if (showLegend) {
4150         legend.width(availableWidth);
4151
4152         g.select('.nv-legendWrap')
4153             .datum(data)
4154             .call(legend);
4155
4156         if ( margin.top != legend.height()) {
4157           margin.top = legend.height();
4158           availableHeight = (height || parseInt(container.style('height')) || 400)
4159                              - margin.top - margin.bottom;
4160         }
4161
4162         wrap.select('.nv-legendWrap')
4163             .attr('transform', 'translate(0,' + (-margin.top) +')')
4164       }
4165
4166       //------------------------------------------------------------
4167
4168       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4169
4170       if (rightAlignYAxis) {
4171         g.select(".nv-y.nv-axis")
4172             .attr("transform", "translate(" + availableWidth + ",0)");
4173       }
4174
4175
4176       //------------------------------------------------------------
4177       // Main Chart Component(s)
4178
4179       bars
4180         .width(availableWidth)
4181         .height(availableHeight)
4182         .color(data.map(function(d,i) {
4183           return d.color || color(d, i);
4184         }).filter(function(d,i) { return !data[i].disabled }));
4185
4186
4187       var barsWrap = g.select('.nv-barsWrap')
4188           .datum(data.filter(function(d) { return !d.disabled }))
4189
4190       barsWrap.transition().call(bars);
4191
4192       //------------------------------------------------------------
4193
4194
4195       //------------------------------------------------------------
4196       // Setup Axes
4197
4198       if (showXAxis) {
4199         xAxis
4200           .scale(x)
4201           .tickSize(-availableHeight, 0);
4202
4203         g.select('.nv-x.nv-axis')
4204             .attr('transform', 'translate(0,' + y.range()[0] + ')');
4205         g.select('.nv-x.nv-axis')
4206             .transition()
4207             .call(xAxis);
4208       }
4209
4210       if (showYAxis) {
4211         yAxis
4212           .scale(y)
4213           .ticks( availableHeight / 36 )
4214           .tickSize( -availableWidth, 0);
4215
4216         g.select('.nv-y.nv-axis')
4217           .transition()
4218             .call(yAxis);
4219       }
4220       //------------------------------------------------------------
4221
4222
4223       //============================================================
4224       // Event Handling/Dispatching (in chart's scope)
4225       //------------------------------------------------------------
4226
4227       legend.dispatch.on('legendClick', function(d,i) { 
4228         d.disabled = !d.disabled;
4229
4230         if (!data.filter(function(d) { return !d.disabled }).length) {
4231           data.map(function(d) {
4232             d.disabled = false;
4233             wrap.selectAll('.nv-series').classed('disabled', false);
4234             return d;
4235           });
4236         }
4237
4238         state.disabled = data.map(function(d) { return !!d.disabled });
4239         dispatch.stateChange(state);
4240
4241         selection.transition().call(chart);
4242       });
4243
4244       legend.dispatch.on('legendDblclick', function(d) {
4245           //Double clicking should always enable current series, and disabled all others.
4246           data.forEach(function(d) {
4247              d.disabled = true;
4248           });
4249           d.disabled = false;  
4250
4251           state.disabled = data.map(function(d) { return !!d.disabled });
4252           dispatch.stateChange(state);
4253           chart.update();
4254       });
4255
4256       dispatch.on('tooltipShow', function(e) {
4257         if (tooltips) showTooltip(e, that.parentNode);
4258       });
4259
4260
4261       dispatch.on('changeState', function(e) {
4262
4263         if (typeof e.disabled !== 'undefined') {
4264           data.forEach(function(series,i) {
4265             series.disabled = e.disabled[i];
4266           });
4267
4268           state.disabled = e.disabled;
4269         }
4270
4271         selection.call(chart);
4272       });
4273
4274       //============================================================
4275
4276     });
4277
4278     return chart;
4279   }
4280
4281
4282   //============================================================
4283   // Event Handling/Dispatching (out of chart's scope)
4284   //------------------------------------------------------------
4285
4286   bars.dispatch.on('elementMouseover.tooltip', function(e) {
4287     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
4288     dispatch.tooltipShow(e);
4289   });
4290
4291   bars.dispatch.on('elementMouseout.tooltip', function(e) {
4292     dispatch.tooltipHide(e);
4293   });
4294
4295   dispatch.on('tooltipHide', function() {
4296     if (tooltips) nv.tooltip.cleanup();
4297   });
4298
4299   //============================================================
4300
4301
4302   //============================================================
4303   // Expose Public Variables
4304   //------------------------------------------------------------
4305
4306   // expose chart's sub-components
4307   chart.dispatch = dispatch;
4308   chart.bars = bars;
4309   chart.legend = legend;
4310   chart.xAxis = xAxis;
4311   chart.yAxis = yAxis;
4312
4313   d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 
4314     'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights', 'interactive');
4315
4316   chart.options = nv.utils.optionsFunc.bind(chart);
4317   
4318   chart.margin = function(_) {
4319     if (!arguments.length) return margin;
4320     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4321     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4322     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4323     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4324     return chart;
4325   };
4326
4327   chart.width = function(_) {
4328     if (!arguments.length) return width;
4329     width = _;
4330     return chart;
4331   };
4332
4333   chart.height = function(_) {
4334     if (!arguments.length) return height;
4335     height = _;
4336     return chart;
4337   };
4338
4339   chart.color = function(_) {
4340     if (!arguments.length) return color;
4341     color = nv.utils.getColor(_);
4342     legend.color(color);
4343     return chart;
4344   };
4345
4346   chart.showLegend = function(_) {
4347     if (!arguments.length) return showLegend;
4348     showLegend = _;
4349     return chart;
4350   };
4351
4352   chart.showXAxis = function(_) {
4353     if (!arguments.length) return showXAxis;
4354     showXAxis = _;
4355     return chart;
4356   };
4357
4358   chart.showYAxis = function(_) {
4359     if (!arguments.length) return showYAxis;
4360     showYAxis = _;
4361     return chart;
4362   };
4363
4364   chart.rightAlignYAxis = function(_) {
4365     if(!arguments.length) return rightAlignYAxis;
4366     rightAlignYAxis = _;
4367     yAxis.orient( (_) ? 'right' : 'left');
4368     return chart;
4369   };
4370
4371   chart.tooltips = function(_) {
4372     if (!arguments.length) return tooltips;
4373     tooltips = _;
4374     return chart;
4375   };
4376
4377   chart.tooltipContent = function(_) {
4378     if (!arguments.length) return tooltip;
4379     tooltip = _;
4380     return chart;
4381   };
4382
4383   chart.state = function(_) {
4384     if (!arguments.length) return state;
4385     state = _;
4386     return chart;
4387   };
4388
4389   chart.defaultState = function(_) {
4390     if (!arguments.length) return defaultState;
4391     defaultState = _;
4392     return chart;
4393   };
4394
4395   chart.noData = function(_) {
4396     if (!arguments.length) return noData;
4397     noData = _;
4398     return chart;
4399   };
4400
4401   chart.transitionDuration = function(_) {
4402     if (!arguments.length) return transitionDuration;
4403     transitionDuration = _;
4404     return chart;
4405   };
4406
4407   //============================================================
4408
4409
4410   return chart;
4411 }
4412 nv.models.indentedTree = function() {
4413   "use strict";
4414   //============================================================
4415   // Public Variables with Default Settings
4416   //------------------------------------------------------------
4417
4418   var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
4419     , width = 960
4420     , height = 500
4421     , color = nv.utils.defaultColor()
4422     , id = Math.floor(Math.random() * 10000)
4423     , header = true
4424     , filterZero = false
4425     , noData = "No Data Available."
4426     , childIndent = 20
4427     , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
4428     , tableClass = null
4429     , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
4430     , iconClose = 'images/grey-minus.png'
4431     , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
4432     , getUrl = function(d) { return d.url }
4433     ;
4434
4435   //============================================================
4436
4437   var idx = 0;
4438
4439   function chart(selection) {
4440     selection.each(function(data) {
4441       var depth = 1,
4442           container = d3.select(this);
4443
4444       var tree = d3.layout.tree()
4445           .children(function(d) { return d.values })
4446           .size([height, childIndent]); //Not sure if this is needed now that the result is HTML
4447
4448       chart.update = function() { container.transition().duration(600).call(chart) };
4449
4450
4451       //------------------------------------------------------------
4452       // Display No Data message if there's nothing to show.
4453       if (!data[0]) data[0] = {key: noData};
4454
4455       //------------------------------------------------------------
4456
4457
4458       var nodes = tree.nodes(data[0]);
4459
4460       // nodes.map(function(d) {
4461       //   d.id = i++;
4462       // })
4463
4464       //------------------------------------------------------------
4465       // Setup containers and skeleton of chart
4466
4467       var wrap = d3.select(this).selectAll('div').data([[nodes]]);
4468       var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
4469       var tableEnter = wrapEnter.append('table');
4470       var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
4471
4472       //------------------------------------------------------------
4473
4474
4475       if (header) {
4476         var thead = tableEnter.append('thead');
4477
4478         var theadRow1 = thead.append('tr');
4479
4480         columns.forEach(function(column) {
4481           theadRow1
4482             .append('th')
4483               .attr('width', column.width ? column.width : '10%')
4484               .style('text-align', column.type == 'numeric' ? 'right' : 'left')
4485             .append('span')
4486               .text(column.label);
4487         });
4488       }
4489
4490
4491       var tbody = table.selectAll('tbody')
4492                     .data(function(d) { return d });
4493       tbody.enter().append('tbody');
4494
4495
4496
4497       //compute max generations
4498       depth = d3.max(nodes, function(node) { return node.depth });
4499       tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
4500
4501
4502       // Update the nodes…
4503       var node = tbody.selectAll('tr')
4504           // .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)});
4505           .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) :  true; } )}, function(d,i) { return d.id || (d.id || ++idx)});
4506           //.style('display', 'table-row'); //TODO: see if this does anything
4507
4508       node.exit().remove();
4509
4510       node.select('img.nv-treeicon')
4511           .attr('src', icon)
4512           .classed('folded', folded);
4513
4514       var nodeEnter = node.enter().append('tr');
4515
4516
4517       columns.forEach(function(column, index) {
4518
4519         var nodeName = nodeEnter.append('td')
4520             .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here
4521             .style('text-align', column.type == 'numeric' ? 'right' : 'left');
4522
4523
4524         if (index == 0) {
4525           nodeName.append('img')
4526               .classed('nv-treeicon', true)
4527               .classed('nv-folded', folded)
4528               .attr('src', icon)
4529               .style('width', '14px')
4530               .style('height', '14px')
4531               .style('padding', '0 1px')
4532               .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
4533               .on('click', click);
4534         }
4535
4536
4537         nodeName.each(function(d) {
4538           if (!index && getUrl(d))
4539             d3.select(this)
4540               .append('a')
4541               .attr('href',getUrl)
4542               .attr('class', d3.functor(column.classes))
4543               .append('span')
4544           else
4545             d3.select(this)
4546               .append('span')
4547
4548             d3.select(this).select('span')
4549               .attr('class', d3.functor(column.classes) )
4550               .text(function(d) { return column.format ? column.format(d) :
4551                                         (d[column.key] || '-') });
4552           });
4553
4554         if  (column.showCount) {
4555           nodeName.append('span')
4556               .attr('class', 'nv-childrenCount');
4557
4558           node.selectAll('span.nv-childrenCount').text(function(d) {
4559                 return ((d.values && d.values.length) || (d._values && d._values.length)) ?                                   //If this is a parent
4560                     '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) :  true; }).length)) //If children are in values check its children and filter
4561                     || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) :  true; }).length)     //Otherwise, do the same, but with the other name, _values...
4562                     || 0) + ')'                                                                                               //This is the catch-all in case there are no children after a filter
4563                     : ''                                                                                                     //If this is not a parent, just give an empty string
4564             });
4565         }
4566
4567         // if (column.click)
4568         //   nodeName.select('span').on('click', column.click);
4569
4570       });
4571
4572       node
4573         .order()
4574         .on('click', function(d) { 
4575           dispatch.elementClick({
4576             row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
4577             data: d,
4578             pos: [d.x, d.y]
4579           });
4580         })
4581         .on('dblclick', function(d) { 
4582           dispatch.elementDblclick({
4583             row: this,
4584             data: d,
4585             pos: [d.x, d.y]
4586           });
4587         })
4588         .on('mouseover', function(d) { 
4589           dispatch.elementMouseover({
4590             row: this,
4591             data: d,
4592             pos: [d.x, d.y]
4593           });
4594         })
4595         .on('mouseout', function(d) { 
4596           dispatch.elementMouseout({
4597             row: this,
4598             data: d,
4599             pos: [d.x, d.y]
4600           });
4601         });
4602
4603
4604
4605
4606       // Toggle children on click.
4607       function click(d, _, unshift) {
4608         d3.event.stopPropagation();
4609
4610         if(d3.event.shiftKey && !unshift) {
4611           //If you shift-click, it'll toggle fold all the children, instead of itself
4612           d3.event.shiftKey = false;
4613           d.values && d.values.forEach(function(node){
4614             if (node.values || node._values) {
4615               click(node, 0, true);
4616             }
4617           });
4618           return true;
4619         }
4620         if(!hasChildren(d)) {
4621           //download file
4622           //window.location.href = d.url;
4623           return true;
4624         }
4625         if (d.values) {
4626           d._values = d.values;
4627           d.values = null;
4628         } else {
4629           d.values = d._values;
4630           d._values = null;
4631         }
4632         chart.update();
4633       }
4634
4635
4636       function icon(d) {
4637         return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
4638       }
4639
4640       function folded(d) {
4641         return (d._values && d._values.length);
4642       }
4643
4644       function hasChildren(d) {
4645         var values = d.values || d._values;
4646
4647         return (values && values.length);
4648       }
4649
4650
4651     });
4652
4653     return chart;
4654   }
4655
4656
4657   //============================================================
4658   // Expose Public Variables
4659   //------------------------------------------------------------
4660   chart.options = nv.utils.optionsFunc.bind(chart);
4661   
4662   chart.margin = function(_) {
4663     if (!arguments.length) return margin;
4664     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4665     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4666     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4667     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4668     return chart;
4669   };
4670
4671   chart.width = function(_) {
4672     if (!arguments.length) return width;
4673     width = _;
4674     return chart;
4675   };
4676
4677   chart.height = function(_) {
4678     if (!arguments.length) return height;
4679     height = _;
4680     return chart;
4681   };
4682
4683   chart.color = function(_) {
4684     if (!arguments.length) return color;
4685     color = nv.utils.getColor(_);
4686     scatter.color(color);
4687     return chart;
4688   };
4689
4690   chart.id = function(_) {
4691     if (!arguments.length) return id;
4692     id = _;
4693     return chart;
4694   };
4695
4696   chart.header = function(_) {
4697     if (!arguments.length) return header;
4698     header = _;
4699     return chart;
4700   };
4701
4702   chart.noData = function(_) {
4703     if (!arguments.length) return noData;
4704     noData = _;
4705     return chart;
4706   };
4707
4708   chart.filterZero = function(_) {
4709     if (!arguments.length) return filterZero;
4710     filterZero = _;
4711     return chart;
4712   };
4713
4714   chart.columns = function(_) {
4715     if (!arguments.length) return columns;
4716     columns = _;
4717     return chart;
4718   };
4719
4720   chart.tableClass = function(_) {
4721     if (!arguments.length) return tableClass;
4722     tableClass = _;
4723     return chart;
4724   };
4725
4726   chart.iconOpen = function(_){
4727      if (!arguments.length) return iconOpen;
4728     iconOpen = _;
4729     return chart;
4730   }
4731
4732   chart.iconClose = function(_){
4733      if (!arguments.length) return iconClose;
4734     iconClose = _;
4735     return chart;
4736   }
4737
4738   chart.getUrl = function(_){
4739      if (!arguments.length) return getUrl;
4740     getUrl = _;
4741     return chart;
4742   }
4743
4744   //============================================================
4745
4746
4747   return chart;
4748 };nv.models.legend = function() {
4749   "use strict";
4750   //============================================================
4751   // Public Variables with Default Settings
4752   //------------------------------------------------------------
4753
4754   var margin = {top: 5, right: 0, bottom: 5, left: 0}
4755     , width = 400
4756     , height = 20
4757     , getKey = function(d) { return d.key }
4758     , color = nv.utils.defaultColor()
4759     , align = true
4760     , rightAlign = true
4761     , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4762     , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4763     , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4764     ;
4765
4766   //============================================================
4767
4768
4769   function chart(selection) {
4770     selection.each(function(data) {
4771       var availableWidth = width - margin.left - margin.right,
4772           container = d3.select(this);
4773
4774
4775       //------------------------------------------------------------
4776       // Setup containers and skeleton of chart
4777
4778       var wrap = container.selectAll('g.nv-legend').data([data]);
4779       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4780       var g = wrap.select('g');
4781
4782       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4783
4784       //------------------------------------------------------------
4785
4786
4787       var series = g.selectAll('.nv-series')
4788           .data(function(d) { return d });
4789       var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4790           .on('mouseover', function(d,i) {
4791             dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
4792           })
4793           .on('mouseout', function(d,i) {
4794             dispatch.legendMouseout(d,i);
4795           })
4796           .on('click', function(d,i) {
4797             dispatch.legendClick(d,i);
4798             if (updateState) {
4799                if (radioButtonMode) {
4800                    //Radio button mode: set every series to disabled,
4801                    //  and enable the clicked series.
4802                    data.forEach(function(series) { series.disabled = true});
4803                    d.disabled = false;
4804                }
4805                else {
4806                    d.disabled = !d.disabled;
4807                    if (data.every(function(series) { return series.disabled})) {
4808                        //the default behavior of NVD3 legends is, if every single series
4809                        // is disabled, turn all series' back on.
4810                        data.forEach(function(series) { series.disabled = false});
4811                    }
4812                }
4813                dispatch.stateChange({
4814                   disabled: data.map(function(d) { return !!d.disabled })
4815                });
4816             }
4817           })
4818           .on('dblclick', function(d,i) {
4819             dispatch.legendDblclick(d,i);
4820             if (updateState) {
4821                 //the default behavior of NVD3 legends, when double clicking one,
4822                 // is to set all other series' to false, and make the double clicked series enabled.
4823                 data.forEach(function(series) {
4824                    series.disabled = true;
4825                 });
4826                 d.disabled = false; 
4827                 dispatch.stateChange({
4828                     disabled: data.map(function(d) { return !!d.disabled })
4829                 });
4830             }
4831           });
4832       seriesEnter.append('circle')
4833           .style('stroke-width', 2)
4834           .attr('class','nv-legend-symbol')
4835           .attr('r', 5);
4836       seriesEnter.append('text')
4837           .attr('text-anchor', 'start')
4838           .attr('class','nv-legend-text')
4839           .attr('dy', '.32em')
4840           .attr('dx', '8');
4841       series.classed('disabled', function(d) { return d.disabled });
4842       series.exit().remove();
4843       series.select('circle')
4844           .style('fill', function(d,i) { return d.color || color(d,i)})
4845           .style('stroke', function(d,i) { return d.color || color(d, i) });
4846       series.select('text').text(getKey);
4847
4848
4849       //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4850
4851       // NEW ALIGNING CODE, TODO: clean up
4852       if (align) {
4853
4854         var seriesWidths = [];
4855         series.each(function(d,i) {
4856               var legendText = d3.select(this).select('text');
4857               var nodeTextLength;
4858               try {
4859                 nodeTextLength = legendText.node().getComputedTextLength();
4860               }
4861               catch(e) {
4862                 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4863               }
4864              
4865               seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4866             });
4867
4868         var seriesPerRow = 0;
4869         var legendWidth = 0;
4870         var columnWidths = [];
4871
4872         while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4873           columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4874           legendWidth += seriesWidths[seriesPerRow++];
4875         }
4876         if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4877
4878
4879         while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4880           columnWidths = [];
4881           seriesPerRow--;
4882
4883           for (var k = 0; k < seriesWidths.length; k++) {
4884             if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4885               columnWidths[k % seriesPerRow] = seriesWidths[k];
4886           }
4887
4888           legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4889                           return prev + cur;
4890                         });
4891         }
4892
4893         var xPositions = [];
4894         for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4895             xPositions[i] = curX;
4896             curX += columnWidths[i];
4897         }
4898
4899         series
4900             .attr('transform', function(d, i) {
4901               return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4902             });
4903
4904         //position legend as far right as possible within the total width
4905         if (rightAlign) {
4906            g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4907         }
4908         else {
4909            g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4910         }
4911
4912         height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4913
4914       } else {
4915
4916         var ypos = 5,
4917             newxpos = 5,
4918             maxwidth = 0,
4919             xpos;
4920         series
4921             .attr('transform', function(d, i) {
4922               var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4923               xpos = newxpos;
4924
4925               if (width < margin.left + margin.right + xpos + length) {
4926                 newxpos = xpos = 5;
4927                 ypos += 20;
4928               }
4929
4930               newxpos += length;
4931               if (newxpos > maxwidth) maxwidth = newxpos;
4932
4933               return 'translate(' + xpos + ',' + ypos + ')';
4934             });
4935
4936         //position legend as far right as possible within the total width
4937         g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4938
4939         height = margin.top + margin.bottom + ypos + 15;
4940
4941       }
4942
4943     });
4944
4945     return chart;
4946   }
4947
4948
4949   //============================================================
4950   // Expose Public Variables
4951   //------------------------------------------------------------
4952
4953   chart.dispatch = dispatch;
4954   chart.options = nv.utils.optionsFunc.bind(chart);
4955   
4956   chart.margin = function(_) {
4957     if (!arguments.length) return margin;
4958     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4959     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4960     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4961     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4962     return chart;
4963   };
4964
4965   chart.width = function(_) {
4966     if (!arguments.length) return width;
4967     width = _;
4968     return chart;
4969   };
4970
4971   chart.height = function(_) {
4972     if (!arguments.length) return height;
4973     height = _;
4974     return chart;
4975   };
4976
4977   chart.key = function(_) {
4978     if (!arguments.length) return getKey;
4979     getKey = _;
4980     return chart;
4981   };
4982
4983   chart.color = function(_) {
4984     if (!arguments.length) return color;
4985     color = nv.utils.getColor(_);
4986     return chart;
4987   };
4988
4989   chart.align = function(_) {
4990     if (!arguments.length) return align;
4991     align = _;
4992     return chart;
4993   };
4994
4995   chart.rightAlign = function(_) {
4996     if (!arguments.length) return rightAlign;
4997     rightAlign = _;
4998     return chart;
4999   };
5000
5001   chart.updateState = function(_) {
5002     if (!arguments.length) return updateState;
5003     updateState = _;
5004     return chart;
5005   };
5006
5007   chart.radioButtonMode = function(_) {
5008     if (!arguments.length) return radioButtonMode;
5009     radioButtonMode = _;
5010     return chart;
5011   };
5012
5013   //============================================================
5014
5015
5016   return chart;
5017 }
5018
5019 nv.models.line = function() {
5020   "use strict";
5021   //============================================================
5022   // Public Variables with Default Settings
5023   //------------------------------------------------------------
5024
5025   var  scatter = nv.models.scatter()
5026     ;
5027
5028   var margin = {top: 0, right: 0, bottom: 0, left: 0}
5029     , width = 960
5030     , height = 500
5031     , color = nv.utils.defaultColor() // a function that returns a color
5032     , getX = function(d) { return d.x } // accessor to get the x value from a data point
5033     , getY = function(d) { return d.y } // accessor to get the y value from a data point
5034     , 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
5035     , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5036     , clipEdge = false // if true, masks lines within x and y scale
5037     , x //can be accessed via chart.xScale()
5038     , y //can be accessed via chart.yScale()
5039     , interpolate = "linear" // controls the line interpolation
5040     ;
5041
5042   scatter
5043     .size(16) // default size
5044     .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5045     ;
5046     
5047   //============================================================
5048
5049
5050   //============================================================
5051   // Private Variables
5052   //------------------------------------------------------------
5053
5054   var x0, y0 //used to store previous scales
5055       ;
5056
5057   //============================================================
5058
5059
5060   function chart(selection) {
5061     selection.each(function(data) {
5062       var availableWidth = width - margin.left - margin.right,
5063           availableHeight = height - margin.top - margin.bottom,
5064           container = d3.select(this);
5065
5066       //------------------------------------------------------------
5067       // Setup Scales
5068
5069       x = scatter.xScale();
5070       y = scatter.yScale();
5071
5072       x0 = x0 || x;
5073       y0 = y0 || y;
5074
5075       //------------------------------------------------------------
5076
5077
5078       //------------------------------------------------------------
5079       // Setup containers and skeleton of chart
5080
5081       var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5082       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5083       var defsEnter = wrapEnter.append('defs');
5084       var gEnter = wrapEnter.append('g');
5085       var g = wrap.select('g')
5086
5087       gEnter.append('g').attr('class', 'nv-groups');
5088       gEnter.append('g').attr('class', 'nv-scatterWrap');
5089
5090       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5091
5092       //------------------------------------------------------------
5093
5094
5095
5096
5097       scatter
5098         .width(availableWidth)
5099         .height(availableHeight)
5100
5101       var scatterWrap = wrap.select('.nv-scatterWrap');
5102           //.datum(data); // Data automatically trickles down from the wrap
5103
5104       scatterWrap.transition().call(scatter);
5105
5106
5107
5108       defsEnter.append('clipPath')
5109           .attr('id', 'nv-edge-clip-' + scatter.id())
5110         .append('rect');
5111
5112       wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5113           .attr('width', availableWidth)
5114           .attr('height', availableHeight);
5115
5116       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5117       scatterWrap
5118           .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5119
5120
5121
5122
5123       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5124           .data(function(d) { return d }, function(d) { return d.key });
5125       groups.enter().append('g')
5126           .style('stroke-opacity', 1e-6)
5127           .style('fill-opacity', 1e-6);
5128       groups.exit()
5129           .transition()
5130           .style('stroke-opacity', 1e-6)
5131           .style('fill-opacity', 1e-6)
5132           .remove();
5133       groups
5134           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5135           .classed('hover', function(d) { return d.hover })
5136           .style('fill', function(d,i){ return color(d, i) })
5137           .style('stroke', function(d,i){ return color(d, i)});
5138       groups
5139           .transition()
5140           .style('stroke-opacity', 1)
5141           .style('fill-opacity', .5);
5142
5143
5144
5145       var areaPaths = groups.selectAll('path.nv-area')
5146           .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5147       areaPaths.enter().append('path')
5148           .attr('class', 'nv-area')
5149           .attr('d', function(d) {
5150             return d3.svg.area()
5151                 .interpolate(interpolate)
5152                 .defined(defined)
5153                 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5154                 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5155                 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5156                 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5157                 .apply(this, [d.values])
5158           });
5159       groups.exit().selectAll('path.nv-area')
5160            .remove();
5161            
5162       areaPaths
5163           .transition()
5164           .attr('d', function(d) {
5165             return d3.svg.area()
5166                 .interpolate(interpolate)
5167                 .defined(defined)
5168                 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5169                 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5170                 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5171                 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5172                 .apply(this, [d.values])
5173           });
5174
5175
5176
5177       var linePaths = groups.selectAll('path.nv-line')
5178           .data(function(d) { return [d.values] });
5179       linePaths.enter().append('path')
5180           .attr('class', 'nv-line')
5181           .attr('d',
5182             d3.svg.line()
5183               .interpolate(interpolate)
5184               .defined(defined)
5185               .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5186               .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5187           );
5188       groups.exit().selectAll('path.nv-line')
5189           .transition()
5190           .attr('d',
5191             d3.svg.line()
5192               .interpolate(interpolate)
5193               .defined(defined)
5194               .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5195               .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5196           );
5197       linePaths
5198           .transition()
5199           .attr('d',
5200             d3.svg.line()
5201               .interpolate(interpolate)
5202               .defined(defined)
5203               .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5204               .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5205           );
5206
5207
5208
5209       //store old scales for use in transitions on update
5210       x0 = x.copy();
5211       y0 = y.copy();
5212
5213     });
5214
5215     return chart;
5216   }
5217
5218
5219   //============================================================
5220   // Expose Public Variables
5221   //------------------------------------------------------------
5222
5223   chart.dispatch = scatter.dispatch;
5224   chart.scatter = scatter;
5225
5226   d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 
5227     'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5228
5229   chart.options = nv.utils.optionsFunc.bind(chart);
5230   
5231   chart.margin = function(_) {
5232     if (!arguments.length) return margin;
5233     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5234     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5235     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5236     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5237     return chart;
5238   };
5239
5240   chart.width = function(_) {
5241     if (!arguments.length) return width;
5242     width = _;
5243     return chart;
5244   };
5245
5246   chart.height = function(_) {
5247     if (!arguments.length) return height;
5248     height = _;
5249     return chart;
5250   };
5251
5252   chart.x = function(_) {
5253     if (!arguments.length) return getX;
5254     getX = _;
5255     scatter.x(_);
5256     return chart;
5257   };
5258
5259   chart.y = function(_) {
5260     if (!arguments.length) return getY;
5261     getY = _;
5262     scatter.y(_);
5263     return chart;
5264   };
5265
5266   chart.clipEdge = function(_) {
5267     if (!arguments.length) return clipEdge;
5268     clipEdge = _;
5269     return chart;
5270   };
5271
5272   chart.color = function(_) {
5273     if (!arguments.length) return color;
5274     color = nv.utils.getColor(_);
5275     scatter.color(color);
5276     return chart;
5277   };
5278
5279   chart.interpolate = function(_) {
5280     if (!arguments.length) return interpolate;
5281     interpolate = _;
5282     return chart;
5283   };
5284
5285   chart.defined = function(_) {
5286     if (!arguments.length) return defined;
5287     defined = _;
5288     return chart;
5289   };
5290
5291   chart.isArea = function(_) {
5292     if (!arguments.length) return isArea;
5293     isArea = d3.functor(_);
5294     return chart;
5295   };
5296
5297   //============================================================
5298
5299
5300   return chart;
5301 }
5302
5303 nv.models.lineChart = function() {
5304   "use strict";
5305   //============================================================
5306   // Public Variables with Default Settings
5307   //------------------------------------------------------------
5308
5309   var lines = nv.models.line()
5310     , xAxis = nv.models.axis()
5311     , yAxis = nv.models.axis()
5312     , legend = nv.models.legend()
5313     , interactiveLayer = nv.interactiveGuideline()
5314     ;
5315
5316   var margin = {top: 30, right: 20, bottom: 50, left: 60}
5317     , color = nv.utils.defaultColor()
5318     , width = null
5319     , height = null
5320     , showLegend = true
5321     , showXAxis = true
5322     , showYAxis = true
5323     , rightAlignYAxis = false
5324     , useInteractiveGuideline = false
5325     , tooltips = true
5326     , tooltip = function(key, x, y, e, graph) {
5327         return '<h3>' + key + '</h3>' +
5328                '<p>' +  y + ' at ' + x + '</p>'
5329       }
5330     , x
5331     , y
5332     , state = {}
5333     , defaultState = null
5334     , noData = 'No Data Available.'
5335     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5336     , transitionDuration = 250
5337     ;
5338
5339   xAxis
5340     .orient('bottom')
5341     .tickPadding(7)
5342     ;
5343   yAxis
5344     .orient((rightAlignYAxis) ? 'right' : 'left')
5345     ;
5346
5347   //============================================================
5348
5349
5350   //============================================================
5351   // Private Variables
5352   //------------------------------------------------------------
5353
5354   var showTooltip = function(e, offsetElement) {
5355     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5356         top = e.pos[1] + ( offsetElement.offsetTop || 0),
5357         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5358         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5359         content = tooltip(e.series.key, x, y, e, chart);
5360
5361     nv.tooltip.show([left, top], content, null, null, offsetElement);
5362   };
5363
5364   //============================================================
5365
5366
5367   function chart(selection) {
5368     selection.each(function(data) {
5369       var container = d3.select(this),
5370           that = this;
5371
5372       var availableWidth = (width  || parseInt(container.style('width')) || 960)
5373                              - margin.left - margin.right,
5374           availableHeight = (height || parseInt(container.style('height')) || 400)
5375                              - margin.top - margin.bottom;
5376
5377
5378       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5379       chart.container = this;
5380
5381       //set state.disabled
5382       state.disabled = data.map(function(d) { return !!d.disabled });
5383
5384     
5385       if (!defaultState) {
5386         var key;
5387         defaultState = {};
5388         for (key in state) {
5389           if (state[key] instanceof Array)
5390             defaultState[key] = state[key].slice(0);
5391           else
5392             defaultState[key] = state[key];
5393         }
5394       }
5395
5396       //------------------------------------------------------------
5397       // Display noData message if there's nothing to show.
5398
5399       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5400         var noDataText = container.selectAll('.nv-noData').data([noData]);
5401
5402         noDataText.enter().append('text')
5403           .attr('class', 'nvd3 nv-noData')
5404           .attr('dy', '-.7em')
5405           .style('text-anchor', 'middle');
5406
5407         noDataText
5408           .attr('x', margin.left + availableWidth / 2)
5409           .attr('y', margin.top + availableHeight / 2)
5410           .text(function(d) { return d });
5411
5412         return chart;
5413       } else {
5414         container.selectAll('.nv-noData').remove();
5415       }
5416
5417       //------------------------------------------------------------
5418
5419
5420       //------------------------------------------------------------
5421       // Setup Scales
5422
5423       x = lines.xScale();
5424       y = lines.yScale();
5425
5426       //------------------------------------------------------------
5427
5428
5429       //------------------------------------------------------------
5430       // Setup containers and skeleton of chart
5431
5432       var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5433       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5434       var g = wrap.select('g');
5435
5436       gEnter.append("rect").style("opacity",0);
5437       gEnter.append('g').attr('class', 'nv-x nv-axis');
5438       gEnter.append('g').attr('class', 'nv-y nv-axis');
5439       gEnter.append('g').attr('class', 'nv-linesWrap');
5440       gEnter.append('g').attr('class', 'nv-legendWrap');
5441       gEnter.append('g').attr('class', 'nv-interactive');
5442
5443       g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
5444       //------------------------------------------------------------
5445       // Legend
5446
5447       if (showLegend) {
5448         legend.width(availableWidth);
5449
5450         g.select('.nv-legendWrap')
5451             .datum(data)
5452             .call(legend);
5453
5454         if ( margin.top != legend.height()) {
5455           margin.top = legend.height();
5456           availableHeight = (height || parseInt(container.style('height')) || 400)
5457                              - margin.top - margin.bottom;
5458         }
5459
5460         wrap.select('.nv-legendWrap')
5461             .attr('transform', 'translate(0,' + (-margin.top) +')')
5462       }
5463
5464       //------------------------------------------------------------
5465
5466       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5467
5468       if (rightAlignYAxis) {
5469           g.select(".nv-y.nv-axis")
5470               .attr("transform", "translate(" + availableWidth + ",0)");
5471       }
5472
5473       //------------------------------------------------------------
5474       // Main Chart Component(s)
5475
5476       
5477       //------------------------------------------------------------
5478       //Set up interactive layer
5479       if (useInteractiveGuideline) {
5480         interactiveLayer
5481            .width(availableWidth)
5482            .height(availableHeight)
5483            .margin({left:margin.left, top:margin.top})
5484            .svgContainer(container)
5485            .xScale(x);
5486         wrap.select(".nv-interactive").call(interactiveLayer);
5487       }
5488
5489
5490       lines
5491         .width(availableWidth)
5492         .height(availableHeight)
5493         .color(data.map(function(d,i) {
5494           return d.color || color(d, i);
5495         }).filter(function(d,i) { return !data[i].disabled }));
5496
5497
5498       var linesWrap = g.select('.nv-linesWrap')
5499           .datum(data.filter(function(d) { return !d.disabled }))
5500
5501       linesWrap.transition().call(lines);
5502
5503       //------------------------------------------------------------
5504
5505
5506       //------------------------------------------------------------
5507       // Setup Axes
5508
5509       if (showXAxis) {
5510         xAxis
5511           .scale(x)
5512           .ticks( availableWidth / 100 )
5513           .tickSize(-availableHeight, 0);
5514
5515         g.select('.nv-x.nv-axis')
5516             .attr('transform', 'translate(0,' + y.range()[0] + ')');
5517         g.select('.nv-x.nv-axis')
5518             .transition()
5519             .call(xAxis);
5520       }
5521
5522       if (showYAxis) {
5523         yAxis
5524           .scale(y)
5525           .ticks( availableHeight / 36 )
5526           .tickSize( -availableWidth, 0);
5527
5528         g.select('.nv-y.nv-axis')
5529             .transition()
5530             .call(yAxis);
5531       }
5532       //------------------------------------------------------------
5533
5534
5535       //============================================================
5536       // Event Handling/Dispatching (in chart's scope)
5537       //------------------------------------------------------------
5538
5539       legend.dispatch.on('stateChange', function(newState) {
5540           state = newState;
5541           dispatch.stateChange(state);
5542           chart.update();
5543       });
5544
5545       interactiveLayer.dispatch.on('elementMousemove', function(e) {
5546           lines.clearHighlights();
5547           var singlePoint, pointIndex, pointXLocation, allData = [];
5548           data
5549           .filter(function(series, i) { 
5550             series.seriesIndex = i;
5551             return !series.disabled; 
5552           })
5553           .forEach(function(series,i) {
5554               pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5555               lines.highlightPoint(i, pointIndex, true);
5556               var point = series.values[pointIndex];
5557               if (typeof point === 'undefined') return;
5558               if (typeof singlePoint === 'undefined') singlePoint = point;
5559               if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5560               allData.push({
5561                   key: series.key,
5562                   value: chart.y()(point, pointIndex),
5563                   color: color(series,series.seriesIndex)
5564               });
5565           });
5566
5567           var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5568           interactiveLayer.tooltip
5569                   .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5570                   .chartContainer(that.parentNode)
5571                   .enabled(tooltips)
5572                   .valueFormatter(function(d,i) {
5573                      return yAxis.tickFormat()(d);
5574                   })
5575                   .data(
5576                       {
5577                         value: xValue,
5578                         series: allData
5579                       }
5580                   )();
5581
5582           interactiveLayer.renderGuideLine(pointXLocation);
5583
5584       });
5585
5586       interactiveLayer.dispatch.on("elementMouseout",function(e) {
5587           dispatch.tooltipHide();
5588           lines.clearHighlights();
5589       });
5590
5591       dispatch.on('tooltipShow', function(e) {
5592         if (tooltips) showTooltip(e, that.parentNode);
5593       });
5594
5595
5596       dispatch.on('changeState', function(e) {
5597
5598         if (typeof e.disabled !== 'undefined') {
5599           data.forEach(function(series,i) {
5600             series.disabled = e.disabled[i];
5601           });
5602
5603           state.disabled = e.disabled;
5604         }
5605
5606         chart.update();
5607       });
5608
5609       //============================================================
5610
5611     });
5612
5613     return chart;
5614   }
5615
5616
5617   //============================================================
5618   // Event Handling/Dispatching (out of chart's scope)
5619   //------------------------------------------------------------
5620
5621   lines.dispatch.on('elementMouseover.tooltip', function(e) {
5622     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5623     dispatch.tooltipShow(e);
5624   });
5625
5626   lines.dispatch.on('elementMouseout.tooltip', function(e) {
5627     dispatch.tooltipHide(e);
5628   });
5629
5630   dispatch.on('tooltipHide', function() {
5631     if (tooltips) nv.tooltip.cleanup();
5632   });
5633
5634   //============================================================
5635
5636
5637   //============================================================
5638   // Expose Public Variables
5639   //------------------------------------------------------------
5640
5641   // expose chart's sub-components
5642   chart.dispatch = dispatch;
5643   chart.lines = lines;
5644   chart.legend = legend;
5645   chart.xAxis = xAxis;
5646   chart.yAxis = yAxis;
5647   chart.interactiveLayer = interactiveLayer;
5648
5649   d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5650     , 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5651
5652   chart.options = nv.utils.optionsFunc.bind(chart);
5653
5654   chart.margin = function(_) {
5655     if (!arguments.length) return margin;
5656     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5657     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5658     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5659     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5660     return chart;
5661   };
5662
5663   chart.width = function(_) {
5664     if (!arguments.length) return width;
5665     width = _;
5666     return chart;
5667   };
5668
5669   chart.height = function(_) {
5670     if (!arguments.length) return height;
5671     height = _;
5672     return chart;
5673   };
5674
5675   chart.color = function(_) {
5676     if (!arguments.length) return color;
5677     color = nv.utils.getColor(_);
5678     legend.color(color);
5679     return chart;
5680   };
5681
5682   chart.showLegend = function(_) {
5683     if (!arguments.length) return showLegend;
5684     showLegend = _;
5685     return chart;
5686   };
5687
5688   chart.showXAxis = function(_) {
5689     if (!arguments.length) return showXAxis;
5690     showXAxis = _;
5691     return chart;
5692   };
5693
5694   chart.showYAxis = function(_) {
5695     if (!arguments.length) return showYAxis;
5696     showYAxis = _;
5697     return chart;
5698   };
5699
5700   chart.rightAlignYAxis = function(_) {
5701     if(!arguments.length) return rightAlignYAxis;
5702     rightAlignYAxis = _;
5703     yAxis.orient( (_) ? 'right' : 'left');
5704     return chart;
5705   };
5706
5707   chart.useInteractiveGuideline = function(_) {
5708     if(!arguments.length) return useInteractiveGuideline;
5709     useInteractiveGuideline = _;
5710     if (_ === true) {
5711        chart.interactive(false);
5712        chart.useVoronoi(false);
5713     }
5714     return chart;
5715   };
5716
5717   chart.tooltips = function(_) {
5718     if (!arguments.length) return tooltips;
5719     tooltips = _;
5720     return chart;
5721   };
5722
5723   chart.tooltipContent = function(_) {
5724     if (!arguments.length) return tooltip;
5725     tooltip = _;
5726     return chart;
5727   };
5728
5729   chart.state = function(_) {
5730     if (!arguments.length) return state;
5731     state = _;
5732     return chart;
5733   };
5734
5735   chart.defaultState = function(_) {
5736     if (!arguments.length) return defaultState;
5737     defaultState = _;
5738     return chart;
5739   };
5740
5741   chart.noData = function(_) {
5742     if (!arguments.length) return noData;
5743     noData = _;
5744     return chart;
5745   };
5746
5747   chart.transitionDuration = function(_) {
5748     if (!arguments.length) return transitionDuration;
5749     transitionDuration = _;
5750     return chart;
5751   };
5752
5753   //============================================================
5754
5755
5756   return chart;
5757 }
5758
5759 nv.models.linePlusBarChart = function() {
5760   "use strict";
5761   //============================================================
5762   // Public Variables with Default Settings
5763   //------------------------------------------------------------
5764
5765   var lines = nv.models.line()
5766     , bars = nv.models.historicalBar()
5767     , xAxis = nv.models.axis()
5768     , y1Axis = nv.models.axis()
5769     , y2Axis = nv.models.axis()
5770     , legend = nv.models.legend()
5771     ;
5772
5773   var margin = {top: 30, right: 60, bottom: 50, left: 60}
5774     , width = null
5775     , height = null
5776     , getX = function(d) { return d.x }
5777     , getY = function(d) { return d.y }
5778     , color = nv.utils.defaultColor()
5779     , showLegend = true
5780     , tooltips = true
5781     , tooltip = function(key, x, y, e, graph) {
5782         return '<h3>' + key + '</h3>' +
5783                '<p>' +  y + ' at ' + x + '</p>';
5784       }
5785     , x
5786     , y1
5787     , y2
5788     , state = {}
5789     , defaultState = null
5790     , noData = "No Data Available."
5791     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5792     ;
5793
5794   bars
5795     .padData(true)
5796     ;
5797   lines
5798     .clipEdge(false)
5799     .padData(true)
5800     ;
5801   xAxis
5802     .orient('bottom')
5803     .tickPadding(7)
5804     .highlightZero(false)
5805     ;
5806   y1Axis
5807     .orient('left')
5808     ;
5809   y2Axis
5810     .orient('right')
5811     ;
5812
5813   //============================================================
5814
5815
5816   //============================================================
5817   // Private Variables
5818   //------------------------------------------------------------
5819
5820   var showTooltip = function(e, offsetElement) {
5821       var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5822           top = e.pos[1] + ( offsetElement.offsetTop || 0),
5823           x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5824           y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5825           content = tooltip(e.series.key, x, y, e, chart);
5826
5827       nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5828     }
5829     ;
5830
5831   //------------------------------------------------------------
5832
5833
5834
5835   function chart(selection) {
5836     selection.each(function(data) {
5837       var container = d3.select(this),
5838           that = this;
5839
5840       var availableWidth = (width  || parseInt(container.style('width')) || 960)
5841                              - margin.left - margin.right,
5842           availableHeight = (height || parseInt(container.style('height')) || 400)
5843                              - margin.top - margin.bottom;
5844
5845       chart.update = function() { container.transition().call(chart); };
5846       // chart.container = this;
5847
5848       //set state.disabled
5849       state.disabled = data.map(function(d) { return !!d.disabled });
5850
5851       if (!defaultState) {
5852         var key;
5853         defaultState = {};
5854         for (key in state) {
5855           if (state[key] instanceof Array)
5856             defaultState[key] = state[key].slice(0);
5857           else
5858             defaultState[key] = state[key];
5859         }
5860       }
5861
5862       //------------------------------------------------------------
5863       // Display No Data message if there's nothing to show.
5864
5865       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5866         var noDataText = container.selectAll('.nv-noData').data([noData]);
5867
5868         noDataText.enter().append('text')
5869           .attr('class', 'nvd3 nv-noData')
5870           .attr('dy', '-.7em')
5871           .style('text-anchor', 'middle');
5872
5873         noDataText
5874           .attr('x', margin.left + availableWidth / 2)
5875           .attr('y', margin.top + availableHeight / 2)
5876           .text(function(d) { return d });
5877
5878         return chart;
5879       } else {
5880         container.selectAll('.nv-noData').remove();
5881       }
5882
5883       //------------------------------------------------------------
5884
5885
5886       //------------------------------------------------------------
5887       // Setup Scales
5888
5889       var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5890       var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5891
5892       //x = xAxis.scale();
5893        x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
5894       //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
5895       y1 = bars.yScale();
5896       y2 = lines.yScale();
5897
5898       //------------------------------------------------------------
5899
5900       //------------------------------------------------------------
5901       // Setup containers and skeleton of chart
5902
5903       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5904       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5905       var g = wrap.select('g');
5906
5907       gEnter.append('g').attr('class', 'nv-x nv-axis');
5908       gEnter.append('g').attr('class', 'nv-y1 nv-axis');
5909       gEnter.append('g').attr('class', 'nv-y2 nv-axis');
5910       gEnter.append('g').attr('class', 'nv-barsWrap');
5911       gEnter.append('g').attr('class', 'nv-linesWrap');
5912       gEnter.append('g').attr('class', 'nv-legendWrap');
5913
5914       //------------------------------------------------------------
5915
5916
5917       //------------------------------------------------------------
5918       // Legend
5919
5920       if (showLegend) {
5921         legend.width( availableWidth / 2 );
5922
5923         g.select('.nv-legendWrap')
5924             .datum(data.map(function(series) {
5925               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5926               series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5927               return series;
5928             }))
5929           .call(legend);
5930
5931         if ( margin.top != legend.height()) {
5932           margin.top = legend.height();
5933           availableHeight = (height || parseInt(container.style('height')) || 400)
5934                              - margin.top - margin.bottom;
5935         }
5936
5937         g.select('.nv-legendWrap')
5938             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5939       }
5940
5941       //------------------------------------------------------------
5942
5943
5944       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5945
5946
5947       //------------------------------------------------------------
5948       // Main Chart Component(s)
5949
5950
5951       lines
5952         .width(availableWidth)
5953         .height(availableHeight)
5954         .color(data.map(function(d,i) {
5955           return d.color || color(d, i);
5956         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
5957
5958       bars
5959         .width(availableWidth)
5960         .height(availableHeight)
5961         .color(data.map(function(d,i) {
5962           return d.color || color(d, i);
5963         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
5964
5965
5966
5967       var barsWrap = g.select('.nv-barsWrap')
5968           .datum(dataBars.length ? dataBars : [{values:[]}])
5969
5970       var linesWrap = g.select('.nv-linesWrap')
5971           .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
5972           //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
5973
5974       d3.transition(barsWrap).call(bars);
5975       d3.transition(linesWrap).call(lines);
5976
5977       //------------------------------------------------------------
5978
5979
5980       //------------------------------------------------------------
5981       // Setup Axes
5982
5983       xAxis
5984         .scale(x)
5985         .ticks( availableWidth / 100 )
5986         .tickSize(-availableHeight, 0);
5987
5988       g.select('.nv-x.nv-axis')
5989           .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5990       d3.transition(g.select('.nv-x.nv-axis'))
5991           .call(xAxis);
5992
5993
5994       y1Axis
5995         .scale(y1)
5996         .ticks( availableHeight / 36 )
5997         .tickSize(-availableWidth, 0);
5998
5999       d3.transition(g.select('.nv-y1.nv-axis'))
6000           .style('opacity', dataBars.length ? 1 : 0)
6001           .call(y1Axis);
6002
6003
6004       y2Axis
6005         .scale(y2)
6006         .ticks( availableHeight / 36 )
6007         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6008
6009       g.select('.nv-y2.nv-axis')
6010           .style('opacity', dataLines.length ? 1 : 0)
6011           .attr('transform', 'translate(' + availableWidth + ',0)');
6012           //.attr('transform', 'translate(' + x.range()[1] + ',0)');
6013
6014       d3.transition(g.select('.nv-y2.nv-axis'))
6015           .call(y2Axis);
6016
6017       //------------------------------------------------------------
6018
6019
6020       //============================================================
6021       // Event Handling/Dispatching (in chart's scope)
6022       //------------------------------------------------------------
6023
6024       legend.dispatch.on('stateChange', function(newState) { 
6025         state = newState;
6026         dispatch.stateChange(state);
6027         chart.update();
6028       });
6029
6030       dispatch.on('tooltipShow', function(e) {
6031         if (tooltips) showTooltip(e, that.parentNode);
6032       });
6033
6034
6035       // Update chart from a state object passed to event handler
6036       dispatch.on('changeState', function(e) {
6037
6038         if (typeof e.disabled !== 'undefined') {
6039           data.forEach(function(series,i) {
6040             series.disabled = e.disabled[i];
6041           });
6042
6043           state.disabled = e.disabled;
6044         }
6045
6046         chart.update();
6047       });
6048
6049       //============================================================
6050
6051
6052     });
6053
6054     return chart;
6055   }
6056
6057
6058   //============================================================
6059   // Event Handling/Dispatching (out of chart's scope)
6060   //------------------------------------------------------------
6061
6062   lines.dispatch.on('elementMouseover.tooltip', function(e) {
6063     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6064     dispatch.tooltipShow(e);
6065   });
6066
6067   lines.dispatch.on('elementMouseout.tooltip', function(e) {
6068     dispatch.tooltipHide(e);
6069   });
6070
6071   bars.dispatch.on('elementMouseover.tooltip', function(e) {
6072     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6073     dispatch.tooltipShow(e);
6074   });
6075
6076   bars.dispatch.on('elementMouseout.tooltip', function(e) {
6077     dispatch.tooltipHide(e);
6078   });
6079
6080   dispatch.on('tooltipHide', function() {
6081     if (tooltips) nv.tooltip.cleanup();
6082   });
6083
6084   //============================================================
6085
6086
6087   //============================================================
6088   // Expose Public Variables
6089   //------------------------------------------------------------
6090
6091   // expose chart's sub-components
6092   chart.dispatch = dispatch;
6093   chart.legend = legend;
6094   chart.lines = lines;
6095   chart.bars = bars;
6096   chart.xAxis = xAxis;
6097   chart.y1Axis = y1Axis;
6098   chart.y2Axis = y2Axis;
6099
6100   d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6101   //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6102   //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6103
6104   chart.options = nv.utils.optionsFunc.bind(chart);
6105   
6106   chart.x = function(_) {
6107     if (!arguments.length) return getX;
6108     getX = _;
6109     lines.x(_);
6110     bars.x(_);
6111     return chart;
6112   };
6113
6114   chart.y = function(_) {
6115     if (!arguments.length) return getY;
6116     getY = _;
6117     lines.y(_);
6118     bars.y(_);
6119     return chart;
6120   };
6121
6122   chart.margin = function(_) {
6123     if (!arguments.length) return margin;
6124     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
6125     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
6126     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6127     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
6128     return chart;
6129   };
6130
6131   chart.width = function(_) {
6132     if (!arguments.length) return width;
6133     width = _;
6134     return chart;
6135   };
6136
6137   chart.height = function(_) {
6138     if (!arguments.length) return height;
6139     height = _;
6140     return chart;
6141   };
6142
6143   chart.color = function(_) {
6144     if (!arguments.length) return color;
6145     color = nv.utils.getColor(_);
6146     legend.color(color);
6147     return chart;
6148   };
6149
6150   chart.showLegend = function(_) {
6151     if (!arguments.length) return showLegend;
6152     showLegend = _;
6153     return chart;
6154   };
6155
6156   chart.tooltips = function(_) {
6157     if (!arguments.length) return tooltips;
6158     tooltips = _;
6159     return chart;
6160   };
6161
6162   chart.tooltipContent = function(_) {
6163     if (!arguments.length) return tooltip;
6164     tooltip = _;
6165     return chart;
6166   };
6167
6168   chart.state = function(_) {
6169     if (!arguments.length) return state;
6170     state = _;
6171     return chart;
6172   };
6173
6174   chart.defaultState = function(_) {
6175     if (!arguments.length) return defaultState;
6176     defaultState = _;
6177     return chart;
6178   };
6179
6180   chart.noData = function(_) {
6181     if (!arguments.length) return noData;
6182     noData = _;
6183     return chart;
6184   };
6185
6186   //============================================================
6187
6188
6189   return chart;
6190 }
6191 nv.models.lineWithFocusChart = function() {
6192   "use strict";
6193   //============================================================
6194   // Public Variables with Default Settings
6195   //------------------------------------------------------------
6196
6197   var lines = nv.models.line()
6198     , lines2 = nv.models.line()
6199     , xAxis = nv.models.axis()
6200     , yAxis = nv.models.axis()
6201     , x2Axis = nv.models.axis()
6202     , y2Axis = nv.models.axis()
6203     , legend = nv.models.legend()
6204     , brush = d3.svg.brush()
6205     ;
6206
6207   var margin = {top: 30, right: 30, bottom: 30, left: 60}
6208     , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6209     , color = nv.utils.defaultColor()
6210     , width = null
6211     , height = null
6212     , height2 = 100
6213     , x
6214     , y
6215     , x2
6216     , y2
6217     , showLegend = true
6218     , brushExtent = null
6219     , tooltips = true
6220     , tooltip = function(key, x, y, e, graph) {
6221         return '<h3>' + key + '</h3>' +
6222                '<p>' +  y + ' at ' + x + '</p>'
6223       }
6224     , noData = "No Data Available."
6225     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6226     , transitionDuration = 250
6227     ;
6228
6229   lines
6230     .clipEdge(true)
6231     ;
6232   lines2
6233     .interactive(false)
6234     ;
6235   xAxis
6236     .orient('bottom')
6237     .tickPadding(5)
6238     ;
6239   yAxis
6240     .orient('left')
6241     ;
6242   x2Axis
6243     .orient('bottom')
6244     .tickPadding(5)
6245     ;
6246   y2Axis
6247     .orient('left')
6248     ;
6249   //============================================================
6250
6251
6252   //============================================================
6253   // Private Variables
6254   //------------------------------------------------------------
6255
6256   var showTooltip = function(e, offsetElement) {
6257     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6258         top = e.pos[1] + ( offsetElement.offsetTop || 0),
6259         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6260         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
6261         content = tooltip(e.series.key, x, y, e, chart);
6262
6263     nv.tooltip.show([left, top], content, null, null, offsetElement);
6264   };
6265
6266   //============================================================
6267
6268
6269   function chart(selection) {
6270     selection.each(function(data) {
6271       var container = d3.select(this),
6272           that = this;
6273
6274       var availableWidth = (width  || parseInt(container.style('width')) || 960)
6275                              - margin.left - margin.right,
6276           availableHeight1 = (height || parseInt(container.style('height')) || 400)
6277                              - margin.top - margin.bottom - height2,
6278           availableHeight2 = height2 - margin2.top - margin2.bottom;
6279
6280       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
6281       chart.container = this;
6282
6283
6284       //------------------------------------------------------------
6285       // Display No Data message if there's nothing to show.
6286
6287       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6288         var noDataText = container.selectAll('.nv-noData').data([noData]);
6289
6290         noDataText.enter().append('text')
6291           .attr('class', 'nvd3 nv-noData')
6292           .attr('dy', '-.7em')
6293           .style('text-anchor', 'middle');
6294
6295         noDataText
6296           .attr('x', margin.left + availableWidth / 2)
6297           .attr('y', margin.top + availableHeight1 / 2)
6298           .text(function(d) { return d });
6299
6300         return chart;
6301       } else {
6302         container.selectAll('.nv-noData').remove();
6303       }
6304
6305       //------------------------------------------------------------
6306
6307
6308       //------------------------------------------------------------
6309       // Setup Scales
6310
6311       x = lines.xScale();
6312       y = lines.yScale();
6313       x2 = lines2.xScale();
6314       y2 = lines2.yScale();
6315
6316       //------------------------------------------------------------
6317
6318
6319       //------------------------------------------------------------
6320       // Setup containers and skeleton of chart
6321
6322       var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
6323       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
6324       var g = wrap.select('g');
6325
6326       gEnter.append('g').attr('class', 'nv-legendWrap');
6327
6328       var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6329       focusEnter.append('g').attr('class', 'nv-x nv-axis');
6330       focusEnter.append('g').attr('class', 'nv-y nv-axis');
6331       focusEnter.append('g').attr('class', 'nv-linesWrap');
6332
6333       var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6334       contextEnter.append('g').attr('class', 'nv-x nv-axis');
6335       contextEnter.append('g').attr('class', 'nv-y nv-axis');
6336       contextEnter.append('g').attr('class', 'nv-linesWrap');
6337       contextEnter.append('g').attr('class', 'nv-brushBackground');
6338       contextEnter.append('g').attr('class', 'nv-x nv-brush');
6339
6340       //------------------------------------------------------------
6341
6342
6343       //------------------------------------------------------------
6344       // Legend
6345
6346       if (showLegend) {
6347         legend.width(availableWidth);
6348
6349         g.select('.nv-legendWrap')
6350             .datum(data)
6351             .call(legend);
6352
6353         if ( margin.top != legend.height()) {
6354           margin.top = legend.height();
6355           availableHeight1 = (height || parseInt(container.style('height')) || 400)
6356                              - margin.top - margin.bottom - height2;
6357         }
6358
6359         g.select('.nv-legendWrap')
6360             .attr('transform', 'translate(0,' + (-margin.top) +')')
6361       }
6362
6363       //------------------------------------------------------------
6364
6365
6366       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6367
6368
6369       //------------------------------------------------------------
6370       // Main Chart Component(s)
6371
6372       lines
6373         .width(availableWidth)
6374         .height(availableHeight1)
6375         .color(
6376           data
6377             .map(function(d,i) {
6378               return d.color || color(d, i);
6379             })
6380             .filter(function(d,i) {
6381               return !data[i].disabled;
6382           })
6383         );
6384
6385       lines2
6386         .defined(lines.defined())
6387         .width(availableWidth)
6388         .height(availableHeight2)
6389         .color(
6390           data
6391             .map(function(d,i) {
6392               return d.color || color(d, i);
6393             })
6394             .filter(function(d,i) {
6395               return !data[i].disabled;
6396           })
6397         );
6398
6399       g.select('.nv-context')
6400           .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6401
6402       var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
6403           .datum(data.filter(function(d) { return !d.disabled }))
6404
6405       d3.transition(contextLinesWrap).call(lines2);
6406
6407       //------------------------------------------------------------
6408
6409
6410       /*
6411       var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6412           .datum(data.filter(function(d) { return !d.disabled }))
6413
6414       d3.transition(focusLinesWrap).call(lines);
6415      */
6416
6417
6418       //------------------------------------------------------------
6419       // Setup Main (Focus) Axes
6420
6421       xAxis
6422         .scale(x)
6423         .ticks( availableWidth / 100 )
6424         .tickSize(-availableHeight1, 0);
6425
6426       yAxis
6427         .scale(y)
6428         .ticks( availableHeight1 / 36 )
6429         .tickSize( -availableWidth, 0);
6430
6431       g.select('.nv-focus .nv-x.nv-axis')
6432           .attr('transform', 'translate(0,' + availableHeight1 + ')');
6433
6434       //------------------------------------------------------------
6435
6436
6437       //------------------------------------------------------------
6438       // Setup Brush
6439
6440       brush
6441         .x(x2)
6442         .on('brush', function() {
6443             //When brushing, turn off transitions because chart needs to change immediately.
6444             var oldTransition = chart.transitionDuration();
6445             chart.transitionDuration(0); 
6446             onBrush();
6447             chart.transitionDuration(oldTransition);
6448         });
6449
6450       if (brushExtent) brush.extent(brushExtent);
6451
6452       var brushBG = g.select('.nv-brushBackground').selectAll('g')
6453           .data([brushExtent || brush.extent()])
6454
6455       var brushBGenter = brushBG.enter()
6456           .append('g');
6457
6458       brushBGenter.append('rect')
6459           .attr('class', 'left')
6460           .attr('x', 0)
6461           .attr('y', 0)
6462           .attr('height', availableHeight2);
6463
6464       brushBGenter.append('rect')
6465           .attr('class', 'right')
6466           .attr('x', 0)
6467           .attr('y', 0)
6468           .attr('height', availableHeight2);
6469
6470       var gBrush = g.select('.nv-x.nv-brush')
6471           .call(brush);
6472       gBrush.selectAll('rect')
6473           //.attr('y', -5)
6474           .attr('height', availableHeight2);
6475       gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6476
6477       onBrush();
6478
6479       //------------------------------------------------------------
6480
6481
6482       //------------------------------------------------------------
6483       // Setup Secondary (Context) Axes
6484
6485       x2Axis
6486         .scale(x2)
6487         .ticks( availableWidth / 100 )
6488         .tickSize(-availableHeight2, 0);
6489
6490       g.select('.nv-context .nv-x.nv-axis')
6491           .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6492       d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6493           .call(x2Axis);
6494
6495
6496       y2Axis
6497         .scale(y2)
6498         .ticks( availableHeight2 / 36 )
6499         .tickSize( -availableWidth, 0);
6500
6501       d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6502           .call(y2Axis);
6503
6504       g.select('.nv-context .nv-x.nv-axis')
6505           .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6506
6507       //------------------------------------------------------------
6508
6509
6510       //============================================================
6511       // Event Handling/Dispatching (in chart's scope)
6512       //------------------------------------------------------------
6513
6514       legend.dispatch.on('stateChange', function(newState) { 
6515         chart.update();
6516       });
6517
6518       dispatch.on('tooltipShow', function(e) {
6519         if (tooltips) showTooltip(e, that.parentNode);
6520       });
6521
6522       //============================================================
6523
6524
6525       //============================================================
6526       // Functions
6527       //------------------------------------------------------------
6528
6529       // Taken from crossfilter (http://square.github.com/crossfilter/)
6530       function resizePath(d) {
6531         var e = +(d == 'e'),
6532             x = e ? 1 : -1,
6533             y = availableHeight2 / 3;
6534         return 'M' + (.5 * x) + ',' + y
6535             + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6536             + 'V' + (2 * y - 6)
6537             + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6538             + 'Z'
6539             + 'M' + (2.5 * x) + ',' + (y + 8)
6540             + 'V' + (2 * y - 8)
6541             + 'M' + (4.5 * x) + ',' + (y + 8)
6542             + 'V' + (2 * y - 8);
6543       }
6544
6545
6546       function updateBrushBG() {
6547         if (!brush.empty()) brush.extent(brushExtent);
6548         brushBG
6549             .data([brush.empty() ? x2.domain() : brushExtent])
6550             .each(function(d,i) {
6551               var leftWidth = x2(d[0]) - x.range()[0],
6552                   rightWidth = x.range()[1] - x2(d[1]);
6553               d3.select(this).select('.left')
6554                 .attr('width',  leftWidth < 0 ? 0 : leftWidth);
6555
6556               d3.select(this).select('.right')
6557                 .attr('x', x2(d[1]))
6558                 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6559             });
6560       }
6561
6562
6563       function onBrush() {
6564         brushExtent = brush.empty() ? null : brush.extent();
6565         var extent = brush.empty() ? x2.domain() : brush.extent();
6566
6567         //The brush extent cannot be less than one.  If it is, don't update the line chart.
6568         if (Math.abs(extent[0] - extent[1]) <= 1) {
6569           return;
6570         }
6571
6572         dispatch.brush({extent: extent, brush: brush});
6573
6574
6575         updateBrushBG();
6576
6577         // Update Main (Focus)
6578         var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6579             .datum(
6580               data
6581                 .filter(function(d) { return !d.disabled })
6582                 .map(function(d,i) {
6583                   return {
6584                     key: d.key,
6585                     values: d.values.filter(function(d,i) {
6586                       return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6587                     })
6588                   }
6589                 })
6590             );
6591         focusLinesWrap.transition().duration(transitionDuration).call(lines);
6592
6593
6594         // Update Main (Focus) Axes
6595         g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6596             .call(xAxis);
6597         g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6598             .call(yAxis);
6599       }
6600
6601       //============================================================
6602
6603
6604     });
6605
6606     return chart;
6607   }
6608
6609
6610   //============================================================
6611   // Event Handling/Dispatching (out of chart's scope)
6612   //------------------------------------------------------------
6613
6614   lines.dispatch.on('elementMouseover.tooltip', function(e) {
6615     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6616     dispatch.tooltipShow(e);
6617   });
6618
6619   lines.dispatch.on('elementMouseout.tooltip', function(e) {
6620     dispatch.tooltipHide(e);
6621   });
6622
6623   dispatch.on('tooltipHide', function() {
6624     if (tooltips) nv.tooltip.cleanup();
6625   });
6626
6627   //============================================================
6628
6629
6630   //============================================================
6631   // Expose Public Variables
6632   //------------------------------------------------------------
6633
6634   // expose chart's sub-components
6635   chart.dispatch = dispatch;
6636   chart.legend = legend;
6637   chart.lines = lines;
6638   chart.lines2 = lines2;
6639   chart.xAxis = xAxis;
6640   chart.yAxis = yAxis;
6641   chart.x2Axis = x2Axis;
6642   chart.y2Axis = y2Axis;
6643
6644   d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6645
6646   chart.options = nv.utils.optionsFunc.bind(chart);
6647   
6648   chart.x = function(_) {
6649     if (!arguments.length) return lines.x;
6650     lines.x(_);
6651     lines2.x(_);
6652     return chart;
6653   };
6654
6655   chart.y = function(_) {
6656     if (!arguments.length) return lines.y;
6657     lines.y(_);
6658     lines2.y(_);
6659     return chart;
6660   };
6661
6662   chart.margin = function(_) {
6663     if (!arguments.length) return margin;
6664     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
6665     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
6666     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6667     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
6668     return chart;
6669   };
6670
6671   chart.margin2 = function(_) {
6672     if (!arguments.length) return margin2;
6673     margin2 = _;
6674     return chart;
6675   };
6676
6677   chart.width = function(_) {
6678     if (!arguments.length) return width;
6679     width = _;
6680     return chart;
6681   };
6682
6683   chart.height = function(_) {
6684     if (!arguments.length) return height;
6685     height = _;
6686     return chart;
6687   };
6688
6689   chart.height2 = function(_) {
6690     if (!arguments.length) return height2;
6691     height2 = _;
6692     return chart;
6693   };
6694
6695   chart.color = function(_) {
6696     if (!arguments.length) return color;
6697     color =nv.utils.getColor(_);
6698     legend.color(color);
6699     return chart;
6700   };
6701
6702   chart.showLegend = function(_) {
6703     if (!arguments.length) return showLegend;
6704     showLegend = _;
6705     return chart;
6706   };
6707
6708   chart.tooltips = function(_) {
6709     if (!arguments.length) return tooltips;
6710     tooltips = _;
6711     return chart;
6712   };
6713
6714   chart.tooltipContent = function(_) {
6715     if (!arguments.length) return tooltip;
6716     tooltip = _;
6717     return chart;
6718   };
6719
6720   chart.interpolate = function(_) {
6721     if (!arguments.length) return lines.interpolate();
6722     lines.interpolate(_);
6723     lines2.interpolate(_);
6724     return chart;
6725   };
6726
6727   chart.noData = function(_) {
6728     if (!arguments.length) return noData;
6729     noData = _;
6730     return chart;
6731   };
6732
6733   // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
6734   chart.xTickFormat = function(_) {
6735     if (!arguments.length) return xAxis.tickFormat();
6736     xAxis.tickFormat(_);
6737     x2Axis.tickFormat(_);
6738     return chart;
6739   };
6740
6741   chart.yTickFormat = function(_) {
6742     if (!arguments.length) return yAxis.tickFormat();
6743     yAxis.tickFormat(_);
6744     y2Axis.tickFormat(_);
6745     return chart;
6746   };
6747   
6748   chart.brushExtent = function(_) {
6749     if (!arguments.length) return brushExtent;
6750     brushExtent = _;
6751     return chart;
6752   };
6753
6754   chart.transitionDuration = function(_) {
6755     if (!arguments.length) return transitionDuration;
6756     transitionDuration = _;
6757     return chart;
6758   };
6759
6760   //============================================================
6761
6762
6763   return chart;
6764 }
6765
6766 nv.models.linePlusBarWithFocusChart = function() {
6767   "use strict";
6768   //============================================================
6769   // Public Variables with Default Settings
6770   //------------------------------------------------------------
6771
6772   var lines = nv.models.line()
6773     , lines2 = nv.models.line()
6774     , bars = nv.models.historicalBar()
6775     , bars2 = nv.models.historicalBar()
6776     , xAxis = nv.models.axis()
6777     , x2Axis = nv.models.axis()
6778     , y1Axis = nv.models.axis()
6779     , y2Axis = nv.models.axis()
6780     , y3Axis = nv.models.axis()
6781     , y4Axis = nv.models.axis()
6782     , legend = nv.models.legend()
6783     , brush = d3.svg.brush()
6784     ;
6785
6786   var margin = {top: 30, right: 30, bottom: 30, left: 60}
6787     , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6788     , width = null
6789     , height = null
6790     , height2 = 100
6791     , getX = function(d) { return d.x }
6792     , getY = function(d) { return d.y }
6793     , color = nv.utils.defaultColor()
6794     , showLegend = true
6795     , extent
6796     , brushExtent = null
6797     , tooltips = true
6798     , tooltip = function(key, x, y, e, graph) {
6799         return '<h3>' + key + '</h3>' +
6800                '<p>' +  y + ' at ' + x + '</p>';
6801       }
6802     , x
6803     , x2
6804     , y1
6805     , y2
6806     , y3
6807     , y4
6808     , noData = "No Data Available."
6809     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6810     , transitionDuration = 0
6811     ;
6812
6813   lines
6814     .clipEdge(true)
6815     ;
6816   lines2
6817     .interactive(false)
6818     ;
6819   xAxis
6820     .orient('bottom')
6821     .tickPadding(5)
6822     ;
6823   y1Axis
6824     .orient('left')
6825     ;
6826   y2Axis
6827     .orient('right')
6828     ;
6829   x2Axis
6830     .orient('bottom')
6831     .tickPadding(5)
6832     ;
6833   y3Axis
6834     .orient('left')
6835     ;
6836   y4Axis
6837     .orient('right')
6838     ;
6839
6840   //============================================================
6841
6842
6843   //============================================================
6844   // Private Variables
6845   //------------------------------------------------------------
6846
6847   var showTooltip = function(e, offsetElement) {
6848     if (extent) {
6849         e.pointIndex += Math.ceil(extent[0]);
6850     }
6851     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6852         top = e.pos[1] + ( offsetElement.offsetTop || 0),
6853         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6854         y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6855         content = tooltip(e.series.key, x, y, e, chart);
6856
6857     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6858   };
6859
6860   //------------------------------------------------------------
6861
6862
6863
6864   function chart(selection) {
6865     selection.each(function(data) {
6866       var container = d3.select(this),
6867           that = this;
6868
6869       var availableWidth = (width  || parseInt(container.style('width')) || 960)
6870                              - margin.left - margin.right,
6871           availableHeight1 = (height || parseInt(container.style('height')) || 400)
6872                              - margin.top - margin.bottom - height2,
6873           availableHeight2 = height2 - margin2.top - margin2.bottom;
6874
6875       chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6876       chart.container = this;
6877
6878
6879       //------------------------------------------------------------
6880       // Display No Data message if there's nothing to show.
6881
6882       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6883         var noDataText = container.selectAll('.nv-noData').data([noData]);
6884
6885         noDataText.enter().append('text')
6886           .attr('class', 'nvd3 nv-noData')
6887           .attr('dy', '-.7em')
6888           .style('text-anchor', 'middle');
6889
6890         noDataText
6891           .attr('x', margin.left + availableWidth / 2)
6892           .attr('y', margin.top + availableHeight1 / 2)
6893           .text(function(d) { return d });
6894
6895         return chart;
6896       } else {
6897         container.selectAll('.nv-noData').remove();
6898       }
6899
6900       //------------------------------------------------------------
6901
6902
6903       //------------------------------------------------------------
6904       // Setup Scales
6905
6906       var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6907       var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6908
6909       x = bars.xScale();
6910       x2 = x2Axis.scale();
6911       y1 = bars.yScale();
6912       y2 = lines.yScale();
6913       y3 = bars2.yScale();
6914       y4 = lines2.yScale();
6915
6916       var series1 = data
6917         .filter(function(d) { return !d.disabled && d.bar })
6918         .map(function(d) {
6919           return d.values.map(function(d,i) {
6920             return { x: getX(d,i), y: getY(d,i) }
6921           })
6922         });
6923
6924       var series2 = data
6925         .filter(function(d) { return !d.disabled && !d.bar })
6926         .map(function(d) {
6927           return d.values.map(function(d,i) {
6928             return { x: getX(d,i), y: getY(d,i) }
6929           })
6930         });
6931
6932       x   .range([0, availableWidth]);
6933       
6934       x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6935           .range([0, availableWidth]);
6936
6937
6938       //------------------------------------------------------------
6939
6940
6941       //------------------------------------------------------------
6942       // Setup containers and skeleton of chart
6943
6944       var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6945       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6946       var g = wrap.select('g');
6947
6948       gEnter.append('g').attr('class', 'nv-legendWrap');
6949       
6950       var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6951       focusEnter.append('g').attr('class', 'nv-x nv-axis');
6952       focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6953       focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6954       focusEnter.append('g').attr('class', 'nv-barsWrap');
6955       focusEnter.append('g').attr('class', 'nv-linesWrap');
6956
6957       var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6958       contextEnter.append('g').attr('class', 'nv-x nv-axis');
6959       contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6960       contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6961       contextEnter.append('g').attr('class', 'nv-barsWrap');
6962       contextEnter.append('g').attr('class', 'nv-linesWrap');
6963       contextEnter.append('g').attr('class', 'nv-brushBackground');
6964       contextEnter.append('g').attr('class', 'nv-x nv-brush');
6965
6966
6967       //------------------------------------------------------------
6968
6969
6970       //------------------------------------------------------------
6971       // Legend
6972
6973       if (showLegend) {
6974         legend.width( availableWidth / 2 );
6975
6976         g.select('.nv-legendWrap')
6977             .datum(data.map(function(series) {
6978               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6979               series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6980               return series;
6981             }))
6982           .call(legend);
6983
6984         if ( margin.top != legend.height()) {
6985           margin.top = legend.height();
6986           availableHeight1 = (height || parseInt(container.style('height')) || 400)
6987                              - margin.top - margin.bottom - height2;
6988         }
6989
6990         g.select('.nv-legendWrap')
6991             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6992       }
6993
6994       //------------------------------------------------------------
6995
6996
6997       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6998
6999
7000       //------------------------------------------------------------
7001       // Context Components
7002
7003       bars2
7004         .width(availableWidth)
7005         .height(availableHeight2)
7006         .color(data.map(function(d,i) {
7007           return d.color || color(d, i);
7008         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7009
7010       lines2
7011         .width(availableWidth)
7012         .height(availableHeight2)
7013         .color(data.map(function(d,i) {
7014           return d.color || color(d, i);
7015         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7016         
7017       var bars2Wrap = g.select('.nv-context .nv-barsWrap')
7018           .datum(dataBars.length ? dataBars : [{values:[]}]);
7019
7020       var lines2Wrap = g.select('.nv-context .nv-linesWrap')
7021           .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
7022           
7023       g.select('.nv-context')
7024           .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7025
7026       bars2Wrap.transition().call(bars2);
7027       lines2Wrap.transition().call(lines2);
7028
7029       //------------------------------------------------------------
7030
7031
7032
7033       //------------------------------------------------------------
7034       // Setup Brush
7035
7036       brush
7037         .x(x2)
7038         .on('brush', onBrush);
7039
7040       if (brushExtent) brush.extent(brushExtent);
7041
7042       var brushBG = g.select('.nv-brushBackground').selectAll('g')
7043           .data([brushExtent || brush.extent()])
7044
7045       var brushBGenter = brushBG.enter()
7046           .append('g');
7047
7048       brushBGenter.append('rect')
7049           .attr('class', 'left')
7050           .attr('x', 0)
7051           .attr('y', 0)
7052           .attr('height', availableHeight2);
7053
7054       brushBGenter.append('rect')
7055           .attr('class', 'right')
7056           .attr('x', 0)
7057           .attr('y', 0)
7058           .attr('height', availableHeight2);
7059
7060       var gBrush = g.select('.nv-x.nv-brush')
7061           .call(brush);
7062       gBrush.selectAll('rect')
7063           //.attr('y', -5)
7064           .attr('height', availableHeight2);
7065       gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7066
7067       //------------------------------------------------------------
7068
7069       //------------------------------------------------------------
7070       // Setup Secondary (Context) Axes
7071
7072       x2Axis
7073         .ticks( availableWidth / 100 )
7074         .tickSize(-availableHeight2, 0);
7075
7076       g.select('.nv-context .nv-x.nv-axis')
7077           .attr('transform', 'translate(0,' + y3.range()[0] + ')');
7078       g.select('.nv-context .nv-x.nv-axis').transition()
7079           .call(x2Axis);
7080
7081
7082       y3Axis
7083         .scale(y3)
7084         .ticks( availableHeight2 / 36 )
7085         .tickSize( -availableWidth, 0);
7086
7087       g.select('.nv-context .nv-y1.nv-axis')
7088           .style('opacity', dataBars.length ? 1 : 0)
7089           .attr('transform', 'translate(0,' + x2.range()[0] + ')');
7090           
7091       g.select('.nv-context .nv-y1.nv-axis').transition()
7092           .call(y3Axis);
7093           
7094
7095       y4Axis
7096         .scale(y4)
7097         .ticks( availableHeight2 / 36 )
7098         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7099
7100       g.select('.nv-context .nv-y2.nv-axis')
7101           .style('opacity', dataLines.length ? 1 : 0)
7102           .attr('transform', 'translate(' + x2.range()[1] + ',0)');
7103
7104       g.select('.nv-context .nv-y2.nv-axis').transition()
7105           .call(y4Axis);
7106           
7107       //------------------------------------------------------------
7108
7109       //============================================================
7110       // Event Handling/Dispatching (in chart's scope)
7111       //------------------------------------------------------------
7112
7113       legend.dispatch.on('stateChange', function(newState) { 
7114         chart.update();
7115       });
7116
7117       dispatch.on('tooltipShow', function(e) {
7118         if (tooltips) showTooltip(e, that.parentNode);
7119       });
7120
7121       //============================================================
7122
7123
7124       //============================================================
7125       // Functions
7126       //------------------------------------------------------------
7127
7128       // Taken from crossfilter (http://square.github.com/crossfilter/)
7129       function resizePath(d) {
7130         var e = +(d == 'e'),
7131             x = e ? 1 : -1,
7132             y = availableHeight2 / 3;
7133         return 'M' + (.5 * x) + ',' + y
7134             + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7135             + 'V' + (2 * y - 6)
7136             + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7137             + 'Z'
7138             + 'M' + (2.5 * x) + ',' + (y + 8)
7139             + 'V' + (2 * y - 8)
7140             + 'M' + (4.5 * x) + ',' + (y + 8)
7141             + 'V' + (2 * y - 8);
7142       }
7143
7144
7145       function updateBrushBG() {
7146         if (!brush.empty()) brush.extent(brushExtent);
7147         brushBG
7148             .data([brush.empty() ? x2.domain() : brushExtent])
7149             .each(function(d,i) {
7150               var leftWidth = x2(d[0]) - x2.range()[0],
7151                   rightWidth = x2.range()[1] - x2(d[1]);
7152               d3.select(this).select('.left')
7153                 .attr('width',  leftWidth < 0 ? 0 : leftWidth);
7154
7155               d3.select(this).select('.right')
7156                 .attr('x', x2(d[1]))
7157                 .attr('width', rightWidth < 0 ? 0 : rightWidth);
7158             });
7159       }
7160
7161
7162       function onBrush() {
7163         brushExtent = brush.empty() ? null : brush.extent();
7164         extent = brush.empty() ? x2.domain() : brush.extent();
7165
7166
7167         dispatch.brush({extent: extent, brush: brush});
7168
7169         updateBrushBG();
7170
7171
7172         //------------------------------------------------------------
7173         // Prepare Main (Focus) Bars and Lines
7174         
7175         bars
7176         .width(availableWidth)
7177         .height(availableHeight1)
7178         .color(data.map(function(d,i) {
7179           return d.color || color(d, i);
7180         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7181
7182
7183         lines
7184         .width(availableWidth)
7185         .height(availableHeight1)
7186         .color(data.map(function(d,i) {
7187           return d.color || color(d, i);
7188         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7189
7190         var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7191             .datum(!dataBars.length ? [{values:[]}] :
7192               dataBars
7193                 .map(function(d,i) {
7194                   return {
7195                     key: d.key,
7196                     values: d.values.filter(function(d,i) {
7197                       return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7198                     })
7199                   }
7200                 })
7201             );
7202         
7203         var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7204             .datum(dataLines[0].disabled ? [{values:[]}] :
7205               dataLines
7206                 .map(function(d,i) {
7207                   return {
7208                     key: d.key,
7209                     values: d.values.filter(function(d,i) {
7210                       return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7211                     })
7212                   }
7213                 })
7214              );
7215                  
7216         //------------------------------------------------------------
7217         
7218         
7219         //------------------------------------------------------------
7220         // Update Main (Focus) X Axis
7221
7222         if (dataBars.length) {
7223             x = bars.xScale();
7224         } else {
7225             x = lines.xScale();
7226         }
7227         
7228         xAxis
7229         .scale(x)
7230         .ticks( availableWidth / 100 )
7231         .tickSize(-availableHeight1, 0);
7232
7233         xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7234         
7235         g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7236           .call(xAxis);
7237         //------------------------------------------------------------
7238         
7239         
7240         //------------------------------------------------------------
7241         // Update Main (Focus) Bars and Lines
7242
7243         focusBarsWrap.transition().duration(transitionDuration).call(bars);
7244         focusLinesWrap.transition().duration(transitionDuration).call(lines);
7245         
7246         //------------------------------------------------------------
7247         
7248           
7249         //------------------------------------------------------------
7250         // Setup and Update Main (Focus) Y Axes
7251         
7252         g.select('.nv-focus .nv-x.nv-axis')
7253           .attr('transform', 'translate(0,' + y1.range()[0] + ')');
7254
7255
7256         y1Axis
7257         .scale(y1)
7258         .ticks( availableHeight1 / 36 )
7259         .tickSize(-availableWidth, 0);
7260
7261         g.select('.nv-focus .nv-y1.nv-axis')
7262           .style('opacity', dataBars.length ? 1 : 0);
7263
7264
7265         y2Axis
7266         .scale(y2)
7267         .ticks( availableHeight1 / 36 )
7268         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7269
7270         g.select('.nv-focus .nv-y2.nv-axis')
7271           .style('opacity', dataLines.length ? 1 : 0)
7272           .attr('transform', 'translate(' + x.range()[1] + ',0)');
7273
7274         g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7275             .call(y1Axis);
7276         g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7277             .call(y2Axis);
7278       }
7279
7280       //============================================================
7281
7282       onBrush();
7283
7284     });
7285
7286     return chart;
7287   }
7288
7289
7290   //============================================================
7291   // Event Handling/Dispatching (out of chart's scope)
7292   //------------------------------------------------------------
7293
7294   lines.dispatch.on('elementMouseover.tooltip', function(e) {
7295     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7296     dispatch.tooltipShow(e);
7297   });
7298
7299   lines.dispatch.on('elementMouseout.tooltip', function(e) {
7300     dispatch.tooltipHide(e);
7301   });
7302
7303   bars.dispatch.on('elementMouseover.tooltip', function(e) {
7304     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7305     dispatch.tooltipShow(e);
7306   });
7307
7308   bars.dispatch.on('elementMouseout.tooltip', function(e) {
7309     dispatch.tooltipHide(e);
7310   });
7311
7312   dispatch.on('tooltipHide', function() {
7313     if (tooltips) nv.tooltip.cleanup();
7314   });
7315
7316   //============================================================
7317
7318
7319   //============================================================
7320   // Expose Public Variables
7321   //------------------------------------------------------------
7322
7323   // expose chart's sub-components
7324   chart.dispatch = dispatch;
7325   chart.legend = legend;
7326   chart.lines = lines;
7327   chart.lines2 = lines2;
7328   chart.bars = bars;
7329   chart.bars2 = bars2;
7330   chart.xAxis = xAxis;
7331   chart.x2Axis = x2Axis;
7332   chart.y1Axis = y1Axis;
7333   chart.y2Axis = y2Axis;
7334   chart.y3Axis = y3Axis;
7335   chart.y4Axis = y4Axis;
7336
7337   d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7338   //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7339   //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7340
7341   chart.options = nv.utils.optionsFunc.bind(chart);
7342   
7343   chart.x = function(_) {
7344     if (!arguments.length) return getX;
7345     getX = _;
7346     lines.x(_);
7347     bars.x(_);
7348     return chart;
7349   };
7350
7351   chart.y = function(_) {
7352     if (!arguments.length) return getY;
7353     getY = _;
7354     lines.y(_);
7355     bars.y(_);
7356     return chart;
7357   };
7358
7359   chart.margin = function(_) {
7360     if (!arguments.length) return margin;
7361     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7362     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7363     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7364     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7365     return chart;
7366   };
7367
7368   chart.width = function(_) {
7369     if (!arguments.length) return width;
7370     width = _;
7371     return chart;
7372   };
7373
7374   chart.height = function(_) {
7375     if (!arguments.length) return height;
7376     height = _;
7377     return chart;
7378   };
7379
7380   chart.color = function(_) {
7381     if (!arguments.length) return color;
7382     color = nv.utils.getColor(_);
7383     legend.color(color);
7384     return chart;
7385   };
7386
7387   chart.showLegend = function(_) {
7388     if (!arguments.length) return showLegend;
7389     showLegend = _;
7390     return chart;
7391   };
7392
7393   chart.tooltips = function(_) {
7394     if (!arguments.length) return tooltips;
7395     tooltips = _;
7396     return chart;
7397   };
7398
7399   chart.tooltipContent = function(_) {
7400     if (!arguments.length) return tooltip;
7401     tooltip = _;
7402     return chart;
7403   };
7404
7405   chart.noData = function(_) {
7406     if (!arguments.length) return noData;
7407     noData = _;
7408     return chart;
7409   };
7410
7411   chart.brushExtent = function(_) {
7412     if (!arguments.length) return brushExtent;
7413     brushExtent = _;
7414     return chart;
7415   };
7416
7417
7418   //============================================================
7419
7420
7421   return chart;
7422 }
7423
7424 nv.models.multiBar = function() {
7425   "use strict";
7426   //============================================================
7427   // Public Variables with Default Settings
7428   //------------------------------------------------------------
7429
7430   var margin = {top: 0, right: 0, bottom: 0, left: 0}
7431     , width = 960
7432     , height = 500
7433     , x = d3.scale.ordinal()
7434     , y = d3.scale.linear()
7435     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7436     , getX = function(d) { return d.x }
7437     , getY = function(d) { return d.y }
7438     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7439     , clipEdge = true
7440     , stacked = false
7441     , color = nv.utils.defaultColor()
7442     , hideable = false
7443     , barColor = null // adding the ability to set the color for each rather than the whole group
7444     , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7445     , delay = 1200
7446     , xDomain
7447     , yDomain
7448     , xRange
7449     , yRange
7450     , groupSpacing = 0.1
7451     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7452     ;
7453
7454   //============================================================
7455
7456
7457   //============================================================
7458   // Private Variables
7459   //------------------------------------------------------------
7460
7461   var x0, y0 //used to store previous scales
7462       ;
7463
7464   //============================================================
7465
7466
7467   function chart(selection) {
7468     selection.each(function(data) {
7469       var availableWidth = width - margin.left - margin.right,
7470           availableHeight = height - margin.top - margin.bottom,
7471           container = d3.select(this);
7472
7473       if(hideable && data.length) hideable = [{
7474         values: data[0].values.map(function(d) {
7475         return {
7476           x: d.x,
7477           y: 0,
7478           series: d.series,
7479           size: 0.01
7480         };}
7481       )}];
7482
7483       if (stacked)
7484         data = d3.layout.stack()
7485                  .offset('zero')
7486                  .values(function(d){ return d.values })
7487                  .y(getY)
7488                  (!data.length && hideable ? hideable : data);
7489
7490
7491       //add series index to each data point for reference
7492       data = data.map(function(series, i) {
7493         series.values = series.values.map(function(point) {
7494           point.series = i;
7495           return point;
7496         });
7497         return series;
7498       });
7499
7500
7501       //------------------------------------------------------------
7502       // HACK for negative value stacking
7503       if (stacked)
7504         data[0].values.map(function(d,i) {
7505           var posBase = 0, negBase = 0;
7506           data.map(function(d) {
7507             var f = d.values[i]
7508             f.size = Math.abs(f.y);
7509             if (f.y<0)  {
7510               f.y1 = negBase;
7511               negBase = negBase - f.size;
7512             } else
7513             { 
7514               f.y1 = f.size + posBase;
7515               posBase = posBase + f.size;
7516             }
7517           });
7518         });
7519
7520       //------------------------------------------------------------
7521       // Setup Scales
7522
7523       // remap and flatten the data for use in calculating the scales' domains
7524       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7525             data.map(function(d) {
7526               return d.values.map(function(d,i) {
7527                 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7528               })
7529             });
7530
7531       x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7532           .rangeBands(xRange || [0, availableWidth], groupSpacing);
7533
7534       //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
7535       y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
7536           .range(yRange || [availableHeight, 0]);
7537
7538       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7539       if (x.domain()[0] === x.domain()[1])
7540         x.domain()[0] ?
7541             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7542           : x.domain([-1,1]);
7543
7544       if (y.domain()[0] === y.domain()[1])
7545         y.domain()[0] ?
7546             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7547           : y.domain([-1,1]);
7548
7549
7550       x0 = x0 || x;
7551       y0 = y0 || y;
7552
7553       //------------------------------------------------------------
7554
7555
7556       //------------------------------------------------------------
7557       // Setup containers and skeleton of chart
7558
7559       var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7560       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7561       var defsEnter = wrapEnter.append('defs');
7562       var gEnter = wrapEnter.append('g');
7563       var g = wrap.select('g')
7564
7565       gEnter.append('g').attr('class', 'nv-groups');
7566
7567       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7568
7569       //------------------------------------------------------------
7570
7571
7572
7573       defsEnter.append('clipPath')
7574           .attr('id', 'nv-edge-clip-' + id)
7575         .append('rect');
7576       wrap.select('#nv-edge-clip-' + id + ' rect')
7577           .attr('width', availableWidth)
7578           .attr('height', availableHeight);
7579
7580       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7581
7582
7583
7584       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7585           .data(function(d) { return d }, function(d,i) { return i });
7586       groups.enter().append('g')
7587           .style('stroke-opacity', 1e-6)
7588           .style('fill-opacity', 1e-6);
7589       groups.exit()
7590         .transition()
7591         .selectAll('rect.nv-bar')
7592         .delay(function(d,i) { 
7593              return i * delay/ data[0].values.length;
7594         })
7595           .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
7596           .attr('height', 0)
7597           .remove();
7598       groups
7599           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7600           .classed('hover', function(d) { return d.hover })
7601           .style('fill', function(d,i){ return color(d, i) })
7602           .style('stroke', function(d,i){ return color(d, i) });
7603       groups
7604           .transition()
7605           .style('stroke-opacity', 1)
7606           .style('fill-opacity', .75);
7607
7608
7609       var bars = groups.selectAll('rect.nv-bar')
7610           .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7611
7612       bars.exit().remove();
7613
7614
7615       var barsEnter = bars.enter().append('rect')
7616           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7617           .attr('x', function(d,i,j) {
7618               return stacked ? 0 : (j * x.rangeBand() / data.length )
7619           })
7620           .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
7621           .attr('height', 0)
7622           .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
7623           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7624           ;
7625       bars
7626           .style('fill', function(d,i,j){ return color(d, j, i);  })
7627           .style('stroke', function(d,i,j){ return color(d, j, i); })
7628           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7629             d3.select(this).classed('hover', true);
7630             dispatch.elementMouseover({
7631               value: getY(d,i),
7632               point: d,
7633               series: data[d.series],
7634               pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
7635               pointIndex: i,
7636               seriesIndex: d.series,
7637               e: d3.event
7638             });
7639           })
7640           .on('mouseout', function(d,i) {
7641             d3.select(this).classed('hover', false);
7642             dispatch.elementMouseout({
7643               value: getY(d,i),
7644               point: d,
7645               series: data[d.series],
7646               pointIndex: i,
7647               seriesIndex: d.series,
7648               e: d3.event
7649             });
7650           })
7651           .on('click', function(d,i) {
7652             dispatch.elementClick({
7653               value: getY(d,i),
7654               point: d,
7655               series: data[d.series],
7656               pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
7657               pointIndex: i,
7658               seriesIndex: d.series,
7659               e: d3.event
7660             });
7661             d3.event.stopPropagation();
7662           })
7663           .on('dblclick', function(d,i) {
7664             dispatch.elementDblClick({
7665               value: getY(d,i),
7666               point: d,
7667               series: data[d.series],
7668               pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
7669               pointIndex: i,
7670               seriesIndex: d.series,
7671               e: d3.event
7672             });
7673             d3.event.stopPropagation();
7674           });
7675       bars
7676           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7677           .transition()
7678           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7679
7680       if (barColor) {
7681         if (!disabled) disabled = data.map(function() { return true });
7682         bars
7683           .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(); })
7684           .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(); });
7685       }
7686
7687
7688       if (stacked)
7689           bars.transition()
7690             .delay(function(d,i) { 
7691
7692                   return i * delay / data[0].values.length;
7693             })
7694             .attr('y', function(d,i) {
7695
7696               return y((stacked ? d.y1 : 0));
7697             })
7698             .attr('height', function(d,i) {
7699               return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
7700             })
7701             .attr('x', function(d,i) {
7702                   return stacked ? 0 : (d.series * x.rangeBand() / data.length )
7703             })
7704             .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
7705       else
7706           bars.transition()
7707             .delay(function(d,i) { 
7708                 return i * delay/ data[0].values.length;
7709             })
7710             .attr('x', function(d,i) {
7711               return d.series * x.rangeBand() / data.length
7712             })
7713             .attr('width', x.rangeBand() / data.length)
7714             .attr('y', function(d,i) {
7715                 return getY(d,i) < 0 ?
7716                         y(0) :
7717                         y(0) - y(getY(d,i)) < 1 ?
7718                           y(0) - 1 :
7719                         y(getY(d,i)) || 0;
7720             })
7721             .attr('height', function(d,i) {
7722                 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7723             });
7724
7725
7726
7727       //store old scales for use in transitions on update
7728       x0 = x.copy();
7729       y0 = y.copy();
7730
7731     });
7732
7733     return chart;
7734   }
7735
7736
7737   //============================================================
7738   // Expose Public Variables
7739   //------------------------------------------------------------
7740
7741   chart.dispatch = dispatch;
7742
7743   chart.options = nv.utils.optionsFunc.bind(chart);
7744   
7745   chart.x = function(_) {
7746     if (!arguments.length) return getX;
7747     getX = _;
7748     return chart;
7749   };
7750
7751   chart.y = function(_) {
7752     if (!arguments.length) return getY;
7753     getY = _;
7754     return chart;
7755   };
7756
7757   chart.margin = function(_) {
7758     if (!arguments.length) return margin;
7759     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7760     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7761     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7762     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7763     return chart;
7764   };
7765
7766   chart.width = function(_) {
7767     if (!arguments.length) return width;
7768     width = _;
7769     return chart;
7770   };
7771
7772   chart.height = function(_) {
7773     if (!arguments.length) return height;
7774     height = _;
7775     return chart;
7776   };
7777
7778   chart.xScale = function(_) {
7779     if (!arguments.length) return x;
7780     x = _;
7781     return chart;
7782   };
7783
7784   chart.yScale = function(_) {
7785     if (!arguments.length) return y;
7786     y = _;
7787     return chart;
7788   };
7789
7790   chart.xDomain = function(_) {
7791     if (!arguments.length) return xDomain;
7792     xDomain = _;
7793     return chart;
7794   };
7795
7796   chart.yDomain = function(_) {
7797     if (!arguments.length) return yDomain;
7798     yDomain = _;
7799     return chart;
7800   };
7801
7802   chart.xRange = function(_) {
7803     if (!arguments.length) return xRange;
7804     xRange = _;
7805     return chart;
7806   };
7807
7808   chart.yRange = function(_) {
7809     if (!arguments.length) return yRange;
7810     yRange = _;
7811     return chart;
7812   };
7813
7814   chart.forceY = function(_) {
7815     if (!arguments.length) return forceY;
7816     forceY = _;
7817     return chart;
7818   };
7819
7820   chart.stacked = function(_) {
7821     if (!arguments.length) return stacked;
7822     stacked = _;
7823     return chart;
7824   };
7825
7826   chart.clipEdge = function(_) {
7827     if (!arguments.length) return clipEdge;
7828     clipEdge = _;
7829     return chart;
7830   };
7831
7832   chart.color = function(_) {
7833     if (!arguments.length) return color;
7834     color = nv.utils.getColor(_);
7835     return chart;
7836   };
7837
7838   chart.barColor = function(_) {
7839     if (!arguments.length) return barColor;
7840     barColor = nv.utils.getColor(_);
7841     return chart;
7842   };
7843
7844   chart.disabled = function(_) {
7845     if (!arguments.length) return disabled;
7846     disabled = _;
7847     return chart;
7848   };
7849
7850   chart.id = function(_) {
7851     if (!arguments.length) return id;
7852     id = _;
7853     return chart;
7854   };
7855
7856   chart.hideable = function(_) {
7857     if (!arguments.length) return hideable;
7858     hideable = _;
7859     return chart;
7860   };
7861
7862   chart.delay = function(_) {
7863     if (!arguments.length) return delay;
7864     delay = _;
7865     return chart;
7866   };
7867
7868   chart.groupSpacing = function(_) {
7869     if (!arguments.length) return groupSpacing;
7870     groupSpacing = _;
7871     return chart;
7872   };
7873
7874   //============================================================
7875
7876
7877   return chart;
7878 }
7879
7880 nv.models.multiBarChart = function() {
7881   "use strict";
7882   //============================================================
7883   // Public Variables with Default Settings
7884   //------------------------------------------------------------
7885
7886   var multibar = nv.models.multiBar()
7887     , xAxis = nv.models.axis()
7888     , yAxis = nv.models.axis()
7889     , legend = nv.models.legend()
7890     , controls = nv.models.legend()
7891     ;
7892
7893   var margin = {top: 30, right: 20, bottom: 50, left: 60}
7894     , width = null
7895     , height = null
7896     , color = nv.utils.defaultColor()
7897     , showControls = true
7898     , showLegend = true
7899     , showXAxis = true
7900     , showYAxis = true
7901     , rightAlignYAxis = false
7902     , reduceXTicks = true // if false a tick will show for every data point
7903     , staggerLabels = false
7904     , rotateLabels = 0
7905     , tooltips = true
7906     , tooltip = function(key, x, y, e, graph) {
7907         return '<h3>' + key + '</h3>' +
7908                '<p>' +  y + ' on ' + x + '</p>'
7909       }
7910     , x //can be accessed via chart.xScale()
7911     , y //can be accessed via chart.yScale()
7912     , state = { stacked: false }
7913     , defaultState = null
7914     , noData = "No Data Available."
7915     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
7916     , controlWidth = function() { return showControls ? 180 : 0 }
7917     , transitionDuration = 250
7918     ;
7919
7920   multibar
7921     .stacked(false)
7922     ;
7923   xAxis
7924     .orient('bottom')
7925     .tickPadding(7)
7926     .highlightZero(true)
7927     .showMaxMin(false)
7928     .tickFormat(function(d) { return d })
7929     ;
7930   yAxis
7931     .orient((rightAlignYAxis) ? 'right' : 'left')
7932     .tickFormat(d3.format(',.1f'))
7933     ;
7934
7935   controls.updateState(false);
7936   //============================================================
7937
7938
7939   //============================================================
7940   // Private Variables
7941   //------------------------------------------------------------
7942
7943   var showTooltip = function(e, offsetElement) {
7944     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7945         top = e.pos[1] + ( offsetElement.offsetTop || 0),
7946         x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7947         y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7948         content = tooltip(e.series.key, x, y, e, chart);
7949
7950     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
7951   };
7952
7953   //============================================================
7954
7955
7956   function chart(selection) {
7957     selection.each(function(data) {
7958       var container = d3.select(this),
7959           that = this;
7960
7961       var availableWidth = (width  || parseInt(container.style('width')) || 960)
7962                              - margin.left - margin.right,
7963           availableHeight = (height || parseInt(container.style('height')) || 400)
7964                              - margin.top - margin.bottom;
7965
7966       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7967       chart.container = this;
7968
7969       //set state.disabled
7970       state.disabled = data.map(function(d) { return !!d.disabled });
7971
7972       if (!defaultState) {
7973         var key;
7974         defaultState = {};
7975         for (key in state) {
7976           if (state[key] instanceof Array)
7977             defaultState[key] = state[key].slice(0);
7978           else
7979             defaultState[key] = state[key];
7980         }
7981       }
7982       //------------------------------------------------------------
7983       // Display noData message if there's nothing to show.
7984
7985       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7986         var noDataText = container.selectAll('.nv-noData').data([noData]);
7987
7988         noDataText.enter().append('text')
7989           .attr('class', 'nvd3 nv-noData')
7990           .attr('dy', '-.7em')
7991           .style('text-anchor', 'middle');
7992
7993         noDataText
7994           .attr('x', margin.left + availableWidth / 2)
7995           .attr('y', margin.top + availableHeight / 2)
7996           .text(function(d) { return d });
7997
7998         return chart;
7999       } else {
8000         container.selectAll('.nv-noData').remove();
8001       }
8002
8003       //------------------------------------------------------------
8004
8005
8006       //------------------------------------------------------------
8007       // Setup Scales
8008
8009       x = multibar.xScale();
8010       y = multibar.yScale();
8011
8012       //------------------------------------------------------------
8013
8014
8015       //------------------------------------------------------------
8016       // Setup containers and skeleton of chart
8017
8018       var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8019       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8020       var g = wrap.select('g');
8021
8022       gEnter.append('g').attr('class', 'nv-x nv-axis');
8023       gEnter.append('g').attr('class', 'nv-y nv-axis');
8024       gEnter.append('g').attr('class', 'nv-barsWrap');
8025       gEnter.append('g').attr('class', 'nv-legendWrap');
8026       gEnter.append('g').attr('class', 'nv-controlsWrap');
8027
8028       //------------------------------------------------------------
8029
8030
8031       //------------------------------------------------------------
8032       // Legend
8033
8034       if (showLegend) {
8035         legend.width(availableWidth - controlWidth());
8036
8037         if (multibar.barColor())
8038           data.forEach(function(series,i) {
8039             series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8040           })
8041
8042         g.select('.nv-legendWrap')
8043             .datum(data)
8044             .call(legend);
8045
8046         if ( margin.top != legend.height()) {
8047           margin.top = legend.height();
8048           availableHeight = (height || parseInt(container.style('height')) || 400)
8049                              - margin.top - margin.bottom;
8050         }
8051
8052         g.select('.nv-legendWrap')
8053             .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8054       }
8055
8056       //------------------------------------------------------------
8057
8058
8059       //------------------------------------------------------------
8060       // Controls
8061
8062       if (showControls) {
8063         var controlsData = [
8064           { key: 'Grouped', disabled: multibar.stacked() },
8065           { key: 'Stacked', disabled: !multibar.stacked() }
8066         ];
8067
8068         controls.width(controlWidth()).color(['#444', '#444', '#444']);
8069         g.select('.nv-controlsWrap')
8070             .datum(controlsData)
8071             .attr('transform', 'translate(0,' + (-margin.top) +')')
8072             .call(controls);
8073       }
8074
8075       //------------------------------------------------------------
8076
8077
8078       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8079
8080       if (rightAlignYAxis) {
8081           g.select(".nv-y.nv-axis")
8082               .attr("transform", "translate(" + availableWidth + ",0)");
8083       }
8084
8085       //------------------------------------------------------------
8086       // Main Chart Component(s)
8087
8088       multibar
8089         .disabled(data.map(function(series) { return series.disabled }))
8090         .width(availableWidth)
8091         .height(availableHeight)
8092         .color(data.map(function(d,i) {
8093           return d.color || color(d, i);
8094         }).filter(function(d,i) { return !data[i].disabled }))
8095
8096
8097       var barsWrap = g.select('.nv-barsWrap')
8098           .datum(data.filter(function(d) { return !d.disabled }))
8099
8100       barsWrap.transition().call(multibar);
8101
8102       //------------------------------------------------------------
8103
8104
8105       //------------------------------------------------------------
8106       // Setup Axes
8107
8108       if (showXAxis) {
8109           xAxis
8110             .scale(x)
8111             .ticks( availableWidth / 100 )
8112             .tickSize(-availableHeight, 0);
8113
8114           g.select('.nv-x.nv-axis')
8115               .attr('transform', 'translate(0,' + y.range()[0] + ')');
8116           g.select('.nv-x.nv-axis').transition()
8117               .call(xAxis);
8118
8119           var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8120
8121           xTicks
8122               .selectAll('line, text')
8123               .style('opacity', 1)
8124
8125           if (staggerLabels) {
8126               var getTranslate = function(x,y) {
8127                   return "translate(" + x + "," + y + ")";
8128               };
8129
8130               var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
8131               // Issue #140
8132               xTicks
8133                 .selectAll("text")
8134                 .attr('transform', function(d,i,j) { 
8135                     return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8136                   });
8137
8138               var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8139               g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8140                 .attr("transform", function(d,i) {
8141                     return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8142                 });
8143           }
8144
8145           if (reduceXTicks)
8146             xTicks
8147               .filter(function(d,i) {
8148                   return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8149                 })
8150               .selectAll('text, line')
8151               .style('opacity', 0);
8152
8153           if(rotateLabels)
8154             xTicks
8155               .selectAll('.tick text')
8156               .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8157               .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8158           
8159           g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8160               .style('opacity', 1);
8161       }
8162
8163
8164       if (showYAxis) {      
8165           yAxis
8166             .scale(y)
8167             .ticks( availableHeight / 36 )
8168             .tickSize( -availableWidth, 0);
8169
8170           g.select('.nv-y.nv-axis').transition()
8171               .call(yAxis);
8172       }
8173
8174
8175       //------------------------------------------------------------
8176
8177
8178
8179       //============================================================
8180       // Event Handling/Dispatching (in chart's scope)
8181       //------------------------------------------------------------
8182
8183       legend.dispatch.on('stateChange', function(newState) { 
8184         state = newState;
8185         dispatch.stateChange(state);
8186         chart.update();
8187       });
8188
8189       controls.dispatch.on('legendClick', function(d,i) {
8190         if (!d.disabled) return;
8191         controlsData = controlsData.map(function(s) {
8192           s.disabled = true;
8193           return s;
8194         });
8195         d.disabled = false;
8196
8197         switch (d.key) {
8198           case 'Grouped':
8199             multibar.stacked(false);
8200             break;
8201           case 'Stacked':
8202             multibar.stacked(true);
8203             break;
8204         }
8205
8206         state.stacked = multibar.stacked();
8207         dispatch.stateChange(state);
8208
8209         chart.update();
8210       });
8211
8212       dispatch.on('tooltipShow', function(e) {
8213         if (tooltips) showTooltip(e, that.parentNode)
8214       });
8215
8216       // Update chart from a state object passed to event handler
8217       dispatch.on('changeState', function(e) {
8218
8219         if (typeof e.disabled !== 'undefined') {
8220           data.forEach(function(series,i) {
8221             series.disabled = e.disabled[i];
8222           });
8223
8224           state.disabled = e.disabled;
8225         }
8226
8227         if (typeof e.stacked !== 'undefined') {
8228           multibar.stacked(e.stacked);
8229           state.stacked = e.stacked;
8230         }
8231
8232         chart.update();
8233       });
8234
8235       //============================================================
8236
8237
8238     });
8239
8240     return chart;
8241   }
8242
8243
8244   //============================================================
8245   // Event Handling/Dispatching (out of chart's scope)
8246   //------------------------------------------------------------
8247
8248   multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8249     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8250     dispatch.tooltipShow(e);
8251   });
8252
8253   multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8254     dispatch.tooltipHide(e);
8255   });
8256   dispatch.on('tooltipHide', function() {
8257     if (tooltips) nv.tooltip.cleanup();
8258   });
8259
8260   //============================================================
8261
8262
8263   //============================================================
8264   // Expose Public Variables
8265   //------------------------------------------------------------
8266
8267   // expose chart's sub-components
8268   chart.dispatch = dispatch;
8269   chart.multibar = multibar;
8270   chart.legend = legend;
8271   chart.xAxis = xAxis;
8272   chart.yAxis = yAxis;
8273
8274   d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8275    'id', 'stacked', 'delay', 'barColor','groupSpacing');
8276
8277   chart.options = nv.utils.optionsFunc.bind(chart);
8278   
8279   chart.margin = function(_) {
8280     if (!arguments.length) return margin;
8281     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8282     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8283     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8284     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8285     return chart;
8286   };
8287
8288   chart.width = function(_) {
8289     if (!arguments.length) return width;
8290     width = _;
8291     return chart;
8292   };
8293
8294   chart.height = function(_) {
8295     if (!arguments.length) return height;
8296     height = _;
8297     return chart;
8298   };
8299
8300   chart.color = function(_) {
8301     if (!arguments.length) return color;
8302     color = nv.utils.getColor(_);
8303     legend.color(color);
8304     return chart;
8305   };
8306
8307   chart.showControls = function(_) {
8308     if (!arguments.length) return showControls;
8309     showControls = _;
8310     return chart;
8311   };
8312
8313   chart.showLegend = function(_) {
8314     if (!arguments.length) return showLegend;
8315     showLegend = _;
8316     return chart;
8317   };
8318
8319   chart.showXAxis = function(_) {
8320     if (!arguments.length) return showXAxis;
8321     showXAxis = _;
8322     return chart;
8323   };
8324
8325   chart.showYAxis = function(_) {
8326     if (!arguments.length) return showYAxis;
8327     showYAxis = _;
8328     return chart;
8329   };
8330
8331   chart.rightAlignYAxis = function(_) {
8332     if(!arguments.length) return rightAlignYAxis;
8333     rightAlignYAxis = _;
8334     yAxis.orient( (_) ? 'right' : 'left');
8335     return chart;
8336   };
8337
8338   chart.reduceXTicks= function(_) {
8339     if (!arguments.length) return reduceXTicks;
8340     reduceXTicks = _;
8341     return chart;
8342   };
8343
8344   chart.rotateLabels = function(_) {
8345     if (!arguments.length) return rotateLabels;
8346     rotateLabels = _;
8347     return chart;
8348   }
8349
8350   chart.staggerLabels = function(_) {
8351     if (!arguments.length) return staggerLabels;
8352     staggerLabels = _;
8353     return chart;
8354   };
8355
8356   chart.tooltip = function(_) {
8357     if (!arguments.length) return tooltip;
8358     tooltip = _;
8359     return chart;
8360   };
8361
8362   chart.tooltips = function(_) {
8363     if (!arguments.length) return tooltips;
8364     tooltips = _;
8365     return chart;
8366   };
8367
8368   chart.tooltipContent = function(_) {
8369     if (!arguments.length) return tooltip;
8370     tooltip = _;
8371     return chart;
8372   };
8373
8374   chart.state = function(_) {
8375     if (!arguments.length) return state;
8376     state = _;
8377     return chart;
8378   };
8379
8380   chart.defaultState = function(_) {
8381     if (!arguments.length) return defaultState;
8382     defaultState = _;
8383     return chart;
8384   };
8385   
8386   chart.noData = function(_) {
8387     if (!arguments.length) return noData;
8388     noData = _;
8389     return chart;
8390   };
8391
8392   chart.transitionDuration = function(_) {
8393     if (!arguments.length) return transitionDuration;
8394     transitionDuration = _;
8395     return chart;
8396   };
8397
8398   //============================================================
8399
8400
8401   return chart;
8402 }
8403
8404 nv.models.multiBarHorizontal = function() {
8405   "use strict";
8406   //============================================================
8407   // Public Variables with Default Settings
8408   //------------------------------------------------------------
8409
8410   var margin = {top: 0, right: 0, bottom: 0, left: 0}
8411     , width = 960
8412     , height = 500
8413     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8414     , x = d3.scale.ordinal()
8415     , y = d3.scale.linear()
8416     , getX = function(d) { return d.x }
8417     , getY = function(d) { return d.y }
8418     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8419     , color = nv.utils.defaultColor()
8420     , barColor = null // adding the ability to set the color for each rather than the whole group
8421     , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8422     , stacked = false
8423     , showValues = false
8424     , valuePadding = 60
8425     , valueFormat = d3.format(',.2f')
8426     , delay = 1200
8427     , xDomain
8428     , yDomain
8429     , xRange
8430     , yRange
8431     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8432     ;
8433
8434   //============================================================
8435
8436
8437   //============================================================
8438   // Private Variables
8439   //------------------------------------------------------------
8440
8441   var x0, y0 //used to store previous scales
8442       ;
8443
8444   //============================================================
8445
8446
8447   function chart(selection) {
8448     selection.each(function(data) {
8449       var availableWidth = width - margin.left - margin.right,
8450           availableHeight = height - margin.top - margin.bottom,
8451           container = d3.select(this);
8452
8453
8454       if (stacked)
8455         data = d3.layout.stack()
8456                  .offset('zero')
8457                  .values(function(d){ return d.values })
8458                  .y(getY)
8459                  (data);
8460
8461
8462       //add series index to each data point for reference
8463       data = data.map(function(series, i) {
8464         series.values = series.values.map(function(point) {
8465           point.series = i;
8466           return point;
8467         });
8468         return series;
8469       });
8470
8471
8472
8473       //------------------------------------------------------------
8474       // HACK for negative value stacking
8475       if (stacked)
8476         data[0].values.map(function(d,i) {
8477           var posBase = 0, negBase = 0;
8478           data.map(function(d) {
8479             var f = d.values[i]
8480             f.size = Math.abs(f.y);
8481             if (f.y<0)  {
8482               f.y1 = negBase - f.size;
8483               negBase = negBase - f.size;
8484             } else
8485             { 
8486               f.y1 = posBase;
8487               posBase = posBase + f.size;
8488             }
8489           });
8490         });
8491
8492
8493
8494       //------------------------------------------------------------
8495       // Setup Scales
8496
8497       // remap and flatten the data for use in calculating the scales' domains
8498       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8499             data.map(function(d) {
8500               return d.values.map(function(d,i) {
8501                 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8502               })
8503             });
8504
8505       x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8506           .rangeBands(xRange || [0, availableHeight], .1);
8507
8508       //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
8509       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)))
8510
8511       if (showValues && !stacked)
8512         y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8513       else
8514         y.range(yRange || [0, availableWidth]);
8515
8516       x0 = x0 || x;
8517       y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8518
8519       //------------------------------------------------------------
8520
8521
8522       //------------------------------------------------------------
8523       // Setup containers and skeleton of chart
8524
8525       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8526       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8527       var defsEnter = wrapEnter.append('defs');
8528       var gEnter = wrapEnter.append('g');
8529       var g = wrap.select('g');
8530
8531       gEnter.append('g').attr('class', 'nv-groups');
8532
8533       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8534
8535       //------------------------------------------------------------
8536
8537
8538
8539       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8540           .data(function(d) { return d }, function(d,i) { return i });
8541       groups.enter().append('g')
8542           .style('stroke-opacity', 1e-6)
8543           .style('fill-opacity', 1e-6);
8544       groups.exit().transition()
8545           .style('stroke-opacity', 1e-6)
8546           .style('fill-opacity', 1e-6)
8547           .remove();
8548       groups
8549           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8550           .classed('hover', function(d) { return d.hover })
8551           .style('fill', function(d,i){ return color(d, i) })
8552           .style('stroke', function(d,i){ return color(d, i) });
8553       groups.transition()
8554           .style('stroke-opacity', 1)
8555           .style('fill-opacity', .75);
8556
8557
8558       var bars = groups.selectAll('g.nv-bar')
8559           .data(function(d) { return d.values });
8560
8561       bars.exit().remove();
8562
8563
8564       var barsEnter = bars.enter().append('g')
8565           .attr('transform', function(d,i,j) {
8566               return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8567           });
8568
8569       barsEnter.append('rect')
8570           .attr('width', 0)
8571           .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8572
8573       bars
8574           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8575             d3.select(this).classed('hover', true);
8576             dispatch.elementMouseover({
8577               value: getY(d,i),
8578               point: d,
8579               series: data[d.series],
8580               pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
8581               pointIndex: i,
8582               seriesIndex: d.series,
8583               e: d3.event
8584             });
8585           })
8586           .on('mouseout', function(d,i) {
8587             d3.select(this).classed('hover', false);
8588             dispatch.elementMouseout({
8589               value: getY(d,i),
8590               point: d,
8591               series: data[d.series],
8592               pointIndex: i,
8593               seriesIndex: d.series,
8594               e: d3.event
8595             });
8596           })
8597           .on('click', function(d,i) {
8598             dispatch.elementClick({
8599               value: getY(d,i),
8600               point: d,
8601               series: data[d.series],
8602               pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
8603               pointIndex: i,
8604               seriesIndex: d.series,
8605               e: d3.event
8606             });
8607             d3.event.stopPropagation();
8608           })
8609           .on('dblclick', function(d,i) {
8610             dispatch.elementDblClick({
8611               value: getY(d,i),
8612               point: d,
8613               series: data[d.series],
8614               pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))],  // TODO: Figure out why the value appears to be shifted
8615               pointIndex: i,
8616               seriesIndex: d.series,
8617               e: d3.event
8618             });
8619             d3.event.stopPropagation();
8620           });
8621
8622
8623       barsEnter.append('text');
8624
8625       if (showValues && !stacked) {
8626         bars.select('text')
8627             .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8628             .attr('y', x.rangeBand() / (data.length * 2))
8629             .attr('dy', '.32em')
8630             .text(function(d,i) { return valueFormat(getY(d,i)) })
8631         bars.transition()
8632           .select('text')
8633             .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8634       } else {
8635         bars.selectAll('text').text('');
8636       }
8637
8638       bars
8639           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8640
8641       if (barColor) {
8642         if (!disabled) disabled = data.map(function() { return true });
8643         bars
8644           .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(); })
8645           .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(); });
8646       }
8647
8648       if (stacked)
8649         bars.transition()
8650             .attr('transform', function(d,i) {
8651               return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8652             })
8653           .select('rect')
8654             .attr('width', function(d,i) {
8655               return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8656             })
8657             .attr('height', x.rangeBand() );
8658       else
8659         bars.transition()
8660             .attr('transform', function(d,i) {
8661               //TODO: stacked must be all positive or all negative, not both?
8662               return 'translate(' + 
8663               (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8664               + ',' +
8665               (d.series * x.rangeBand() / data.length
8666               +
8667               x(getX(d,i)) )
8668               + ')'
8669             })
8670           .select('rect')
8671             .attr('height', x.rangeBand() / data.length )
8672             .attr('width', function(d,i) {
8673               return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8674             });
8675
8676
8677       //store old scales for use in transitions on update
8678       x0 = x.copy();
8679       y0 = y.copy();
8680
8681     });
8682
8683     return chart;
8684   }
8685
8686
8687   //============================================================
8688   // Expose Public Variables
8689   //------------------------------------------------------------
8690
8691   chart.dispatch = dispatch;
8692
8693   chart.options = nv.utils.optionsFunc.bind(chart);
8694   
8695   chart.x = function(_) {
8696     if (!arguments.length) return getX;
8697     getX = _;
8698     return chart;
8699   };
8700
8701   chart.y = function(_) {
8702     if (!arguments.length) return getY;
8703     getY = _;
8704     return chart;
8705   };
8706
8707   chart.margin = function(_) {
8708     if (!arguments.length) return margin;
8709     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8710     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8711     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8712     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8713     return chart;
8714   };
8715
8716   chart.width = function(_) {
8717     if (!arguments.length) return width;
8718     width = _;
8719     return chart;
8720   };
8721
8722   chart.height = function(_) {
8723     if (!arguments.length) return height;
8724     height = _;
8725     return chart;
8726   };
8727
8728   chart.xScale = function(_) {
8729     if (!arguments.length) return x;
8730     x = _;
8731     return chart;
8732   };
8733
8734   chart.yScale = function(_) {
8735     if (!arguments.length) return y;
8736     y = _;
8737     return chart;
8738   };
8739
8740   chart.xDomain = function(_) {
8741     if (!arguments.length) return xDomain;
8742     xDomain = _;
8743     return chart;
8744   };
8745
8746   chart.yDomain = function(_) {
8747     if (!arguments.length) return yDomain;
8748     yDomain = _;
8749     return chart;
8750   };
8751
8752   chart.xRange = function(_) {
8753     if (!arguments.length) return xRange;
8754     xRange = _;
8755     return chart;
8756   };
8757
8758   chart.yRange = function(_) {
8759     if (!arguments.length) return yRange;
8760     yRange = _;
8761     return chart;
8762   };
8763
8764   chart.forceY = function(_) {
8765     if (!arguments.length) return forceY;
8766     forceY = _;
8767     return chart;
8768   };
8769
8770   chart.stacked = function(_) {
8771     if (!arguments.length) return stacked;
8772     stacked = _;
8773     return chart;
8774   };
8775
8776   chart.color = function(_) {
8777     if (!arguments.length) return color;
8778     color = nv.utils.getColor(_);
8779     return chart;
8780   };
8781
8782   chart.barColor = function(_) {
8783     if (!arguments.length) return barColor;
8784     barColor = nv.utils.getColor(_);
8785     return chart;
8786   };
8787
8788   chart.disabled = function(_) {
8789     if (!arguments.length) return disabled;
8790     disabled = _;
8791     return chart;
8792   };
8793
8794   chart.id = function(_) {
8795     if (!arguments.length) return id;
8796     id = _;
8797     return chart;
8798   };
8799
8800   chart.delay = function(_) {
8801     if (!arguments.length) return delay;
8802     delay = _;
8803     return chart;
8804   };
8805
8806   chart.showValues = function(_) {
8807     if (!arguments.length) return showValues;
8808     showValues = _;
8809     return chart;
8810   };
8811
8812   chart.valueFormat= function(_) {
8813     if (!arguments.length) return valueFormat;
8814     valueFormat = _;
8815     return chart;
8816   };
8817
8818   chart.valuePadding = function(_) {
8819     if (!arguments.length) return valuePadding;
8820     valuePadding = _;
8821     return chart;
8822   };
8823
8824   //============================================================
8825
8826
8827   return chart;
8828 }
8829
8830 nv.models.multiBarHorizontalChart = function() {
8831   "use strict";
8832   //============================================================
8833   // Public Variables with Default Settings
8834   //------------------------------------------------------------
8835
8836   var multibar = nv.models.multiBarHorizontal()
8837     , xAxis = nv.models.axis()
8838     , yAxis = nv.models.axis()
8839     , legend = nv.models.legend().height(30)
8840     , controls = nv.models.legend().height(30)
8841     ;
8842
8843   var margin = {top: 30, right: 20, bottom: 50, left: 60}
8844     , width = null
8845     , height = null
8846     , color = nv.utils.defaultColor()
8847     , showControls = true
8848     , showLegend = true
8849     , stacked = false
8850     , tooltips = true
8851     , tooltip = function(key, x, y, e, graph) {
8852         return '<h3>' + key + ' - ' + x + '</h3>' +
8853                '<p>' +  y + '</p>'
8854       }
8855     , x //can be accessed via chart.xScale()
8856     , y //can be accessed via chart.yScale()
8857     , state = { stacked: stacked }
8858     , defaultState = null
8859     , noData = 'No Data Available.'
8860     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
8861     , controlWidth = function() { return showControls ? 180 : 0 }
8862     , transitionDuration = 250
8863     ;
8864
8865   multibar
8866     .stacked(stacked)
8867     ;
8868   xAxis
8869     .orient('left')
8870     .tickPadding(5)
8871     .highlightZero(false)
8872     .showMaxMin(false)
8873     .tickFormat(function(d) { return d })
8874     ;
8875   yAxis
8876     .orient('bottom')
8877     .tickFormat(d3.format(',.1f'))
8878     ;
8879
8880   controls.updateState(false);
8881   //============================================================
8882
8883
8884   //============================================================
8885   // Private Variables
8886   //------------------------------------------------------------
8887
8888   var showTooltip = function(e, offsetElement) {
8889     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8890         top = e.pos[1] + ( offsetElement.offsetTop || 0),
8891         x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8892         y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8893         content = tooltip(e.series.key, x, y, e, chart);
8894
8895     nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
8896   };
8897
8898   //============================================================
8899
8900
8901   function chart(selection) {
8902     selection.each(function(data) {
8903       var container = d3.select(this),
8904           that = this;
8905
8906       var availableWidth = (width  || parseInt(container.style('width')) || 960)
8907                              - margin.left - margin.right,
8908           availableHeight = (height || parseInt(container.style('height')) || 400)
8909                              - margin.top - margin.bottom;
8910
8911       chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
8912       chart.container = this;
8913
8914       //set state.disabled
8915       state.disabled = data.map(function(d) { return !!d.disabled });
8916
8917       if (!defaultState) {
8918         var key;
8919         defaultState = {};
8920         for (key in state) {
8921           if (state[key] instanceof Array)
8922             defaultState[key] = state[key].slice(0);
8923           else
8924             defaultState[key] = state[key];
8925         }
8926       }
8927
8928       //------------------------------------------------------------
8929       // Display No Data message if there's nothing to show.
8930
8931       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8932         var noDataText = container.selectAll('.nv-noData').data([noData]);
8933
8934         noDataText.enter().append('text')
8935           .attr('class', 'nvd3 nv-noData')
8936           .attr('dy', '-.7em')
8937           .style('text-anchor', 'middle');
8938
8939         noDataText
8940           .attr('x', margin.left + availableWidth / 2)
8941           .attr('y', margin.top + availableHeight / 2)
8942           .text(function(d) { return d });
8943
8944         return chart;
8945       } else {
8946         container.selectAll('.nv-noData').remove();
8947       }
8948
8949       //------------------------------------------------------------
8950
8951
8952       //------------------------------------------------------------
8953       // Setup Scales
8954
8955       x = multibar.xScale();
8956       y = multibar.yScale();
8957
8958       //------------------------------------------------------------
8959
8960
8961       //------------------------------------------------------------
8962       // Setup containers and skeleton of chart
8963
8964       var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8965       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8966       var g = wrap.select('g');
8967
8968       gEnter.append('g').attr('class', 'nv-x nv-axis');
8969       gEnter.append('g').attr('class', 'nv-y nv-axis');
8970       gEnter.append('g').attr('class', 'nv-barsWrap');
8971       gEnter.append('g').attr('class', 'nv-legendWrap');
8972       gEnter.append('g').attr('class', 'nv-controlsWrap');
8973
8974       //------------------------------------------------------------
8975
8976
8977       //------------------------------------------------------------
8978       // Legend
8979
8980       if (showLegend) {
8981         legend.width(availableWidth - controlWidth());
8982
8983         if (multibar.barColor())
8984           data.forEach(function(series,i) {
8985             series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8986           })
8987
8988         g.select('.nv-legendWrap')
8989             .datum(data)
8990             .call(legend);
8991
8992         if ( margin.top != legend.height()) {
8993           margin.top = legend.height();
8994           availableHeight = (height || parseInt(container.style('height')) || 400)
8995                              - margin.top - margin.bottom;
8996         }
8997
8998         g.select('.nv-legendWrap')
8999             .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9000       }
9001
9002       //------------------------------------------------------------
9003
9004
9005       //------------------------------------------------------------
9006       // Controls
9007
9008       if (showControls) {
9009         var controlsData = [
9010           { key: 'Grouped', disabled: multibar.stacked() },
9011           { key: 'Stacked', disabled: !multibar.stacked() }
9012         ];
9013
9014         controls.width(controlWidth()).color(['#444', '#444', '#444']);
9015         g.select('.nv-controlsWrap')
9016             .datum(controlsData)
9017             .attr('transform', 'translate(0,' + (-margin.top) +')')
9018             .call(controls);
9019       }
9020
9021       //------------------------------------------------------------
9022
9023
9024       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9025
9026
9027       //------------------------------------------------------------
9028       // Main Chart Component(s)
9029
9030       multibar
9031         .disabled(data.map(function(series) { return series.disabled }))
9032         .width(availableWidth)
9033         .height(availableHeight)
9034         .color(data.map(function(d,i) {
9035           return d.color || color(d, i);
9036         }).filter(function(d,i) { return !data[i].disabled }))
9037
9038
9039       var barsWrap = g.select('.nv-barsWrap')
9040           .datum(data.filter(function(d) { return !d.disabled }))
9041
9042       barsWrap.transition().call(multibar);
9043
9044       //------------------------------------------------------------
9045
9046
9047       //------------------------------------------------------------
9048       // Setup Axes
9049
9050       xAxis
9051         .scale(x)
9052         .ticks( availableHeight / 24 )
9053         .tickSize(-availableWidth, 0);
9054
9055       g.select('.nv-x.nv-axis').transition()
9056           .call(xAxis);
9057
9058       var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9059
9060       xTicks
9061           .selectAll('line, text')
9062           .style('opacity', 1)
9063
9064
9065       yAxis
9066         .scale(y)
9067         .ticks( availableWidth / 100 )
9068         .tickSize( -availableHeight, 0);
9069
9070       g.select('.nv-y.nv-axis')
9071           .attr('transform', 'translate(0,' + availableHeight + ')');
9072       g.select('.nv-y.nv-axis').transition()
9073           .call(yAxis);
9074
9075       //------------------------------------------------------------
9076
9077
9078
9079       //============================================================
9080       // Event Handling/Dispatching (in chart's scope)
9081       //------------------------------------------------------------
9082
9083       legend.dispatch.on('stateChange', function(newState) { 
9084         state = newState;
9085         dispatch.stateChange(state);
9086         chart.update();
9087       });
9088
9089       controls.dispatch.on('legendClick', function(d,i) {
9090         if (!d.disabled) return;
9091         controlsData = controlsData.map(function(s) {
9092           s.disabled = true;
9093           return s;
9094         });
9095         d.disabled = false;
9096
9097         switch (d.key) {
9098           case 'Grouped':
9099             multibar.stacked(false);
9100             break;
9101           case 'Stacked':
9102             multibar.stacked(true);
9103             break;
9104         }
9105
9106         state.stacked = multibar.stacked();
9107         dispatch.stateChange(state);
9108
9109         chart.update();
9110       });
9111
9112       dispatch.on('tooltipShow', function(e) {
9113         if (tooltips) showTooltip(e, that.parentNode);
9114       });
9115
9116       // Update chart from a state object passed to event handler
9117       dispatch.on('changeState', function(e) {
9118
9119         if (typeof e.disabled !== 'undefined') {
9120           data.forEach(function(series,i) {
9121             series.disabled = e.disabled[i];
9122           });
9123
9124           state.disabled = e.disabled;
9125         }
9126
9127         if (typeof e.stacked !== 'undefined') {
9128           multibar.stacked(e.stacked);
9129           state.stacked = e.stacked;
9130         }
9131
9132         selection.call(chart);
9133       });
9134       //============================================================
9135
9136
9137     });
9138
9139     return chart;
9140   }
9141
9142
9143   //============================================================
9144   // Event Handling/Dispatching (out of chart's scope)
9145   //------------------------------------------------------------
9146
9147   multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9148     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9149     dispatch.tooltipShow(e);
9150   });
9151
9152   multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9153     dispatch.tooltipHide(e);
9154   });
9155   dispatch.on('tooltipHide', function() {
9156     if (tooltips) nv.tooltip.cleanup();
9157   });
9158
9159   //============================================================
9160
9161
9162   //============================================================
9163   // Expose Public Variables
9164   //------------------------------------------------------------
9165
9166   // expose chart's sub-components
9167   chart.dispatch = dispatch;
9168   chart.multibar = multibar;
9169   chart.legend = legend;
9170   chart.xAxis = xAxis;
9171   chart.yAxis = yAxis;
9172
9173   d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor');
9174
9175   chart.options = nv.utils.optionsFunc.bind(chart);
9176   
9177   chart.margin = function(_) {
9178     if (!arguments.length) return margin;
9179     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9180     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9181     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9182     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9183     return chart;
9184   };
9185
9186   chart.width = function(_) {
9187     if (!arguments.length) return width;
9188     width = _;
9189     return chart;
9190   };
9191
9192   chart.height = function(_) {
9193     if (!arguments.length) return height;
9194     height = _;
9195     return chart;
9196   };
9197
9198   chart.color = function(_) {
9199     if (!arguments.length) return color;
9200     color = nv.utils.getColor(_);
9201     legend.color(color);
9202     return chart;
9203   };
9204
9205   chart.showControls = function(_) {
9206     if (!arguments.length) return showControls;
9207     showControls = _;
9208     return chart;
9209   };
9210
9211   chart.showLegend = function(_) {
9212     if (!arguments.length) return showLegend;
9213     showLegend = _;
9214     return chart;
9215   };
9216
9217   chart.tooltip = function(_) {
9218     if (!arguments.length) return tooltip;
9219     tooltip = _;
9220     return chart;
9221   };
9222
9223   chart.tooltips = function(_) {
9224     if (!arguments.length) return tooltips;
9225     tooltips = _;
9226     return chart;
9227   };
9228
9229   chart.tooltipContent = function(_) {
9230     if (!arguments.length) return tooltip;
9231     tooltip = _;
9232     return chart;
9233   };
9234
9235   chart.state = function(_) {
9236     if (!arguments.length) return state;
9237     state = _;
9238     return chart;
9239   };
9240
9241   chart.defaultState = function(_) {
9242     if (!arguments.length) return defaultState;
9243     defaultState = _;
9244     return chart;
9245   };
9246
9247   chart.noData = function(_) {
9248     if (!arguments.length) return noData;
9249     noData = _;
9250     return chart;
9251   };
9252
9253   chart.transitionDuration = function(_) {
9254     if (!arguments.length) return transitionDuration;
9255     transitionDuration = _;
9256     return chart;
9257   };
9258   //============================================================
9259
9260
9261   return chart;
9262 }
9263 nv.models.multiChart = function() {
9264   "use strict";
9265   //============================================================
9266   // Public Variables with Default Settings
9267   //------------------------------------------------------------
9268
9269   var margin = {top: 30, right: 20, bottom: 50, left: 60},
9270       color = d3.scale.category20().range(),
9271       width = null, 
9272       height = null,
9273       showLegend = true,
9274       tooltips = true,
9275       tooltip = function(key, x, y, e, graph) {
9276         return '<h3>' + key + '</h3>' +
9277                '<p>' +  y + ' at ' + x + '</p>'
9278       },
9279       x,
9280       y,
9281       yDomain1,
9282       yDomain2
9283       ; //can be accessed via chart.lines.[x/y]Scale()
9284
9285   //============================================================
9286   // Private Variables
9287   //------------------------------------------------------------
9288
9289   var x = d3.scale.linear(),
9290       yScale1 = d3.scale.linear(),
9291       yScale2 = d3.scale.linear(),
9292
9293       lines1 = nv.models.line().yScale(yScale1),
9294       lines2 = nv.models.line().yScale(yScale2),
9295
9296       bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9297       bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9298
9299       stack1 = nv.models.stackedArea().yScale(yScale1),
9300       stack2 = nv.models.stackedArea().yScale(yScale2),
9301
9302       xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9303       yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9304       yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9305
9306       legend = nv.models.legend().height(30),
9307       dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
9308
9309   var showTooltip = function(e, offsetElement) {
9310     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9311         top = e.pos[1] + ( offsetElement.offsetTop || 0),
9312         x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
9313         y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
9314         content = tooltip(e.series.key, x, y, e, chart);
9315
9316     nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
9317   };
9318
9319   function chart(selection) {
9320     selection.each(function(data) {
9321       var container = d3.select(this),
9322           that = this;
9323
9324       chart.update = function() { container.transition().call(chart); };
9325       chart.container = this;
9326
9327       var availableWidth = (width  || parseInt(container.style('width')) || 960)
9328                              - margin.left - margin.right,
9329           availableHeight = (height || parseInt(container.style('height')) || 400)
9330                              - margin.top - margin.bottom;
9331
9332       var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
9333       var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
9334       var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
9335       var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
9336       var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
9337       var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
9338
9339       var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9340             .map(function(d) {
9341               return d.values.map(function(d,i) {
9342                 return { x: d.x, y: d.y }
9343               })
9344             })
9345
9346       var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9347             .map(function(d) {
9348               return d.values.map(function(d,i) {
9349                 return { x: d.x, y: d.y }
9350               })
9351             })
9352
9353       x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9354           .range([0, availableWidth]);
9355
9356       var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9357       var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9358
9359       gEnter.append('g').attr('class', 'x axis');
9360       gEnter.append('g').attr('class', 'y1 axis');
9361       gEnter.append('g').attr('class', 'y2 axis');
9362       gEnter.append('g').attr('class', 'lines1Wrap');
9363       gEnter.append('g').attr('class', 'lines2Wrap');
9364       gEnter.append('g').attr('class', 'bars1Wrap');
9365       gEnter.append('g').attr('class', 'bars2Wrap');
9366       gEnter.append('g').attr('class', 'stack1Wrap');
9367       gEnter.append('g').attr('class', 'stack2Wrap');
9368       gEnter.append('g').attr('class', 'legendWrap');
9369
9370       var g = wrap.select('g');
9371
9372       if (showLegend) {
9373         legend.width( availableWidth / 2 );
9374
9375         g.select('.legendWrap')
9376             .datum(data.map(function(series) { 
9377               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9378               series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9379               return series;
9380             }))
9381           .call(legend);
9382
9383         if ( margin.top != legend.height()) {
9384           margin.top = legend.height();
9385           availableHeight = (height || parseInt(container.style('height')) || 400)
9386                              - margin.top - margin.bottom;
9387         }
9388
9389         g.select('.legendWrap')
9390             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
9391       }
9392
9393
9394       lines1
9395         .width(availableWidth)
9396         .height(availableHeight)
9397         .interpolate("monotone")
9398         .color(data.map(function(d,i) {
9399           return d.color || color[i % color.length];
9400         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9401
9402       lines2
9403         .width(availableWidth)
9404         .height(availableHeight)
9405         .interpolate("monotone")
9406         .color(data.map(function(d,i) {
9407           return d.color || color[i % color.length];
9408         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9409
9410       bars1
9411         .width(availableWidth)
9412         .height(availableHeight)
9413         .color(data.map(function(d,i) {
9414           return d.color || color[i % color.length];
9415         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9416
9417       bars2
9418         .width(availableWidth)
9419         .height(availableHeight)
9420         .color(data.map(function(d,i) {
9421           return d.color || color[i % color.length];
9422         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9423
9424       stack1
9425         .width(availableWidth)
9426         .height(availableHeight)
9427         .color(data.map(function(d,i) {
9428           return d.color || color[i % color.length];
9429         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9430
9431       stack2
9432         .width(availableWidth)
9433         .height(availableHeight)
9434         .color(data.map(function(d,i) {
9435           return d.color || color[i % color.length];
9436         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9437
9438       g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9439
9440
9441       var lines1Wrap = g.select('.lines1Wrap')
9442           .datum(dataLines1)
9443       var bars1Wrap = g.select('.bars1Wrap')
9444           .datum(dataBars1)
9445       var stack1Wrap = g.select('.stack1Wrap')
9446           .datum(dataStack1)
9447
9448       var lines2Wrap = g.select('.lines2Wrap')
9449           .datum(dataLines2)
9450       var bars2Wrap = g.select('.bars2Wrap')
9451           .datum(dataBars2)
9452       var stack2Wrap = g.select('.stack2Wrap')
9453           .datum(dataStack2)
9454
9455       var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9456         return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9457       }).concat([{x:0, y:0}]) : []
9458       var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9459         return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9460       }).concat([{x:0, y:0}]) : []
9461
9462       yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9463               .range([0, availableHeight])
9464
9465       yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9466               .range([0, availableHeight])
9467
9468       lines1.yDomain(yScale1.domain())
9469       bars1.yDomain(yScale1.domain())
9470       stack1.yDomain(yScale1.domain())
9471
9472       lines2.yDomain(yScale2.domain())
9473       bars2.yDomain(yScale2.domain())
9474       stack2.yDomain(yScale2.domain())
9475
9476       if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9477       if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9478
9479       if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9480       if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9481
9482       if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9483       if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9484       
9485
9486
9487       xAxis
9488         .ticks( availableWidth / 100 )
9489         .tickSize(-availableHeight, 0);
9490
9491       g.select('.x.axis')
9492           .attr('transform', 'translate(0,' + availableHeight + ')');
9493       d3.transition(g.select('.x.axis'))
9494           .call(xAxis);
9495
9496       yAxis1
9497         .ticks( availableHeight / 36 )
9498         .tickSize( -availableWidth, 0);
9499
9500
9501       d3.transition(g.select('.y1.axis'))
9502           .call(yAxis1);
9503
9504       yAxis2
9505         .ticks( availableHeight / 36 )
9506         .tickSize( -availableWidth, 0);
9507
9508       d3.transition(g.select('.y2.axis'))
9509           .call(yAxis2);
9510
9511       g.select('.y2.axis')
9512           .style('opacity', series2.length ? 1 : 0)
9513           .attr('transform', 'translate(' + x.range()[1] + ',0)');
9514
9515       legend.dispatch.on('stateChange', function(newState) { 
9516         chart.update();
9517       });
9518      
9519       dispatch.on('tooltipShow', function(e) {
9520         if (tooltips) showTooltip(e, that.parentNode);
9521       });
9522
9523     });
9524
9525     return chart;
9526   }
9527
9528
9529   //============================================================
9530   // Event Handling/Dispatching (out of chart's scope)
9531   //------------------------------------------------------------
9532
9533   lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9534     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9535     dispatch.tooltipShow(e);
9536   });
9537
9538   lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9539     dispatch.tooltipHide(e);
9540   });
9541
9542   lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9543     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9544     dispatch.tooltipShow(e);
9545   });
9546
9547   lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9548     dispatch.tooltipHide(e);
9549   });
9550
9551   bars1.dispatch.on('elementMouseover.tooltip', function(e) {
9552     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9553     dispatch.tooltipShow(e);
9554   });
9555
9556   bars1.dispatch.on('elementMouseout.tooltip', function(e) {
9557     dispatch.tooltipHide(e);
9558   });
9559
9560   bars2.dispatch.on('elementMouseover.tooltip', function(e) {
9561     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9562     dispatch.tooltipShow(e);
9563   });
9564
9565   bars2.dispatch.on('elementMouseout.tooltip', function(e) {
9566     dispatch.tooltipHide(e);
9567   });
9568
9569   stack1.dispatch.on('tooltipShow', function(e) {
9570     //disable tooltips when value ~= 0
9571     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
9572     if (!Math.round(stack1.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9573       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9574       return false;
9575     }
9576
9577     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9578     dispatch.tooltipShow(e);
9579   });
9580
9581   stack1.dispatch.on('tooltipHide', function(e) {
9582     dispatch.tooltipHide(e);
9583   });
9584
9585   stack2.dispatch.on('tooltipShow', function(e) {
9586     //disable tooltips when value ~= 0
9587     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
9588     if (!Math.round(stack2.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9589       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9590       return false;
9591     }
9592
9593     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9594     dispatch.tooltipShow(e);
9595   });
9596
9597   stack2.dispatch.on('tooltipHide', function(e) {
9598     dispatch.tooltipHide(e);
9599   });
9600
9601     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9602     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9603     dispatch.tooltipShow(e);
9604   });
9605
9606   lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9607     dispatch.tooltipHide(e);
9608   });
9609
9610   lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9611     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9612     dispatch.tooltipShow(e);
9613   });
9614
9615   lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9616     dispatch.tooltipHide(e);
9617   });
9618
9619   dispatch.on('tooltipHide', function() {
9620     if (tooltips) nv.tooltip.cleanup();
9621   });
9622
9623
9624
9625   //============================================================
9626   // Global getters and setters
9627   //------------------------------------------------------------
9628
9629   chart.dispatch = dispatch;
9630   chart.lines1 = lines1;
9631   chart.lines2 = lines2;
9632   chart.bars1 = bars1;
9633   chart.bars2 = bars2;
9634   chart.stack1 = stack1;
9635   chart.stack2 = stack2;
9636   chart.xAxis = xAxis;
9637   chart.yAxis1 = yAxis1;
9638   chart.yAxis2 = yAxis2;
9639   chart.options = nv.utils.optionsFunc.bind(chart);
9640
9641   chart.x = function(_) {
9642     if (!arguments.length) return getX;
9643     getX = _;
9644     lines1.x(_);
9645     bars1.x(_);
9646     return chart;
9647   };
9648
9649   chart.y = function(_) {
9650     if (!arguments.length) return getY;
9651     getY = _;
9652     lines1.y(_);
9653     bars1.y(_);
9654     return chart;
9655   };
9656
9657   chart.yDomain1 = function(_) {
9658     if (!arguments.length) return yDomain1;
9659     yDomain1 = _;
9660     return chart;
9661   };
9662
9663   chart.yDomain2 = function(_) {
9664     if (!arguments.length) return yDomain2;
9665     yDomain2 = _;
9666     return chart;
9667   };
9668
9669   chart.margin = function(_) {
9670     if (!arguments.length) return margin;
9671     margin = _;
9672     return chart;
9673   };
9674
9675   chart.width = function(_) {
9676     if (!arguments.length) return width;
9677     width = _;
9678     return chart;
9679   };
9680
9681   chart.height = function(_) {
9682     if (!arguments.length) return height;
9683     height = _;
9684     return chart;
9685   };
9686
9687   chart.color = function(_) {
9688     if (!arguments.length) return color;
9689     color = _;
9690     legend.color(_);
9691     return chart;
9692   };
9693
9694   chart.showLegend = function(_) {
9695     if (!arguments.length) return showLegend;
9696     showLegend = _;
9697     return chart;
9698   };
9699
9700   chart.tooltips = function(_) {
9701     if (!arguments.length) return tooltips;
9702     tooltips = _;
9703     return chart;
9704   };
9705
9706   chart.tooltipContent = function(_) {
9707     if (!arguments.length) return tooltip;
9708     tooltip = _;
9709     return chart;
9710   };
9711
9712   return chart;
9713 }
9714
9715
9716 nv.models.ohlcBar = function() {
9717   "use strict";
9718   //============================================================
9719   // Public Variables with Default Settings
9720   //------------------------------------------------------------
9721
9722   var margin = {top: 0, right: 0, bottom: 0, left: 0}
9723     , width = 960
9724     , height = 500
9725     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9726     , x = d3.scale.linear()
9727     , y = d3.scale.linear()
9728     , getX = function(d) { return d.x }
9729     , getY = function(d) { return d.y }
9730     , getOpen = function(d) { return d.open }
9731     , getClose = function(d) { return d.close }
9732     , getHigh = function(d) { return d.high }
9733     , getLow = function(d) { return d.low }
9734     , forceX = []
9735     , forceY = []
9736     , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9737     , clipEdge = true
9738     , color = nv.utils.defaultColor()
9739     , xDomain
9740     , yDomain
9741     , xRange
9742     , yRange
9743     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9744     ;
9745
9746   //============================================================
9747
9748   //============================================================
9749   // Private Variables
9750   //------------------------------------------------------------
9751
9752   //TODO: store old scales for transitions
9753
9754   //============================================================
9755
9756
9757   function chart(selection) {
9758     selection.each(function(data) {
9759       var availableWidth = width - margin.left - margin.right,
9760           availableHeight = height - margin.top - margin.bottom,
9761           container = d3.select(this);
9762
9763
9764       //------------------------------------------------------------
9765       // Setup Scales
9766
9767       x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9768
9769       if (padData)
9770         x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
9771       else
9772         x.range(xRange || [0, availableWidth]);
9773
9774       y   .domain(yDomain || [
9775             d3.min(data[0].values.map(getLow).concat(forceY)),
9776             d3.max(data[0].values.map(getHigh).concat(forceY))
9777           ])
9778           .range(yRange || [availableHeight, 0]);
9779
9780       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9781       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
9782       if (x.domain()[0] === x.domain()[1])
9783         x.domain()[0] ?
9784             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9785           : x.domain([-1,1]);
9786
9787       if (y.domain()[0] === y.domain()[1])
9788         y.domain()[0] ?
9789             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9790           : y.domain([-1,1]);
9791
9792       //------------------------------------------------------------
9793
9794
9795       //------------------------------------------------------------
9796       // Setup containers and skeleton of chart
9797
9798       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9799       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9800       var defsEnter = wrapEnter.append('defs');
9801       var gEnter = wrapEnter.append('g');
9802       var g = wrap.select('g');
9803
9804       gEnter.append('g').attr('class', 'nv-ticks');
9805
9806       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9807
9808       //------------------------------------------------------------
9809
9810
9811       container
9812           .on('click', function(d,i) {
9813             dispatch.chartClick({
9814                 data: d,
9815                 index: i,
9816                 pos: d3.event,
9817                 id: id
9818             });
9819           });
9820
9821
9822       defsEnter.append('clipPath')
9823           .attr('id', 'nv-chart-clip-path-' + id)
9824         .append('rect');
9825
9826       wrap.select('#nv-chart-clip-path-' + id + ' rect')
9827           .attr('width', availableWidth)
9828           .attr('height', availableHeight);
9829
9830       g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9831
9832
9833
9834       var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9835           .data(function(d) { return d });
9836
9837       ticks.exit().remove();
9838
9839
9840       var ticksEnter = ticks.enter().append('path')
9841           .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9842           .attr('d', function(d,i) {
9843             var w = (availableWidth / data[0].values.length) * .9;
9844             return 'm0,0l0,'
9845                  + (y(getOpen(d,i))
9846                  - y(getHigh(d,i)))
9847                  + 'l'
9848                  + (-w/2)
9849                  + ',0l'
9850                  + (w/2)
9851                  + ',0l0,'
9852                  + (y(getLow(d,i)) - y(getOpen(d,i)))
9853                  + 'l0,'
9854                  + (y(getClose(d,i))
9855                  - y(getLow(d,i)))
9856                  + 'l'
9857                  + (w/2)
9858                  + ',0l'
9859                  + (-w/2)
9860                  + ',0z';
9861           })
9862           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9863           //.attr('fill', function(d,i) { return color[0]; })
9864           //.attr('stroke', function(d,i) { return color[0]; })
9865           //.attr('x', 0 )
9866           //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
9867           //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
9868           .on('mouseover', function(d,i) {
9869             d3.select(this).classed('hover', true);
9870             dispatch.elementMouseover({
9871                 point: d,
9872                 series: data[0],
9873                 pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
9874                 pointIndex: i,
9875                 seriesIndex: 0,
9876                 e: d3.event
9877             });
9878
9879           })
9880           .on('mouseout', function(d,i) {
9881                 d3.select(this).classed('hover', false);
9882                 dispatch.elementMouseout({
9883                     point: d,
9884                     series: data[0],
9885                     pointIndex: i,
9886                     seriesIndex: 0,
9887                     e: d3.event
9888                 });
9889           })
9890           .on('click', function(d,i) {
9891                 dispatch.elementClick({
9892                     //label: d[label],
9893                     value: getY(d,i),
9894                     data: d,
9895                     index: i,
9896                     pos: [x(getX(d,i)), y(getY(d,i))],
9897                     e: d3.event,
9898                     id: id
9899                 });
9900               d3.event.stopPropagation();
9901           })
9902           .on('dblclick', function(d,i) {
9903               dispatch.elementDblClick({
9904                   //label: d[label],
9905                   value: getY(d,i),
9906                   data: d,
9907                   index: i,
9908                   pos: [x(getX(d,i)), y(getY(d,i))],
9909                   e: d3.event,
9910                   id: id
9911               });
9912               d3.event.stopPropagation();
9913           });
9914
9915       ticks
9916           .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9917       d3.transition(ticks)
9918           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9919           .attr('d', function(d,i) {
9920             var w = (availableWidth / data[0].values.length) * .9;
9921             return 'm0,0l0,'
9922                  + (y(getOpen(d,i))
9923                  - y(getHigh(d,i)))
9924                  + 'l'
9925                  + (-w/2)
9926                  + ',0l'
9927                  + (w/2)
9928                  + ',0l0,'
9929                  + (y(getLow(d,i))
9930                  - y(getOpen(d,i)))
9931                  + 'l0,'
9932                  + (y(getClose(d,i))
9933                  - y(getLow(d,i)))
9934                  + 'l'
9935                  + (w/2)
9936                  + ',0l'
9937                  + (-w/2)
9938                  + ',0z';
9939           })
9940           //.attr('width', (availableWidth / data[0].values.length) * .9 )
9941
9942
9943       //d3.transition(ticks)
9944           //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
9945           //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9946           //.order();  // not sure if this makes any sense for this model
9947
9948     });
9949
9950     return chart;
9951   }
9952
9953
9954   //============================================================
9955   // Expose Public Variables
9956   //------------------------------------------------------------
9957
9958   chart.dispatch = dispatch;
9959
9960   chart.options = nv.utils.optionsFunc.bind(chart);
9961
9962   chart.x = function(_) {
9963     if (!arguments.length) return getX;
9964     getX = _;
9965     return chart;
9966   };
9967
9968   chart.y = function(_) {
9969     if (!arguments.length) return getY;
9970     getY = _;
9971     return chart;
9972   };
9973
9974   chart.open = function(_) {
9975     if (!arguments.length) return getOpen;
9976     getOpen = _;
9977     return chart;
9978   };
9979
9980   chart.close = function(_) {
9981     if (!arguments.length) return getClose;
9982     getClose = _;
9983     return chart;
9984   };
9985
9986   chart.high = function(_) {
9987     if (!arguments.length) return getHigh;
9988     getHigh = _;
9989     return chart;
9990   };
9991
9992   chart.low = function(_) {
9993     if (!arguments.length) return getLow;
9994     getLow = _;
9995     return chart;
9996   };
9997
9998   chart.margin = function(_) {
9999     if (!arguments.length) return margin;
10000     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10001     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10002     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10003     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10004     return chart;
10005   };
10006
10007   chart.width = function(_) {
10008     if (!arguments.length) return width;
10009     width = _;
10010     return chart;
10011   };
10012
10013   chart.height = function(_) {
10014     if (!arguments.length) return height;
10015     height = _;
10016     return chart;
10017   };
10018
10019   chart.xScale = function(_) {
10020     if (!arguments.length) return x;
10021     x = _;
10022     return chart;
10023   };
10024
10025   chart.yScale = function(_) {
10026     if (!arguments.length) return y;
10027     y = _;
10028     return chart;
10029   };
10030
10031   chart.xDomain = function(_) {
10032     if (!arguments.length) return xDomain;
10033     xDomain = _;
10034     return chart;
10035   };
10036
10037   chart.yDomain = function(_) {
10038     if (!arguments.length) return yDomain;
10039     yDomain = _;
10040     return chart;
10041   };
10042
10043   chart.xRange = function(_) {
10044     if (!arguments.length) return xRange;
10045     xRange = _;
10046     return chart;
10047   };
10048
10049   chart.yRange = function(_) {
10050     if (!arguments.length) return yRange;
10051     yRange = _;
10052     return chart;
10053   };
10054
10055   chart.forceX = function(_) {
10056     if (!arguments.length) return forceX;
10057     forceX = _;
10058     return chart;
10059   };
10060
10061   chart.forceY = function(_) {
10062     if (!arguments.length) return forceY;
10063     forceY = _;
10064     return chart;
10065   };
10066
10067   chart.padData = function(_) {
10068     if (!arguments.length) return padData;
10069     padData = _;
10070     return chart;
10071   };
10072
10073   chart.clipEdge = function(_) {
10074     if (!arguments.length) return clipEdge;
10075     clipEdge = _;
10076     return chart;
10077   };
10078
10079   chart.color = function(_) {
10080     if (!arguments.length) return color;
10081     color = nv.utils.getColor(_);
10082     return chart;
10083   };
10084
10085   chart.id = function(_) {
10086     if (!arguments.length) return id;
10087     id = _;
10088     return chart;
10089   };
10090
10091   //============================================================
10092
10093
10094   return chart;
10095 }
10096 nv.models.pie = function() {
10097   "use strict";
10098   //============================================================
10099   // Public Variables with Default Settings
10100   //------------------------------------------------------------
10101
10102   var margin = {top: 0, right: 0, bottom: 0, left: 0}
10103     , width = 500
10104     , height = 500
10105     , getX = function(d) { return d.x }
10106     , getY = function(d) { return d.y }
10107     , getDescription = function(d) { return d.description }
10108     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10109     , color = nv.utils.defaultColor()
10110     , valueFormat = d3.format(',.2f')
10111     , showLabels = true
10112     , pieLabelsOutside = true
10113     , donutLabelsOutside = false
10114     , labelType = "key"
10115     , labelThreshold = .02 //if slice percentage is under this, don't show label
10116     , donut = false
10117     , labelSunbeamLayout = false
10118     , startAngle = false
10119     , endAngle = false
10120     , donutRatio = 0.5
10121     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10122     ;
10123
10124   //============================================================
10125
10126
10127   function chart(selection) {
10128     selection.each(function(data) {
10129       var availableWidth = width - margin.left - margin.right,
10130           availableHeight = height - margin.top - margin.bottom,
10131           radius = Math.min(availableWidth, availableHeight) / 2,
10132           arcRadius = radius-(radius / 5),
10133           container = d3.select(this);
10134
10135
10136       //------------------------------------------------------------
10137       // Setup containers and skeleton of chart
10138
10139       //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
10140       var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10141       var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10142       var gEnter = wrapEnter.append('g');
10143       var g = wrap.select('g');
10144
10145       gEnter.append('g').attr('class', 'nv-pie');
10146
10147       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10148       g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10149
10150       //------------------------------------------------------------
10151
10152
10153       container
10154           .on('click', function(d,i) {
10155               dispatch.chartClick({
10156                   data: d,
10157                   index: i,
10158                   pos: d3.event,
10159                   id: id
10160               });
10161           });
10162
10163
10164       var arc = d3.svg.arc()
10165                   .outerRadius(arcRadius);
10166
10167       if (startAngle) arc.startAngle(startAngle)
10168       if (endAngle) arc.endAngle(endAngle);
10169       if (donut) arc.innerRadius(radius * donutRatio);
10170
10171       // Setup the Pie chart and choose the data element
10172       var pie = d3.layout.pie()
10173           .sort(null)
10174           .value(function(d) { return d.disabled ? 0 : getY(d) });
10175
10176       var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
10177           .data(pie);
10178
10179       slices.exit().remove();
10180
10181       var ae = slices.enter().append('g')
10182               .attr('class', 'nv-slice')
10183               .on('mouseover', function(d,i){
10184                 d3.select(this).classed('hover', true);
10185                 dispatch.elementMouseover({
10186                     label: getX(d.data),
10187                     value: getY(d.data),
10188                     point: d.data,
10189                     pointIndex: i,
10190                     pos: [d3.event.pageX, d3.event.pageY],
10191                     id: id
10192                 });
10193               })
10194               .on('mouseout', function(d,i){
10195                 d3.select(this).classed('hover', false);
10196                 dispatch.elementMouseout({
10197                     label: getX(d.data),
10198                     value: getY(d.data),
10199                     point: d.data,
10200                     index: i,
10201                     id: id
10202                 });
10203               })
10204               .on('click', function(d,i) {
10205                 dispatch.elementClick({
10206                     label: getX(d.data),
10207                     value: getY(d.data),
10208                     point: d.data,
10209                     index: i,
10210                     pos: d3.event,
10211                     id: id
10212                 });
10213                 d3.event.stopPropagation();
10214               })
10215               .on('dblclick', function(d,i) {
10216                 dispatch.elementDblClick({
10217                     label: getX(d.data),
10218                     value: getY(d.data),
10219                     point: d.data,
10220                     index: i,
10221                     pos: d3.event,
10222                     id: id
10223                 });
10224                 d3.event.stopPropagation();
10225               });
10226
10227         slices
10228             .attr('fill', function(d,i) { return color(d, i); })
10229             .attr('stroke', function(d,i) { return color(d, i); });
10230
10231         var paths = ae.append('path')
10232             .each(function(d) { this._current = d; });
10233             //.attr('d', arc);
10234
10235         d3.transition(slices.select('path'))
10236             .attr('d', arc)
10237             .attrTween('d', arcTween);
10238
10239         if (showLabels) {
10240           // This does the normal label
10241           var labelsArc = d3.svg.arc().innerRadius(0);
10242           
10243           if (pieLabelsOutside){ labelsArc = arc; }
10244
10245           if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
10246
10247           ae.append("g").classed("nv-label", true)
10248             .each(function(d, i) {
10249               var group = d3.select(this);
10250
10251               group
10252                 .attr('transform', function(d) {
10253                      if (labelSunbeamLayout) {
10254                        d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10255                        d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10256                        var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10257                        if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10258                          rotateAngle -= 90;
10259                        } else {
10260                          rotateAngle += 90;
10261                        }
10262                        return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10263                      } else {
10264                        d.outerRadius = radius + 10; // Set Outer Coordinate
10265                        d.innerRadius = radius + 15; // Set Inner Coordinate
10266                        return 'translate(' + labelsArc.centroid(d) + ')'
10267                      }
10268                 });
10269
10270               group.append('rect')
10271                   .style('stroke', '#fff')
10272                   .style('fill', '#fff')
10273                   .attr("rx", 3)
10274                   .attr("ry", 3);
10275
10276               group.append('text')
10277                   .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
10278                   .style('fill', '#000')
10279
10280
10281           });
10282
10283           slices.select(".nv-label").transition()
10284             .attr('transform', function(d) {
10285                 if (labelSunbeamLayout) {
10286                   d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10287                   d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10288                   var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10289                   if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10290                     rotateAngle -= 90;
10291                   } else {
10292                     rotateAngle += 90;
10293                   }
10294                   return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10295                 } else {
10296                   d.outerRadius = radius + 10; // Set Outer Coordinate
10297                   d.innerRadius = radius + 15; // Set Inner Coordinate
10298                   return 'translate(' + labelsArc.centroid(d) + ')'
10299                 }
10300             });
10301
10302           slices.each(function(d, i) {
10303             var slice = d3.select(this);
10304
10305             slice
10306               .select(".nv-label text")
10307                 .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
10308                 .text(function(d, i) {
10309                   var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10310                   var labelTypes = {
10311                     "key" : getX(d.data),
10312                     "value": getY(d.data),
10313                     "percent": d3.format('%')(percent)
10314                   };
10315                   return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
10316                 });
10317
10318             var textBox = slice.select('text').node().getBBox();
10319             slice.select(".nv-label rect")
10320               .attr("width", textBox.width + 10)
10321               .attr("height", textBox.height + 10)
10322               .attr("transform", function() {
10323                 return "translate(" + [textBox.x - 5, textBox.y - 5] + ")";
10324               });
10325           });
10326         }
10327
10328
10329         // Computes the angle of an arc, converting from radians to degrees.
10330         function angle(d) {
10331           var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10332           return a > 90 ? a - 180 : a;
10333         }
10334
10335         function arcTween(a) {
10336           a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10337           a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10338           if (!donut) a.innerRadius = 0;
10339           var i = d3.interpolate(this._current, a);
10340           this._current = i(0);
10341           return function(t) {
10342             return arc(i(t));
10343           };
10344         }
10345
10346         function tweenPie(b) {
10347           b.innerRadius = 0;
10348           var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
10349           return function(t) {
10350               return arc(i(t));
10351           };
10352         }
10353
10354     });
10355
10356     return chart;
10357   }
10358
10359
10360   //============================================================
10361   // Expose Public Variables
10362   //------------------------------------------------------------
10363
10364   chart.dispatch = dispatch;
10365   chart.options = nv.utils.optionsFunc.bind(chart);
10366
10367   chart.margin = function(_) {
10368     if (!arguments.length) return margin;
10369     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10370     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10371     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10372     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10373     return chart;
10374   };
10375
10376   chart.width = function(_) {
10377     if (!arguments.length) return width;
10378     width = _;
10379     return chart;
10380   };
10381
10382   chart.height = function(_) {
10383     if (!arguments.length) return height;
10384     height = _;
10385     return chart;
10386   };
10387
10388   chart.values = function(_) {
10389     nv.log("pie.values() is no longer supported.");
10390     return chart;
10391   };
10392
10393   chart.x = function(_) {
10394     if (!arguments.length) return getX;
10395     getX = _;
10396     return chart;
10397   };
10398
10399   chart.y = function(_) {
10400     if (!arguments.length) return getY;
10401     getY = d3.functor(_);
10402     return chart;
10403   };
10404   
10405   chart.description = function(_) {
10406     if (!arguments.length) return getDescription;
10407     getDescription = _;
10408     return chart;
10409   };
10410
10411   chart.showLabels = function(_) {
10412     if (!arguments.length) return showLabels;
10413     showLabels = _;
10414     return chart;
10415   };
10416   
10417   chart.labelSunbeamLayout = function(_) {
10418     if (!arguments.length) return labelSunbeamLayout;
10419     labelSunbeamLayout = _;
10420     return chart;
10421   };
10422
10423   chart.donutLabelsOutside = function(_) {
10424     if (!arguments.length) return donutLabelsOutside;
10425     donutLabelsOutside = _;
10426     return chart;
10427   };
10428   
10429   chart.pieLabelsOutside = function(_) {
10430     if (!arguments.length) return pieLabelsOutside;
10431     pieLabelsOutside = _;
10432     return chart;
10433   };
10434
10435   chart.labelType = function(_) {
10436     if (!arguments.length) return labelType;
10437     labelType = _;
10438     labelType = labelType || "key";
10439     return chart;
10440   };
10441
10442   chart.donut = function(_) {
10443     if (!arguments.length) return donut;
10444     donut = _;
10445     return chart;
10446   };
10447   
10448   chart.donutRatio = function(_) {
10449     if (!arguments.length) return donutRatio;
10450     donutRatio = _;
10451     return chart;
10452   };
10453
10454   chart.startAngle = function(_) {
10455     if (!arguments.length) return startAngle;
10456     startAngle = _;
10457     return chart;
10458   };
10459
10460   chart.endAngle = function(_) {
10461     if (!arguments.length) return endAngle;
10462     endAngle = _;
10463     return chart;
10464   };
10465
10466   chart.id = function(_) {
10467     if (!arguments.length) return id;
10468     id = _;
10469     return chart;
10470   };
10471
10472   chart.color = function(_) {
10473     if (!arguments.length) return color;
10474     color = nv.utils.getColor(_);
10475     return chart;
10476   };
10477
10478   chart.valueFormat = function(_) {
10479     if (!arguments.length) return valueFormat;
10480     valueFormat = _;
10481     return chart;
10482   };
10483
10484   chart.labelThreshold = function(_) {
10485     if (!arguments.length) return labelThreshold;
10486     labelThreshold = _;
10487     return chart;
10488   };
10489   //============================================================
10490
10491
10492   return chart;
10493 }
10494 nv.models.pieChart = function() {
10495   "use strict";
10496   //============================================================
10497   // Public Variables with Default Settings
10498   //------------------------------------------------------------
10499
10500   var pie = nv.models.pie()
10501     , legend = nv.models.legend()
10502     ;
10503
10504   var margin = {top: 30, right: 20, bottom: 20, left: 20}
10505     , width = null
10506     , height = null
10507     , showLegend = true
10508     , color = nv.utils.defaultColor()
10509     , tooltips = true
10510     , tooltip = function(key, y, e, graph) {
10511         return '<h3>' + key + '</h3>' +
10512                '<p>' +  y + '</p>'
10513       }
10514     , state = {}
10515     , defaultState = null
10516     , noData = "No Data Available."
10517     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10518     ;
10519
10520   //============================================================
10521
10522
10523   //============================================================
10524   // Private Variables
10525   //------------------------------------------------------------
10526
10527   var showTooltip = function(e, offsetElement) {
10528     var tooltipLabel = pie.description()(e.point) || pie.x()(e.point)
10529     var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
10530         top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
10531         y = pie.valueFormat()(pie.y()(e.point)),
10532         content = tooltip(tooltipLabel, y, e, chart);
10533
10534     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10535   };
10536
10537   //============================================================
10538
10539
10540   function chart(selection) {
10541     selection.each(function(data) {
10542       var container = d3.select(this),
10543           that = this;
10544
10545       var availableWidth = (width || parseInt(container.style('width')) || 960)
10546                              - margin.left - margin.right,
10547           availableHeight = (height || parseInt(container.style('height')) || 400)
10548                              - margin.top - margin.bottom;
10549
10550       chart.update = function() { container.transition().call(chart); };
10551       chart.container = this;
10552
10553       //set state.disabled
10554       state.disabled = data.map(function(d) { return !!d.disabled });
10555
10556       if (!defaultState) {
10557         var key;
10558         defaultState = {};
10559         for (key in state) {
10560           if (state[key] instanceof Array)
10561             defaultState[key] = state[key].slice(0);
10562           else
10563             defaultState[key] = state[key];
10564         }
10565       }
10566
10567       //------------------------------------------------------------
10568       // Display No Data message if there's nothing to show.
10569
10570       if (!data || !data.length) {
10571         var noDataText = container.selectAll('.nv-noData').data([noData]);
10572
10573         noDataText.enter().append('text')
10574           .attr('class', 'nvd3 nv-noData')
10575           .attr('dy', '-.7em')
10576           .style('text-anchor', 'middle');
10577
10578         noDataText
10579           .attr('x', margin.left + availableWidth / 2)
10580           .attr('y', margin.top + availableHeight / 2)
10581           .text(function(d) { return d });
10582
10583         return chart;
10584       } else {
10585         container.selectAll('.nv-noData').remove();
10586       }
10587
10588       //------------------------------------------------------------
10589
10590
10591       //------------------------------------------------------------
10592       // Setup containers and skeleton of chart
10593
10594       var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10595       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10596       var g = wrap.select('g');
10597
10598       gEnter.append('g').attr('class', 'nv-pieWrap');
10599       gEnter.append('g').attr('class', 'nv-legendWrap');
10600
10601       //------------------------------------------------------------
10602
10603
10604       //------------------------------------------------------------
10605       // Legend
10606
10607       if (showLegend) {
10608         legend
10609           .width( availableWidth )
10610           .key(pie.x());
10611
10612         wrap.select('.nv-legendWrap')
10613             .datum(data)
10614             .call(legend);
10615
10616         if ( margin.top != legend.height()) {
10617           margin.top = legend.height();
10618           availableHeight = (height || parseInt(container.style('height')) || 400)
10619                              - margin.top - margin.bottom;
10620         }
10621
10622         wrap.select('.nv-legendWrap')
10623             .attr('transform', 'translate(0,' + (-margin.top) +')');
10624       }
10625
10626       //------------------------------------------------------------
10627
10628
10629       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10630
10631
10632       //------------------------------------------------------------
10633       // Main Chart Component(s)
10634
10635       pie
10636         .width(availableWidth)
10637         .height(availableHeight);
10638
10639
10640       var pieWrap = g.select('.nv-pieWrap')
10641           .datum([data]);
10642
10643       d3.transition(pieWrap).call(pie);
10644
10645       //------------------------------------------------------------
10646
10647
10648       //============================================================
10649       // Event Handling/Dispatching (in chart's scope)
10650       //------------------------------------------------------------
10651
10652       legend.dispatch.on('stateChange', function(newState) {
10653         state = newState;
10654         dispatch.stateChange(state);
10655         chart.update();
10656       });
10657
10658       pie.dispatch.on('elementMouseout.tooltip', function(e) {
10659         dispatch.tooltipHide(e);
10660       });
10661
10662       // Update chart from a state object passed to event handler
10663       dispatch.on('changeState', function(e) {
10664
10665         if (typeof e.disabled !== 'undefined') {
10666           data.forEach(function(series,i) {
10667             series.disabled = e.disabled[i];
10668           });
10669
10670           state.disabled = e.disabled;
10671         }
10672
10673         chart.update();
10674       });
10675
10676       //============================================================
10677
10678
10679     });
10680
10681     return chart;
10682   }
10683
10684   //============================================================
10685   // Event Handling/Dispatching (out of chart's scope)
10686   //------------------------------------------------------------
10687
10688   pie.dispatch.on('elementMouseover.tooltip', function(e) {
10689     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
10690     dispatch.tooltipShow(e);
10691   });
10692
10693   dispatch.on('tooltipShow', function(e) {
10694     if (tooltips) showTooltip(e);
10695   });
10696
10697   dispatch.on('tooltipHide', function() {
10698     if (tooltips) nv.tooltip.cleanup();
10699   });
10700
10701   //============================================================
10702
10703
10704   //============================================================
10705   // Expose Public Variables
10706   //------------------------------------------------------------
10707
10708   // expose chart's sub-components
10709   chart.legend = legend;
10710   chart.dispatch = dispatch;
10711   chart.pie = pie;
10712
10713   d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'labelType', 'donut', 'donutRatio', 'labelThreshold');
10714   chart.options = nv.utils.optionsFunc.bind(chart);
10715   
10716   chart.margin = function(_) {
10717     if (!arguments.length) return margin;
10718     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10719     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10720     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10721     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10722     return chart;
10723   };
10724
10725   chart.width = function(_) {
10726     if (!arguments.length) return width;
10727     width = _;
10728     return chart;
10729   };
10730
10731   chart.height = function(_) {
10732     if (!arguments.length) return height;
10733     height = _;
10734     return chart;
10735   };
10736
10737   chart.color = function(_) {
10738     if (!arguments.length) return color;
10739     color = nv.utils.getColor(_);
10740     legend.color(color);
10741     pie.color(color);
10742     return chart;
10743   };
10744
10745   chart.showLegend = function(_) {
10746     if (!arguments.length) return showLegend;
10747     showLegend = _;
10748     return chart;
10749   };
10750
10751   chart.tooltips = function(_) {
10752     if (!arguments.length) return tooltips;
10753     tooltips = _;
10754     return chart;
10755   };
10756
10757   chart.tooltipContent = function(_) {
10758     if (!arguments.length) return tooltip;
10759     tooltip = _;
10760     return chart;
10761   };
10762
10763   chart.state = function(_) {
10764     if (!arguments.length) return state;
10765     state = _;
10766     return chart;
10767   };
10768
10769   chart.defaultState = function(_) {
10770     if (!arguments.length) return defaultState;
10771     defaultState = _;
10772     return chart;
10773   };
10774
10775   chart.noData = function(_) {
10776     if (!arguments.length) return noData;
10777     noData = _;
10778     return chart;
10779   };
10780
10781   //============================================================
10782
10783
10784   return chart;
10785 }
10786
10787 nv.models.scatter = function() {
10788   "use strict";
10789   //============================================================
10790   // Public Variables with Default Settings
10791   //------------------------------------------------------------
10792
10793   var margin       = {top: 0, right: 0, bottom: 0, left: 0}
10794     , width        = 960
10795     , height       = 500
10796     , color        = nv.utils.defaultColor() // chooses color
10797     , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10798     , x            = d3.scale.linear()
10799     , y            = d3.scale.linear()
10800     , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10801     , getX         = function(d) { return d.x } // accessor to get the x value
10802     , getY         = function(d) { return d.y } // accessor to get the y value
10803     , getSize      = function(d) { return d.size || 1} // accessor to get the point size
10804     , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
10805     , onlyCircles  = true // Set to false to use shapes
10806     , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10807     , forceY       = [] // List of numbers to Force into the Y scale
10808     , forceSize    = [] // List of numbers to Force into the Size scale
10809     , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
10810     , pointKey     = null
10811     , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
10812     , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10813     , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10814     , clipEdge     = false // if true, masks points within x and y scale
10815     , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
10816     , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
10817     , xDomain      = null // Override x domain (skips the calculation from data)
10818     , yDomain      = null // Override y domain
10819     , xRange       = null // Override x range
10820     , yRange       = null // Override y range
10821     , sizeDomain   = null // Override point size domain
10822     , sizeRange    = null
10823     , singlePoint  = false
10824     , dispatch     = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
10825     , useVoronoi   = true
10826     ;
10827
10828   //============================================================
10829
10830
10831   //============================================================
10832   // Private Variables
10833   //------------------------------------------------------------
10834
10835   var x0, y0, z0 // used to store previous scales
10836     , timeoutID
10837     , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
10838     ;
10839
10840   //============================================================
10841
10842
10843   function chart(selection) {
10844     selection.each(function(data) {
10845       var availableWidth = width - margin.left - margin.right,
10846           availableHeight = height - margin.top - margin.bottom,
10847           container = d3.select(this);
10848
10849       //add series index to each data point for reference
10850       data = data.map(function(series, i) {
10851         series.values = series.values.map(function(point) {
10852           point.series = i;
10853           return point;
10854         });
10855         return series;
10856       });
10857
10858       //------------------------------------------------------------
10859       // Setup Scales
10860
10861       // remap and flatten the data for use in calculating the scales' domains
10862       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
10863             d3.merge(
10864               data.map(function(d) {
10865                 return d.values.map(function(d,i) {
10866                   return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
10867                 })
10868               })
10869             );
10870
10871       x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10872
10873       if (padData && data[0])
10874         x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
10875         //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
10876       else
10877         x.range(xRange || [0, availableWidth]);
10878
10879       y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10880           .range(yRange || [availableHeight, 0]);
10881
10882       z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10883           .range(sizeRange || [16, 256]);
10884
10885       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10886       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
10887       if (x.domain()[0] === x.domain()[1])
10888         x.domain()[0] ?
10889             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10890           : x.domain([-1,1]);
10891
10892       if (y.domain()[0] === y.domain()[1])
10893         y.domain()[0] ?
10894             y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
10895           : y.domain([-1,1]);
10896
10897       if ( isNaN(x.domain()[0])) {
10898           x.domain([-1,1]);
10899       }
10900
10901       if ( isNaN(y.domain()[0])) {
10902           y.domain([-1,1]);
10903       }
10904
10905
10906       x0 = x0 || x;
10907       y0 = y0 || y;
10908       z0 = z0 || z;
10909
10910       //------------------------------------------------------------
10911
10912
10913       //------------------------------------------------------------
10914       // Setup containers and skeleton of chart
10915
10916       var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10917       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
10918       var defsEnter = wrapEnter.append('defs');
10919       var gEnter = wrapEnter.append('g');
10920       var g = wrap.select('g');
10921
10922       gEnter.append('g').attr('class', 'nv-groups');
10923       gEnter.append('g').attr('class', 'nv-point-paths');
10924
10925       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10926
10927       //------------------------------------------------------------
10928
10929
10930       defsEnter.append('clipPath')
10931           .attr('id', 'nv-edge-clip-' + id)
10932         .append('rect');
10933
10934       wrap.select('#nv-edge-clip-' + id + ' rect')
10935           .attr('width', availableWidth)
10936           .attr('height', availableHeight);
10937
10938       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10939
10940
10941       function updateInteractiveLayer() {
10942
10943         if (!interactive) return false;
10944
10945         var eventElements;
10946
10947         var vertices = d3.merge(data.map(function(group, groupIndex) {
10948             return group.values
10949               .map(function(point, pointIndex) {
10950                 // *Adding noise to make duplicates very unlikely
10951                 // *Injecting series and point index for reference
10952                 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10953                 */
10954                 var pX = getX(point,pointIndex);
10955                 var pY = getY(point,pointIndex);
10956
10957                 return [x(pX)+ Math.random() * 1e-7, 
10958                         y(pY)+ Math.random() * 1e-7, 
10959                         groupIndex, 
10960                         pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
10961               })
10962               .filter(function(pointArray, pointIndex) {
10963                 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10964               })
10965           })
10966         );
10967
10968
10969
10970         //inject series and point index for reference into voronoi
10971         if (useVoronoi === true) {
10972
10973           if (clipVoronoi) {
10974             var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
10975                 .data([id])
10976               .enter();
10977
10978             pointClipsEnter.append('clipPath')
10979                   .attr('class', 'nv-point-clips')
10980                   .attr('id', 'nv-points-clip-' + id);
10981
10982             var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
10983                 .data(vertices);
10984             pointClips.enter().append('circle')
10985                 .attr('r', clipRadius);
10986             pointClips.exit().remove();
10987             pointClips
10988                 .attr('cx', function(d) { return d[0] })
10989                 .attr('cy', function(d) { return d[1] });
10990
10991             wrap.select('.nv-point-paths')
10992                 .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
10993           }
10994
10995
10996           if(vertices.length) {
10997             // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
10998             vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
10999             vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
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           }
11003
11004           var bounds = d3.geom.polygon([
11005               [-10,-10],
11006               [-10,height + 10],
11007               [width + 10,height + 10],
11008               [width + 10,-10]
11009           ]);
11010
11011           var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11012               return {
11013                 'data': bounds.clip(d),
11014                 'series': vertices[i][2],
11015                 'point': vertices[i][3]
11016               }
11017             });
11018
11019
11020           var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11021               .data(voronoi);
11022           pointPaths.enter().append('path')
11023               .attr('class', function(d,i) { return 'nv-path-'+i; });
11024           pointPaths.exit().remove();
11025           pointPaths
11026               .attr('d', function(d) {
11027                 if (d.data.length === 0) 
11028                     return 'M 0 0'
11029                 else 
11030                     return 'M' + d.data.join('L') + 'Z'; 
11031               });
11032
11033           var mouseEventCallback = function(d,mDispatch) {
11034                 if (needsUpdate) return 0;
11035                 var series = data[d.series];
11036                 if (typeof series === 'undefined') return;
11037                 
11038                 var point  = series.values[d.point];
11039
11040                 mDispatch({
11041                   point: point,
11042                   series: series,
11043                   pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11044                   seriesIndex: d.series,
11045                   pointIndex: d.point
11046                 });
11047           };
11048
11049           pointPaths
11050               .on('click', function(d) {
11051                 mouseEventCallback(d, dispatch.elementClick);
11052               })
11053               .on('mouseover', function(d) {
11054                 mouseEventCallback(d, dispatch.elementMouseover);
11055               })
11056               .on('mouseout', function(d, i) {
11057                 mouseEventCallback(d, dispatch.elementMouseout);
11058               });
11059
11060
11061         } else {
11062           /*
11063           // bring data in form needed for click handlers
11064           var dataWithPoints = vertices.map(function(d, i) {
11065               return {
11066                 'data': d,
11067                 'series': vertices[i][2],
11068                 'point': vertices[i][3]
11069               }
11070             });
11071            */
11072
11073           // add event handlers to points instead voronoi paths
11074           wrap.select('.nv-groups').selectAll('.nv-group')
11075             .selectAll('.nv-point')
11076               //.data(dataWithPoints)
11077               //.style('pointer-events', 'auto') // recativate events, disabled by css
11078               .on('click', function(d,i) { 
11079                 //nv.log('test', d, i);
11080                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11081                 var series = data[d.series],
11082                     point  = series.values[i];
11083
11084                 dispatch.elementClick({
11085                   point: point,
11086                   series: series,
11087                   pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11088                   seriesIndex: d.series,
11089                   pointIndex: i
11090                 });
11091               })
11092               .on('mouseover', function(d,i) {
11093                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11094                 var series = data[d.series],
11095                     point  = series.values[i];
11096
11097                 dispatch.elementMouseover({
11098                   point: point,
11099                   series: series,
11100                   pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11101                   seriesIndex: d.series,
11102                   pointIndex: i
11103                 });
11104               })
11105               .on('mouseout', function(d,i) {
11106                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11107                 var series = data[d.series],
11108                     point  = series.values[i];
11109
11110                 dispatch.elementMouseout({
11111                   point: point,
11112                   series: series,
11113                   seriesIndex: d.series,
11114                   pointIndex: i
11115                 });
11116               });
11117           }
11118
11119           needsUpdate = false;
11120       }
11121
11122       needsUpdate = true;
11123
11124       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11125           .data(function(d) { return d }, function(d) { return d.key });
11126       groups.enter().append('g')
11127           .style('stroke-opacity', 1e-6)
11128           .style('fill-opacity', 1e-6);
11129       groups.exit()
11130           .remove();
11131       groups
11132           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11133           .classed('hover', function(d) { return d.hover });
11134       groups
11135           .transition()
11136           .style('fill', function(d,i) { return color(d, i) })
11137           .style('stroke', function(d,i) { return color(d, i) })
11138           .style('stroke-opacity', 1)
11139           .style('fill-opacity', .5);
11140
11141
11142       if (onlyCircles) {
11143
11144         var points = groups.selectAll('circle.nv-point')
11145             .data(function(d) { return d.values }, pointKey);
11146         points.enter().append('circle')
11147             .style('fill', function (d,i) { return d.color })
11148             .style('stroke', function (d,i) { return d.color })
11149             .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11150             .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11151             .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11152         points.exit().remove();
11153         groups.exit().selectAll('path.nv-point').transition()
11154             .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11155             .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11156             .remove();
11157         points.each(function(d,i) {
11158           d3.select(this)
11159             .classed('nv-point', true)
11160             .classed('nv-point-' + i, true)
11161             .classed('hover',false)
11162             ;
11163         });
11164         points.transition()
11165             .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11166             .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11167             .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11168
11169       } else {
11170
11171         var points = groups.selectAll('path.nv-point')
11172             .data(function(d) { return d.values });
11173         points.enter().append('path')
11174             .style('fill', function (d,i) { return d.color })
11175             .style('stroke', function (d,i) { return d.color })
11176             .attr('transform', function(d,i) {
11177               return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11178             })
11179             .attr('d',
11180               d3.svg.symbol()
11181                 .type(getShape)
11182                 .size(function(d,i) { return z(getSize(d,i)) })
11183             );
11184         points.exit().remove();
11185         groups.exit().selectAll('path.nv-point')
11186             .transition()
11187             .attr('transform', function(d,i) {
11188               return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11189             })
11190             .remove();
11191         points.each(function(d,i) {
11192           d3.select(this)
11193             .classed('nv-point', true)
11194             .classed('nv-point-' + i, true)
11195             .classed('hover',false)
11196             ;
11197         });
11198         points.transition()
11199             .attr('transform', function(d,i) {
11200               //nv.log(d,i,getX(d,i), x(getX(d,i)));
11201               return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11202             })
11203             .attr('d',
11204               d3.svg.symbol()
11205                 .type(getShape)
11206                 .size(function(d,i) { return z(getSize(d,i)) })
11207             );
11208       }
11209
11210
11211       // Delay updating the invisible interactive layer for smoother animation
11212       clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11213       timeoutID = setTimeout(updateInteractiveLayer, 300);
11214       //updateInteractiveLayer();
11215
11216       //store old scales for use in transitions on update
11217       x0 = x.copy();
11218       y0 = y.copy();
11219       z0 = z.copy();
11220
11221     });
11222
11223     return chart;
11224   }
11225
11226
11227   //============================================================
11228   // Event Handling/Dispatching (out of chart's scope)
11229   //------------------------------------------------------------
11230   chart.clearHighlights = function() {
11231       //Remove the 'hover' class from all highlighted points.
11232       d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11233   };
11234
11235   chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11236       d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11237           .classed("hover",isHoverOver); 
11238   };
11239
11240
11241   dispatch.on('elementMouseover.point', function(d) {
11242      if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11243   });
11244
11245   dispatch.on('elementMouseout.point', function(d) {
11246      if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11247   });
11248
11249   //============================================================
11250
11251
11252   //============================================================
11253   // Expose Public Variables
11254   //------------------------------------------------------------
11255
11256   chart.dispatch = dispatch;
11257   chart.options = nv.utils.optionsFunc.bind(chart);
11258   
11259   chart.x = function(_) {
11260     if (!arguments.length) return getX;
11261     getX = d3.functor(_);
11262     return chart;
11263   };
11264
11265   chart.y = function(_) {
11266     if (!arguments.length) return getY;
11267     getY = d3.functor(_);
11268     return chart;
11269   };
11270
11271   chart.size = function(_) {
11272     if (!arguments.length) return getSize;
11273     getSize = d3.functor(_);
11274     return chart;
11275   };
11276
11277   chart.margin = function(_) {
11278     if (!arguments.length) return margin;
11279     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11280     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11281     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11282     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11283     return chart;
11284   };
11285
11286   chart.width = function(_) {
11287     if (!arguments.length) return width;
11288     width = _;
11289     return chart;
11290   };
11291
11292   chart.height = function(_) {
11293     if (!arguments.length) return height;
11294     height = _;
11295     return chart;
11296   };
11297
11298   chart.xScale = function(_) {
11299     if (!arguments.length) return x;
11300     x = _;
11301     return chart;
11302   };
11303
11304   chart.yScale = function(_) {
11305     if (!arguments.length) return y;
11306     y = _;
11307     return chart;
11308   };
11309
11310   chart.zScale = function(_) {
11311     if (!arguments.length) return z;
11312     z = _;
11313     return chart;
11314   };
11315
11316   chart.xDomain = function(_) {
11317     if (!arguments.length) return xDomain;
11318     xDomain = _;
11319     return chart;
11320   };
11321
11322   chart.yDomain = function(_) {
11323     if (!arguments.length) return yDomain;
11324     yDomain = _;
11325     return chart;
11326   };
11327
11328   chart.sizeDomain = function(_) {
11329     if (!arguments.length) return sizeDomain;
11330     sizeDomain = _;
11331     return chart;
11332   };
11333
11334   chart.xRange = function(_) {
11335     if (!arguments.length) return xRange;
11336     xRange = _;
11337     return chart;
11338   };
11339
11340   chart.yRange = function(_) {
11341     if (!arguments.length) return yRange;
11342     yRange = _;
11343     return chart;
11344   };
11345
11346   chart.sizeRange = function(_) {
11347     if (!arguments.length) return sizeRange;
11348     sizeRange = _;
11349     return chart;
11350   };
11351
11352   chart.forceX = function(_) {
11353     if (!arguments.length) return forceX;
11354     forceX = _;
11355     return chart;
11356   };
11357
11358   chart.forceY = function(_) {
11359     if (!arguments.length) return forceY;
11360     forceY = _;
11361     return chart;
11362   };
11363
11364   chart.forceSize = function(_) {
11365     if (!arguments.length) return forceSize;
11366     forceSize = _;
11367     return chart;
11368   };
11369
11370   chart.interactive = function(_) {
11371     if (!arguments.length) return interactive;
11372     interactive = _;
11373     return chart;
11374   };
11375
11376   chart.pointKey = function(_) {
11377     if (!arguments.length) return pointKey;
11378     pointKey = _;
11379     return chart;
11380   };
11381
11382   chart.pointActive = function(_) {
11383     if (!arguments.length) return pointActive;
11384     pointActive = _;
11385     return chart;
11386   };
11387
11388   chart.padData = function(_) {
11389     if (!arguments.length) return padData;
11390     padData = _;
11391     return chart;
11392   };
11393
11394   chart.padDataOuter = function(_) {
11395     if (!arguments.length) return padDataOuter;
11396     padDataOuter = _;
11397     return chart;
11398   };
11399
11400   chart.clipEdge = function(_) {
11401     if (!arguments.length) return clipEdge;
11402     clipEdge = _;
11403     return chart;
11404   };
11405
11406   chart.clipVoronoi= function(_) {
11407     if (!arguments.length) return clipVoronoi;
11408     clipVoronoi = _;
11409     return chart;
11410   };
11411
11412   chart.useVoronoi= function(_) {
11413     if (!arguments.length) return useVoronoi;
11414     useVoronoi = _;
11415     if (useVoronoi === false) {
11416         clipVoronoi = false;
11417     }
11418     return chart;
11419   };
11420
11421   chart.clipRadius = function(_) {
11422     if (!arguments.length) return clipRadius;
11423     clipRadius = _;
11424     return chart;
11425   };
11426
11427   chart.color = function(_) {
11428     if (!arguments.length) return color;
11429     color = nv.utils.getColor(_);
11430     return chart;
11431   };
11432
11433   chart.shape = function(_) {
11434     if (!arguments.length) return getShape;
11435     getShape = _;
11436     return chart;
11437   };
11438
11439   chart.onlyCircles = function(_) {
11440     if (!arguments.length) return onlyCircles;
11441     onlyCircles = _;
11442     return chart;
11443   };
11444
11445   chart.id = function(_) {
11446     if (!arguments.length) return id;
11447     id = _;
11448     return chart;
11449   };
11450
11451   chart.singlePoint = function(_) {
11452     if (!arguments.length) return singlePoint;
11453     singlePoint = _;
11454     return chart;
11455   };
11456
11457   //============================================================
11458
11459
11460   return chart;
11461 }
11462 nv.models.scatterChart = function() {
11463   "use strict";
11464   //============================================================
11465   // Public Variables with Default Settings
11466   //------------------------------------------------------------
11467
11468   var scatter      = nv.models.scatter()
11469     , xAxis        = nv.models.axis()
11470     , yAxis        = nv.models.axis()
11471     , legend       = nv.models.legend()
11472     , controls     = nv.models.legend()
11473     , distX        = nv.models.distribution()
11474     , distY        = nv.models.distribution()
11475     ;
11476
11477   var margin       = {top: 30, right: 20, bottom: 50, left: 75}
11478     , width        = null
11479     , height       = null
11480     , color        = nv.utils.defaultColor()
11481     , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
11482     , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
11483     , xPadding     = 0
11484     , yPadding     = 0
11485     , showDistX    = false
11486     , showDistY    = false
11487     , showLegend   = true
11488     , showXAxis    = true
11489     , showYAxis    = true
11490     , rightAlignYAxis = false
11491     , showControls = !!d3.fisheye
11492     , fisheye      = 0
11493     , pauseFisheye = false
11494     , tooltips     = true
11495     , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
11496     , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
11497     , tooltip      = null
11498     , state = {}
11499     , defaultState = null
11500     , dispatch     = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11501     , noData       = "No Data Available."
11502     , transitionDuration = 250
11503     ;
11504
11505   scatter
11506     .xScale(x)
11507     .yScale(y)
11508     ;
11509   xAxis
11510     .orient('bottom')
11511     .tickPadding(10)
11512     ;
11513   yAxis
11514     .orient((rightAlignYAxis) ? 'right' : 'left')
11515     .tickPadding(10)
11516     ;
11517   distX
11518     .axis('x')
11519     ;
11520   distY
11521     .axis('y')
11522     ;
11523
11524   controls.updateState(false);
11525
11526   //============================================================
11527
11528
11529   //============================================================
11530   // Private Variables
11531   //------------------------------------------------------------
11532
11533   var x0, y0;
11534
11535   var showTooltip = function(e, offsetElement) {
11536     //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
11537
11538     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11539         top = e.pos[1] + ( offsetElement.offsetTop || 0),
11540         leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11541         topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
11542         leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
11543         topY = e.pos[1] + ( offsetElement.offsetTop || 0),
11544         xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
11545         yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
11546
11547       if( tooltipX != null )
11548           nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
11549       if( tooltipY != null )
11550           nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
11551       if( tooltip != null )
11552           nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
11553   };
11554
11555   var controlsData = [
11556     { key: 'Magnify', disabled: true }
11557   ];
11558
11559   //============================================================
11560
11561
11562   function chart(selection) {
11563     selection.each(function(data) {
11564       var container = d3.select(this),
11565           that = this;
11566
11567       var availableWidth = (width  || parseInt(container.style('width')) || 960)
11568                              - margin.left - margin.right,
11569           availableHeight = (height || parseInt(container.style('height')) || 400)
11570                              - margin.top - margin.bottom;
11571
11572       chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
11573       chart.container = this;
11574
11575       //set state.disabled
11576       state.disabled = data.map(function(d) { return !!d.disabled });
11577
11578       if (!defaultState) {
11579         var key;
11580         defaultState = {};
11581         for (key in state) {
11582           if (state[key] instanceof Array)
11583             defaultState[key] = state[key].slice(0);
11584           else
11585             defaultState[key] = state[key];
11586         }
11587       }
11588
11589       //------------------------------------------------------------
11590       // Display noData message if there's nothing to show.
11591
11592       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11593         var noDataText = container.selectAll('.nv-noData').data([noData]);
11594
11595         noDataText.enter().append('text')
11596           .attr('class', 'nvd3 nv-noData')
11597           .attr('dy', '-.7em')
11598           .style('text-anchor', 'middle');
11599
11600         noDataText
11601           .attr('x', margin.left + availableWidth / 2)
11602           .attr('y', margin.top + availableHeight / 2)
11603           .text(function(d) { return d });
11604
11605         return chart;
11606       } else {
11607         container.selectAll('.nv-noData').remove();
11608       }
11609
11610       //------------------------------------------------------------
11611
11612
11613       //------------------------------------------------------------
11614       // Setup Scales
11615
11616       x0 = x0 || x;
11617       y0 = y0 || y;
11618
11619       //------------------------------------------------------------
11620
11621
11622       //------------------------------------------------------------
11623       // Setup containers and skeleton of chart
11624
11625       var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11626       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11627       var gEnter = wrapEnter.append('g');
11628       var g = wrap.select('g');
11629
11630       // background for pointer events
11631       gEnter.append('rect').attr('class', 'nvd3 nv-background');
11632
11633       gEnter.append('g').attr('class', 'nv-x nv-axis');
11634       gEnter.append('g').attr('class', 'nv-y nv-axis');
11635       gEnter.append('g').attr('class', 'nv-scatterWrap');
11636       gEnter.append('g').attr('class', 'nv-distWrap');
11637       gEnter.append('g').attr('class', 'nv-legendWrap');
11638       gEnter.append('g').attr('class', 'nv-controlsWrap');
11639
11640       //------------------------------------------------------------
11641
11642
11643       //------------------------------------------------------------
11644       // Legend
11645
11646       if (showLegend) {
11647         var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
11648         legend.width(legendWidth);
11649
11650         wrap.select('.nv-legendWrap')
11651             .datum(data)
11652             .call(legend);
11653
11654         if ( margin.top != legend.height()) {
11655           margin.top = legend.height();
11656           availableHeight = (height || parseInt(container.style('height')) || 400)
11657                              - margin.top - margin.bottom;
11658         }
11659
11660         wrap.select('.nv-legendWrap')
11661             .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
11662       }
11663
11664       //------------------------------------------------------------
11665
11666
11667       //------------------------------------------------------------
11668       // Controls
11669
11670       if (showControls) {
11671         controls.width(180).color(['#444']);
11672         g.select('.nv-controlsWrap')
11673             .datum(controlsData)
11674             .attr('transform', 'translate(0,' + (-margin.top) +')')
11675             .call(controls);
11676       }
11677
11678       //------------------------------------------------------------
11679
11680
11681       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11682
11683       if (rightAlignYAxis) {
11684           g.select(".nv-y.nv-axis")
11685               .attr("transform", "translate(" + availableWidth + ",0)");
11686       }
11687
11688       //------------------------------------------------------------
11689       // Main Chart Component(s)
11690
11691       scatter
11692           .width(availableWidth)
11693           .height(availableHeight)
11694           .color(data.map(function(d,i) {
11695             return d.color || color(d, i);
11696           }).filter(function(d,i) { return !data[i].disabled }));
11697
11698       if (xPadding !== 0)
11699         scatter.xDomain(null);
11700
11701       if (yPadding !== 0)
11702         scatter.yDomain(null);
11703
11704       wrap.select('.nv-scatterWrap')
11705           .datum(data.filter(function(d) { return !d.disabled }))
11706           .call(scatter);
11707
11708       //Adjust for x and y padding
11709       if (xPadding !== 0) {
11710         var xRange = x.domain()[1] - x.domain()[0];
11711         scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
11712       }
11713
11714       if (yPadding !== 0) {
11715         var yRange = y.domain()[1] - y.domain()[0];
11716         scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
11717       }
11718
11719       //Only need to update the scatter again if x/yPadding changed the domain.
11720       if (yPadding !== 0 || xPadding !== 0) {
11721         wrap.select('.nv-scatterWrap')
11722             .datum(data.filter(function(d) { return !d.disabled }))
11723             .call(scatter);
11724       }
11725
11726       //------------------------------------------------------------
11727
11728
11729       //------------------------------------------------------------
11730       // Setup Axes
11731       if (showXAxis) {
11732         xAxis
11733             .scale(x)
11734             .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
11735             .tickSize( -availableHeight , 0);
11736
11737         g.select('.nv-x.nv-axis')
11738             .attr('transform', 'translate(0,' + y.range()[0] + ')')
11739             .call(xAxis);
11740
11741       }
11742
11743       if (showYAxis) {
11744         yAxis
11745             .scale(y)
11746             .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
11747             .tickSize( -availableWidth, 0);
11748
11749         g.select('.nv-y.nv-axis')
11750             .call(yAxis);
11751       }
11752
11753
11754       if (showDistX) {
11755         distX
11756             .getData(scatter.x())
11757             .scale(x)
11758             .width(availableWidth)
11759             .color(data.map(function(d,i) {
11760               return d.color || color(d, i);
11761             }).filter(function(d,i) { return !data[i].disabled }));
11762         gEnter.select('.nv-distWrap').append('g')
11763             .attr('class', 'nv-distributionX');
11764         g.select('.nv-distributionX')
11765             .attr('transform', 'translate(0,' + y.range()[0] + ')')
11766             .datum(data.filter(function(d) { return !d.disabled }))
11767             .call(distX);
11768       }
11769
11770       if (showDistY) {
11771         distY
11772             .getData(scatter.y())
11773             .scale(y)
11774             .width(availableHeight)
11775             .color(data.map(function(d,i) {
11776               return d.color || color(d, i);
11777             }).filter(function(d,i) { return !data[i].disabled }));
11778         gEnter.select('.nv-distWrap').append('g')
11779             .attr('class', 'nv-distributionY');
11780         g.select('.nv-distributionY')
11781             .attr('transform', 
11782               'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11783             .datum(data.filter(function(d) { return !d.disabled }))
11784             .call(distY);
11785       }
11786
11787       //------------------------------------------------------------
11788
11789
11790
11791
11792       if (d3.fisheye) {
11793         g.select('.nv-background')
11794             .attr('width', availableWidth)
11795             .attr('height', availableHeight);
11796
11797         g.select('.nv-background').on('mousemove', updateFisheye);
11798         g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
11799         scatter.dispatch.on('elementClick.freezeFisheye', function() {
11800           pauseFisheye = !pauseFisheye;
11801         });
11802       }
11803
11804
11805       function updateFisheye() {
11806         if (pauseFisheye) {
11807           g.select('.nv-point-paths').style('pointer-events', 'all');
11808           return false;
11809         }
11810
11811         g.select('.nv-point-paths').style('pointer-events', 'none' );
11812
11813         var mouse = d3.mouse(this);
11814         x.distortion(fisheye).focus(mouse[0]);
11815         y.distortion(fisheye).focus(mouse[1]);
11816
11817         g.select('.nv-scatterWrap')
11818             .call(scatter);
11819
11820         if (showXAxis)
11821           g.select('.nv-x.nv-axis').call(xAxis);
11822         
11823         if (showYAxis)
11824           g.select('.nv-y.nv-axis').call(yAxis);
11825         
11826         g.select('.nv-distributionX')
11827             .datum(data.filter(function(d) { return !d.disabled }))
11828             .call(distX);
11829         g.select('.nv-distributionY')
11830             .datum(data.filter(function(d) { return !d.disabled }))
11831             .call(distY);
11832       }
11833
11834
11835
11836       //============================================================
11837       // Event Handling/Dispatching (in chart's scope)
11838       //------------------------------------------------------------
11839
11840       controls.dispatch.on('legendClick', function(d,i) {
11841         d.disabled = !d.disabled;
11842
11843         fisheye = d.disabled ? 0 : 2.5;
11844         g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
11845         g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
11846
11847         if (d.disabled) {
11848           x.distortion(fisheye).focus(0);
11849           y.distortion(fisheye).focus(0);
11850
11851           g.select('.nv-scatterWrap').call(scatter);
11852           g.select('.nv-x.nv-axis').call(xAxis);
11853           g.select('.nv-y.nv-axis').call(yAxis);
11854         } else {
11855           pauseFisheye = false;
11856         }
11857
11858         chart.update();
11859       });
11860
11861       legend.dispatch.on('stateChange', function(newState) {
11862         state.disabled = newState.disabled;
11863         dispatch.stateChange(state);
11864         chart.update();
11865       });
11866
11867       scatter.dispatch.on('elementMouseover.tooltip', function(e) {
11868         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11869             .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
11870         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11871             .attr('x2', e.pos[0] + distX.size());
11872
11873         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11874         dispatch.tooltipShow(e);
11875       });
11876
11877       dispatch.on('tooltipShow', function(e) {
11878         if (tooltips) showTooltip(e, that.parentNode);
11879       });
11880
11881       // Update chart from a state object passed to event handler
11882       dispatch.on('changeState', function(e) {
11883
11884         if (typeof e.disabled !== 'undefined') {
11885           data.forEach(function(series,i) {
11886             series.disabled = e.disabled[i];
11887           });
11888
11889           state.disabled = e.disabled;
11890         }
11891
11892         chart.update();
11893       });
11894
11895       //============================================================
11896
11897
11898       //store old scales for use in transitions on update
11899       x0 = x.copy();
11900       y0 = y.copy();
11901
11902
11903     });
11904
11905     return chart;
11906   }
11907
11908
11909   //============================================================
11910   // Event Handling/Dispatching (out of chart's scope)
11911   //------------------------------------------------------------
11912
11913   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11914     dispatch.tooltipHide(e);
11915
11916     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11917         .attr('y1', 0);
11918     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11919         .attr('x2', distY.size());
11920   });
11921   dispatch.on('tooltipHide', function() {
11922     if (tooltips) nv.tooltip.cleanup();
11923   });
11924
11925   //============================================================
11926
11927
11928   //============================================================
11929   // Expose Public Variables
11930   //------------------------------------------------------------
11931
11932   // expose chart's sub-components
11933   chart.dispatch = dispatch;
11934   chart.scatter = scatter;
11935   chart.legend = legend;
11936   chart.controls = controls;
11937   chart.xAxis = xAxis;
11938   chart.yAxis = yAxis;
11939   chart.distX = distX;
11940   chart.distY = distY;
11941
11942   d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
11943   chart.options = nv.utils.optionsFunc.bind(chart);
11944   
11945   chart.margin = function(_) {
11946     if (!arguments.length) return margin;
11947     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11948     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11949     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11950     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11951     return chart;
11952   };
11953
11954   chart.width = function(_) {
11955     if (!arguments.length) return width;
11956     width = _;
11957     return chart;
11958   };
11959
11960   chart.height = function(_) {
11961     if (!arguments.length) return height;
11962     height = _;
11963     return chart;
11964   };
11965
11966   chart.color = function(_) {
11967     if (!arguments.length) return color;
11968     color = nv.utils.getColor(_);
11969     legend.color(color);
11970     distX.color(color);
11971     distY.color(color);
11972     return chart;
11973   };
11974
11975   chart.showDistX = function(_) {
11976     if (!arguments.length) return showDistX;
11977     showDistX = _;
11978     return chart;
11979   };
11980
11981   chart.showDistY = function(_) {
11982     if (!arguments.length) return showDistY;
11983     showDistY = _;
11984     return chart;
11985   };
11986
11987   chart.showControls = function(_) {
11988     if (!arguments.length) return showControls;
11989     showControls = _;
11990     return chart;
11991   };
11992
11993   chart.showLegend = function(_) {
11994     if (!arguments.length) return showLegend;
11995     showLegend = _;
11996     return chart;
11997   };
11998
11999   chart.showXAxis = function(_) {
12000     if (!arguments.length) return showXAxis;
12001     showXAxis = _;
12002     return chart;
12003   };
12004
12005   chart.showYAxis = function(_) {
12006     if (!arguments.length) return showYAxis;
12007     showYAxis = _;
12008     return chart;
12009   };
12010
12011   chart.rightAlignYAxis = function(_) {
12012     if(!arguments.length) return rightAlignYAxis;
12013     rightAlignYAxis = _;
12014     yAxis.orient( (_) ? 'right' : 'left');
12015     return chart;
12016   };
12017
12018
12019   chart.fisheye = function(_) {
12020     if (!arguments.length) return fisheye;
12021     fisheye = _;
12022     return chart;
12023   };
12024
12025   chart.xPadding = function(_) {
12026     if (!arguments.length) return xPadding;
12027     xPadding = _;
12028     return chart;
12029   };
12030
12031   chart.yPadding = function(_) {
12032     if (!arguments.length) return yPadding;
12033     yPadding = _;
12034     return chart;
12035   };
12036
12037   chart.tooltips = function(_) {
12038     if (!arguments.length) return tooltips;
12039     tooltips = _;
12040     return chart;
12041   };
12042
12043   chart.tooltipContent = function(_) {
12044     if (!arguments.length) return tooltip;
12045     tooltip = _;
12046     return chart;
12047   };
12048
12049   chart.tooltipXContent = function(_) {
12050     if (!arguments.length) return tooltipX;
12051     tooltipX = _;
12052     return chart;
12053   };
12054
12055   chart.tooltipYContent = function(_) {
12056     if (!arguments.length) return tooltipY;
12057     tooltipY = _;
12058     return chart;
12059   };
12060
12061   chart.state = function(_) {
12062     if (!arguments.length) return state;
12063     state = _;
12064     return chart;
12065   };
12066
12067   chart.defaultState = function(_) {
12068     if (!arguments.length) return defaultState;
12069     defaultState = _;
12070     return chart;
12071   };
12072   
12073   chart.noData = function(_) {
12074     if (!arguments.length) return noData;
12075     noData = _;
12076     return chart;
12077   };
12078
12079   chart.transitionDuration = function(_) {
12080     if (!arguments.length) return transitionDuration;
12081     transitionDuration = _;
12082     return chart;
12083   };
12084
12085   //============================================================
12086
12087
12088   return chart;
12089 }
12090
12091 nv.models.scatterPlusLineChart = function() {
12092   "use strict";
12093   //============================================================
12094   // Public Variables with Default Settings
12095   //------------------------------------------------------------
12096
12097   var scatter      = nv.models.scatter()
12098     , xAxis        = nv.models.axis()
12099     , yAxis        = nv.models.axis()
12100     , legend       = nv.models.legend()
12101     , controls     = nv.models.legend()
12102     , distX        = nv.models.distribution()
12103     , distY        = nv.models.distribution()
12104     ;
12105
12106   var margin       = {top: 30, right: 20, bottom: 50, left: 75}
12107     , width        = null
12108     , height       = null
12109     , color        = nv.utils.defaultColor()
12110     , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12111     , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12112     , showDistX    = false
12113     , showDistY    = false
12114     , showLegend   = true
12115     , showXAxis    = true
12116     , showYAxis    = true
12117     , rightAlignYAxis = false
12118     , showControls = !!d3.fisheye
12119     , fisheye      = 0
12120     , pauseFisheye = false
12121     , tooltips     = true
12122     , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
12123     , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
12124     , tooltip      = function(key, x, y, date) { return '<h3>' + key + '</h3>' 
12125                                                       + '<p>' + date + '</p>' }
12126     , state = {}
12127     , defaultState = null
12128     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
12129     , noData       = "No Data Available."
12130     , transitionDuration = 250
12131     ;
12132
12133   scatter
12134     .xScale(x)
12135     .yScale(y)
12136     ;
12137   xAxis
12138     .orient('bottom')
12139     .tickPadding(10)
12140     ;
12141   yAxis
12142     .orient((rightAlignYAxis) ? 'right' : 'left')
12143     .tickPadding(10)
12144     ;
12145   distX
12146     .axis('x')
12147     ;
12148   distY
12149     .axis('y')
12150     ;
12151   
12152   controls.updateState(false);
12153   //============================================================
12154
12155
12156   //============================================================
12157   // Private Variables
12158   //------------------------------------------------------------
12159
12160   var x0, y0;
12161
12162   var showTooltip = function(e, offsetElement) {
12163     //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12164
12165     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12166         top = e.pos[1] + ( offsetElement.offsetTop || 0),
12167         leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12168         topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12169         leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12170         topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12171         xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12172         yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12173
12174       if( tooltipX != null )
12175           nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12176       if( tooltipY != null )
12177           nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12178       if( tooltip != null )
12179           nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12180   };
12181
12182   var controlsData = [
12183     { key: 'Magnify', disabled: true }
12184   ];
12185
12186   //============================================================
12187
12188
12189   function chart(selection) {
12190     selection.each(function(data) {
12191       var container = d3.select(this),
12192           that = this;
12193
12194       var availableWidth = (width  || parseInt(container.style('width')) || 960)
12195                              - margin.left - margin.right,
12196           availableHeight = (height || parseInt(container.style('height')) || 400)
12197                              - margin.top - margin.bottom;
12198
12199       chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
12200       chart.container = this;
12201
12202       //set state.disabled
12203       state.disabled = data.map(function(d) { return !!d.disabled });
12204
12205       if (!defaultState) {
12206         var key;
12207         defaultState = {};
12208         for (key in state) {
12209           if (state[key] instanceof Array)
12210             defaultState[key] = state[key].slice(0);
12211           else
12212             defaultState[key] = state[key];
12213         }
12214       }
12215
12216       //------------------------------------------------------------
12217       // Display noData message if there's nothing to show.
12218
12219       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12220         var noDataText = container.selectAll('.nv-noData').data([noData]);
12221
12222         noDataText.enter().append('text')
12223           .attr('class', 'nvd3 nv-noData')
12224           .attr('dy', '-.7em')
12225           .style('text-anchor', 'middle');
12226
12227         noDataText
12228           .attr('x', margin.left + availableWidth / 2)
12229           .attr('y', margin.top + availableHeight / 2)
12230           .text(function(d) { return d });
12231
12232         return chart;
12233       } else {
12234         container.selectAll('.nv-noData').remove();
12235       }
12236
12237       //------------------------------------------------------------
12238
12239
12240       //------------------------------------------------------------
12241       // Setup Scales
12242
12243       x = scatter.xScale();
12244       y = scatter.yScale();
12245
12246       x0 = x0 || x;
12247       y0 = y0 || y;
12248
12249       //------------------------------------------------------------
12250
12251
12252       //------------------------------------------------------------
12253       // Setup containers and skeleton of chart
12254
12255       var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12256       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12257       var gEnter = wrapEnter.append('g');
12258       var g = wrap.select('g')
12259
12260       // background for pointer events
12261       gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12262
12263       gEnter.append('g').attr('class', 'nv-x nv-axis');
12264       gEnter.append('g').attr('class', 'nv-y nv-axis');
12265       gEnter.append('g').attr('class', 'nv-scatterWrap');
12266       gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12267       gEnter.append('g').attr('class', 'nv-distWrap');
12268       gEnter.append('g').attr('class', 'nv-legendWrap');
12269       gEnter.append('g').attr('class', 'nv-controlsWrap');
12270
12271       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12272
12273       if (rightAlignYAxis) {
12274           g.select(".nv-y.nv-axis")
12275               .attr("transform", "translate(" + availableWidth + ",0)");
12276       }
12277
12278       //------------------------------------------------------------
12279
12280
12281       //------------------------------------------------------------
12282       // Legend
12283
12284       if (showLegend) {
12285         legend.width( availableWidth / 2 );
12286
12287         wrap.select('.nv-legendWrap')
12288             .datum(data)
12289             .call(legend);
12290
12291         if ( margin.top != legend.height()) {
12292           margin.top = legend.height();
12293           availableHeight = (height || parseInt(container.style('height')) || 400)
12294                              - margin.top - margin.bottom;
12295         }
12296
12297         wrap.select('.nv-legendWrap')
12298             .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
12299       }
12300
12301       //------------------------------------------------------------
12302
12303
12304       //------------------------------------------------------------
12305       // Controls
12306
12307       if (showControls) {
12308         controls.width(180).color(['#444']);
12309         g.select('.nv-controlsWrap')
12310             .datum(controlsData)
12311             .attr('transform', 'translate(0,' + (-margin.top) +')')
12312             .call(controls);
12313       }
12314
12315       //------------------------------------------------------------
12316
12317
12318       //------------------------------------------------------------
12319       // Main Chart Component(s)
12320
12321       scatter
12322           .width(availableWidth)
12323           .height(availableHeight)
12324           .color(data.map(function(d,i) {
12325             return d.color || color(d, i);
12326           }).filter(function(d,i) { return !data[i].disabled }))
12327
12328       wrap.select('.nv-scatterWrap')
12329           .datum(data.filter(function(d) { return !d.disabled }))
12330           .call(scatter);
12331
12332       wrap.select('.nv-regressionLinesWrap')
12333           .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
12334
12335       var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
12336                       .data(function(d) {return d });
12337       
12338       regWrap.enter().append('g').attr('class', 'nv-regLines');
12339
12340       var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
12341       var regLineEnter = regLine.enter()
12342                        .append('line').attr('class', 'nv-regLine')
12343                        .style('stroke-opacity', 0);
12344
12345       regLine
12346           .transition()
12347           .attr('x1', x.range()[0])
12348           .attr('x2', x.range()[1])
12349           .attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
12350           .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
12351           .style('stroke', function(d,i,j) { return color(d,j) })
12352           .style('stroke-opacity', function(d,i) {
12353             return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 
12354           });
12355
12356       //------------------------------------------------------------
12357
12358
12359       //------------------------------------------------------------
12360       // Setup Axes
12361
12362       if (showXAxis) {
12363         xAxis
12364             .scale(x)
12365             .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
12366             .tickSize( -availableHeight , 0);
12367
12368         g.select('.nv-x.nv-axis')
12369             .attr('transform', 'translate(0,' + y.range()[0] + ')')
12370             .call(xAxis);
12371       }
12372
12373       if (showYAxis) {
12374         yAxis
12375             .scale(y)
12376             .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
12377             .tickSize( -availableWidth, 0);
12378
12379         g.select('.nv-y.nv-axis')
12380             .call(yAxis);
12381       }
12382
12383
12384       if (showDistX) {
12385         distX
12386             .getData(scatter.x())
12387             .scale(x)
12388             .width(availableWidth)
12389             .color(data.map(function(d,i) {
12390               return d.color || color(d, i);
12391             }).filter(function(d,i) { return !data[i].disabled }));
12392         gEnter.select('.nv-distWrap').append('g')
12393             .attr('class', 'nv-distributionX');
12394         g.select('.nv-distributionX')
12395             .attr('transform', 'translate(0,' + y.range()[0] + ')')
12396             .datum(data.filter(function(d) { return !d.disabled }))
12397             .call(distX);
12398       }
12399
12400       if (showDistY) {
12401         distY
12402             .getData(scatter.y())
12403             .scale(y)
12404             .width(availableHeight)
12405             .color(data.map(function(d,i) {
12406               return d.color || color(d, i);
12407             }).filter(function(d,i) { return !data[i].disabled }));
12408         gEnter.select('.nv-distWrap').append('g')
12409             .attr('class', 'nv-distributionY');
12410         g.select('.nv-distributionY')
12411             .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12412             .datum(data.filter(function(d) { return !d.disabled }))
12413             .call(distY);
12414       }
12415
12416       //------------------------------------------------------------
12417
12418
12419
12420
12421       if (d3.fisheye) {
12422         g.select('.nv-background')
12423             .attr('width', availableWidth)
12424             .attr('height', availableHeight)
12425             ;
12426
12427         g.select('.nv-background').on('mousemove', updateFisheye);
12428         g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12429         scatter.dispatch.on('elementClick.freezeFisheye', function() {
12430           pauseFisheye = !pauseFisheye;
12431         });
12432       }
12433
12434
12435       function updateFisheye() {
12436         if (pauseFisheye) {
12437           g.select('.nv-point-paths').style('pointer-events', 'all');
12438           return false;
12439         }
12440
12441         g.select('.nv-point-paths').style('pointer-events', 'none' );
12442
12443         var mouse = d3.mouse(this);
12444         x.distortion(fisheye).focus(mouse[0]);
12445         y.distortion(fisheye).focus(mouse[1]);
12446
12447         g.select('.nv-scatterWrap')
12448             .datum(data.filter(function(d) { return !d.disabled }))
12449             .call(scatter);
12450
12451         if (showXAxis)
12452           g.select('.nv-x.nv-axis').call(xAxis);
12453
12454         if (showYAxis)
12455           g.select('.nv-y.nv-axis').call(yAxis);
12456         
12457         g.select('.nv-distributionX')
12458             .datum(data.filter(function(d) { return !d.disabled }))
12459             .call(distX);
12460         g.select('.nv-distributionY')
12461             .datum(data.filter(function(d) { return !d.disabled }))
12462             .call(distY);
12463       }
12464
12465
12466
12467       //============================================================
12468       // Event Handling/Dispatching (in chart's scope)
12469       //------------------------------------------------------------
12470
12471       controls.dispatch.on('legendClick', function(d,i) {
12472         d.disabled = !d.disabled;
12473
12474         fisheye = d.disabled ? 0 : 2.5;
12475         g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12476         g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12477
12478         if (d.disabled) {
12479           x.distortion(fisheye).focus(0);
12480           y.distortion(fisheye).focus(0);
12481
12482           g.select('.nv-scatterWrap').call(scatter);
12483           g.select('.nv-x.nv-axis').call(xAxis);
12484           g.select('.nv-y.nv-axis').call(yAxis);
12485         } else {
12486           pauseFisheye = false;
12487         }
12488
12489         chart.update();
12490       });
12491
12492       legend.dispatch.on('stateChange', function(newState) { 
12493         state = newState;
12494         dispatch.stateChange(state);
12495         chart.update();
12496       });
12497
12498
12499       scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12500         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12501             .attr('y1', e.pos[1] - availableHeight);
12502         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12503             .attr('x2', e.pos[0] + distX.size());
12504
12505         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12506         dispatch.tooltipShow(e);
12507       });
12508
12509       dispatch.on('tooltipShow', function(e) {
12510         if (tooltips) showTooltip(e, that.parentNode);
12511       });
12512
12513       // Update chart from a state object passed to event handler
12514       dispatch.on('changeState', function(e) {
12515
12516         if (typeof e.disabled !== 'undefined') {
12517           data.forEach(function(series,i) {
12518             series.disabled = e.disabled[i];
12519           });
12520
12521           state.disabled = e.disabled;
12522         }
12523
12524         chart.update();
12525       });
12526
12527       //============================================================
12528
12529
12530       //store old scales for use in transitions on update
12531       x0 = x.copy();
12532       y0 = y.copy();
12533
12534
12535     });
12536
12537     return chart;
12538   }
12539
12540
12541   //============================================================
12542   // Event Handling/Dispatching (out of chart's scope)
12543   //------------------------------------------------------------
12544
12545   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12546     dispatch.tooltipHide(e);
12547
12548     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12549         .attr('y1', 0);
12550     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12551         .attr('x2', distY.size());
12552   });
12553   dispatch.on('tooltipHide', function() {
12554     if (tooltips) nv.tooltip.cleanup();
12555   });
12556
12557   //============================================================
12558
12559
12560   //============================================================
12561   // Expose Public Variables
12562   //------------------------------------------------------------
12563
12564   // expose chart's sub-components
12565   chart.dispatch = dispatch;
12566   chart.scatter = scatter;
12567   chart.legend = legend;
12568   chart.controls = controls;
12569   chart.xAxis = xAxis;
12570   chart.yAxis = yAxis;
12571   chart.distX = distX;
12572   chart.distY = distY;
12573
12574   d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
12575
12576   chart.options = nv.utils.optionsFunc.bind(chart);
12577   
12578   chart.margin = function(_) {
12579     if (!arguments.length) return margin;
12580     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
12581     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
12582     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12583     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
12584     return chart;
12585   };
12586
12587   chart.width = function(_) {
12588     if (!arguments.length) return width;
12589     width = _;
12590     return chart;
12591   };
12592
12593   chart.height = function(_) {
12594     if (!arguments.length) return height;
12595     height = _;
12596     return chart;
12597   };
12598
12599   chart.color = function(_) {
12600     if (!arguments.length) return color;
12601     color = nv.utils.getColor(_);
12602     legend.color(color);
12603     distX.color(color);
12604     distY.color(color);
12605     return chart;
12606   };
12607
12608   chart.showDistX = function(_) {
12609     if (!arguments.length) return showDistX;
12610     showDistX = _;
12611     return chart;
12612   };
12613
12614   chart.showDistY = function(_) {
12615     if (!arguments.length) return showDistY;
12616     showDistY = _;
12617     return chart;
12618   };
12619
12620   chart.showControls = function(_) {
12621     if (!arguments.length) return showControls;
12622     showControls = _;
12623     return chart;
12624   };
12625
12626   chart.showLegend = function(_) {
12627     if (!arguments.length) return showLegend;
12628     showLegend = _;
12629     return chart;
12630   };
12631
12632   chart.showXAxis = function(_) {
12633     if (!arguments.length) return showXAxis;
12634     showXAxis = _;
12635     return chart;
12636   };
12637
12638   chart.showYAxis = function(_) {
12639     if (!arguments.length) return showYAxis;
12640     showYAxis = _;
12641     return chart;
12642   };
12643
12644   chart.rightAlignYAxis = function(_) {
12645     if(!arguments.length) return rightAlignYAxis;
12646     rightAlignYAxis = _;
12647     yAxis.orient( (_) ? 'right' : 'left');
12648     return chart;
12649   };
12650
12651   chart.fisheye = function(_) {
12652     if (!arguments.length) return fisheye;
12653     fisheye = _;
12654     return chart;
12655   };
12656
12657   chart.tooltips = function(_) {
12658     if (!arguments.length) return tooltips;
12659     tooltips = _;
12660     return chart;
12661   };
12662
12663   chart.tooltipContent = function(_) {
12664     if (!arguments.length) return tooltip;
12665     tooltip = _;
12666     return chart;
12667   };
12668
12669   chart.tooltipXContent = function(_) {
12670     if (!arguments.length) return tooltipX;
12671     tooltipX = _;
12672     return chart;
12673   };
12674
12675   chart.tooltipYContent = function(_) {
12676     if (!arguments.length) return tooltipY;
12677     tooltipY = _;
12678     return chart;
12679   };
12680
12681   chart.state = function(_) {
12682     if (!arguments.length) return state;
12683     state = _;
12684     return chart;
12685   };
12686
12687   chart.defaultState = function(_) {
12688     if (!arguments.length) return defaultState;
12689     defaultState = _;
12690     return chart;
12691   };
12692
12693   chart.noData = function(_) {
12694     if (!arguments.length) return noData;
12695     noData = _;
12696     return chart;
12697   };
12698
12699   chart.transitionDuration = function(_) {
12700     if (!arguments.length) return transitionDuration;
12701     transitionDuration = _;
12702     return chart;
12703   };
12704
12705   //============================================================
12706
12707
12708   return chart;
12709 }
12710
12711 nv.models.sparkline = function() {
12712   "use strict";
12713   //============================================================
12714   // Public Variables with Default Settings
12715   //------------------------------------------------------------
12716
12717   var margin = {top: 2, right: 0, bottom: 2, left: 0}
12718     , width = 400
12719     , height = 32
12720     , animate = true
12721     , x = d3.scale.linear()
12722     , y = d3.scale.linear()
12723     , getX = function(d) { return d.x }
12724     , getY = function(d) { return d.y }
12725     , color = nv.utils.getColor(['#000'])
12726     , xDomain
12727     , yDomain
12728     , xRange
12729     , yRange
12730     ;
12731
12732   //============================================================
12733
12734
12735   function chart(selection) {
12736     selection.each(function(data) {
12737       var availableWidth = width - margin.left - margin.right,
12738           availableHeight = height - margin.top - margin.bottom,
12739           container = d3.select(this);
12740
12741
12742       //------------------------------------------------------------
12743       // Setup Scales
12744
12745       x   .domain(xDomain || d3.extent(data, getX ))
12746           .range(xRange || [0, availableWidth]);
12747
12748       y   .domain(yDomain || d3.extent(data, getY ))
12749           .range(yRange || [availableHeight, 0]);
12750
12751       //------------------------------------------------------------
12752
12753
12754       //------------------------------------------------------------
12755       // Setup containers and skeleton of chart
12756
12757       var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
12758       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
12759       var gEnter = wrapEnter.append('g');
12760       var g = wrap.select('g');
12761
12762       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
12763
12764       //------------------------------------------------------------
12765
12766
12767       var paths = wrap.selectAll('path')
12768           .data(function(d) { return [d] });
12769       paths.enter().append('path');
12770       paths.exit().remove();
12771       paths
12772           .style('stroke', function(d,i) { return d.color || color(d, i) })
12773           .attr('d', d3.svg.line()
12774             .x(function(d,i) { return x(getX(d,i)) })
12775             .y(function(d,i) { return y(getY(d,i)) })
12776           );
12777
12778
12779       // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
12780       var points = wrap.selectAll('circle.nv-point')
12781           .data(function(data) {
12782               var yValues = data.map(function(d, i) { return getY(d,i); });
12783               function pointIndex(index) {
12784                   if (index != -1) {
12785                       var result = data[index];
12786                       result.pointIndex = index;
12787                       return result;
12788                   } else {
12789                       return null;
12790                   }
12791               }
12792               var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
12793                   minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
12794                   currentPoint = pointIndex(yValues.length - 1);
12795               return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
12796           });
12797       points.enter().append('circle');
12798       points.exit().remove();
12799       points
12800           .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
12801           .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
12802           .attr('r', 2)
12803           .attr('class', function(d,i) {
12804             return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
12805                    getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
12806           });
12807     });
12808
12809     return chart;
12810   }
12811
12812
12813   //============================================================
12814   // Expose Public Variables
12815   //------------------------------------------------------------
12816   chart.options = nv.utils.optionsFunc.bind(chart);
12817   
12818   chart.margin = function(_) {
12819     if (!arguments.length) return margin;
12820     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
12821     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
12822     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12823     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
12824     return chart;
12825   };
12826
12827   chart.width = function(_) {
12828     if (!arguments.length) return width;
12829     width = _;
12830     return chart;
12831   };
12832
12833   chart.height = function(_) {
12834     if (!arguments.length) return height;
12835     height = _;
12836     return chart;
12837   };
12838
12839   chart.x = function(_) {
12840     if (!arguments.length) return getX;
12841     getX = d3.functor(_);
12842     return chart;
12843   };
12844
12845   chart.y = function(_) {
12846     if (!arguments.length) return getY;
12847     getY = d3.functor(_);
12848     return chart;
12849   };
12850
12851   chart.xScale = function(_) {
12852     if (!arguments.length) return x;
12853     x = _;
12854     return chart;
12855   };
12856
12857   chart.yScale = function(_) {
12858     if (!arguments.length) return y;
12859     y = _;
12860     return chart;
12861   };
12862
12863   chart.xDomain = function(_) {
12864     if (!arguments.length) return xDomain;
12865     xDomain = _;
12866     return chart;
12867   };
12868
12869   chart.yDomain = function(_) {
12870     if (!arguments.length) return yDomain;
12871     yDomain = _;
12872     return chart;
12873   };
12874
12875   chart.xRange = function(_) {
12876     if (!arguments.length) return xRange;
12877     xRange = _;
12878     return chart;
12879   };
12880
12881   chart.yRange = function(_) {
12882     if (!arguments.length) return yRange;
12883     yRange = _;
12884     return chart;
12885   };
12886
12887   chart.animate = function(_) {
12888     if (!arguments.length) return animate;
12889     animate = _;
12890     return chart;
12891   };
12892
12893   chart.color = function(_) {
12894     if (!arguments.length) return color;
12895     color = nv.utils.getColor(_);
12896     return chart;
12897   };
12898
12899   //============================================================
12900
12901
12902   return chart;
12903 }
12904
12905 nv.models.sparklinePlus = function() {
12906   "use strict";
12907   //============================================================
12908   // Public Variables with Default Settings
12909   //------------------------------------------------------------
12910
12911   var sparkline = nv.models.sparkline();
12912
12913   var margin = {top: 15, right: 100, bottom: 10, left: 50}
12914     , width = null
12915     , height = null
12916     , x
12917     , y
12918     , index = []
12919     , paused = false
12920     , xTickFormat = d3.format(',r')
12921     , yTickFormat = d3.format(',.2f')
12922     , showValue = true
12923     , alignValue = true
12924     , rightAlignValue = false
12925     , noData = "No Data Available."
12926     ;
12927
12928   //============================================================
12929
12930
12931   function chart(selection) {
12932     selection.each(function(data) {
12933       var container = d3.select(this);
12934
12935       var availableWidth = (width  || parseInt(container.style('width')) || 960)
12936                              - margin.left - margin.right,
12937           availableHeight = (height || parseInt(container.style('height')) || 400)
12938                              - margin.top - margin.bottom;
12939
12940       
12941
12942       chart.update = function() { chart(selection) };
12943       chart.container = this;
12944
12945
12946       //------------------------------------------------------------
12947       // Display No Data message if there's nothing to show.
12948
12949       if (!data || !data.length) {
12950         var noDataText = container.selectAll('.nv-noData').data([noData]);
12951
12952         noDataText.enter().append('text')
12953           .attr('class', 'nvd3 nv-noData')
12954           .attr('dy', '-.7em')
12955           .style('text-anchor', 'middle');
12956
12957         noDataText
12958           .attr('x', margin.left + availableWidth / 2)
12959           .attr('y', margin.top + availableHeight / 2)
12960           .text(function(d) { return d });
12961
12962         return chart;
12963       } else {
12964         container.selectAll('.nv-noData').remove();
12965       }
12966
12967       var currentValue = sparkline.y()(data[data.length-1], data.length-1);
12968
12969       //------------------------------------------------------------
12970
12971
12972
12973       //------------------------------------------------------------
12974       // Setup Scales
12975
12976       x = sparkline.xScale();
12977       y = sparkline.yScale();
12978
12979       //------------------------------------------------------------
12980
12981
12982       //------------------------------------------------------------
12983       // Setup containers and skeleton of chart
12984
12985       var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
12986       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
12987       var gEnter = wrapEnter.append('g');
12988       var g = wrap.select('g');
12989
12990       gEnter.append('g').attr('class', 'nv-sparklineWrap');
12991       gEnter.append('g').attr('class', 'nv-valueWrap');
12992       gEnter.append('g').attr('class', 'nv-hoverArea');
12993
12994       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12995
12996       //------------------------------------------------------------
12997
12998
12999       //------------------------------------------------------------
13000       // Main Chart Component(s)
13001
13002       var sparklineWrap = g.select('.nv-sparklineWrap');
13003
13004       sparkline
13005         .width(availableWidth)
13006         .height(availableHeight);
13007
13008       sparklineWrap
13009           .call(sparkline);
13010
13011       //------------------------------------------------------------
13012
13013
13014       var valueWrap = g.select('.nv-valueWrap');
13015       
13016       var value = valueWrap.selectAll('.nv-currentValue')
13017           .data([currentValue]);
13018
13019       value.enter().append('text').attr('class', 'nv-currentValue')
13020           .attr('dx', rightAlignValue ? -8 : 8)
13021           .attr('dy', '.9em')
13022           .style('text-anchor', rightAlignValue ? 'end' : 'start');
13023
13024       value
13025           .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13026           .attr('y', alignValue ? function(d) { return y(d) } : 0)
13027           .style('fill', sparkline.color()(data[data.length-1], data.length-1))
13028           .text(yTickFormat(currentValue));
13029
13030
13031
13032       gEnter.select('.nv-hoverArea').append('rect')
13033           .on('mousemove', sparklineHover)
13034           .on('click', function() { paused = !paused })
13035           .on('mouseout', function() { index = []; updateValueLine(); });
13036           //.on('mouseout', function() { index = null; updateValueLine(); });
13037
13038       g.select('.nv-hoverArea rect')
13039           .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13040           .attr('width', availableWidth + margin.left + margin.right)
13041           .attr('height', availableHeight + margin.top);
13042
13043
13044
13045       function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13046         if (paused) return;
13047
13048         var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13049
13050         var hoverEnter = hoverValue.enter()
13051           .append('g').attr('class', 'nv-hoverValue')
13052             .style('stroke-opacity', 0)
13053             .style('fill-opacity', 0);
13054
13055         hoverValue.exit()
13056           .transition().duration(250)
13057             .style('stroke-opacity', 0)
13058             .style('fill-opacity', 0)
13059             .remove();
13060
13061         hoverValue
13062             .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13063           .transition().duration(250)
13064             .style('stroke-opacity', 1)
13065             .style('fill-opacity', 1);
13066
13067         if (!index.length) return;
13068
13069         hoverEnter.append('line')
13070             .attr('x1', 0)
13071             .attr('y1', -margin.top)
13072             .attr('x2', 0)
13073             .attr('y2', availableHeight);
13074
13075
13076         hoverEnter.append('text').attr('class', 'nv-xValue')
13077             .attr('x', -6)
13078             .attr('y', -margin.top)
13079             .attr('text-anchor', 'end')
13080             .attr('dy', '.9em')
13081
13082
13083         g.select('.nv-hoverValue .nv-xValue')
13084             .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13085
13086         hoverEnter.append('text').attr('class', 'nv-yValue')
13087             .attr('x', 6)
13088             .attr('y', -margin.top)
13089             .attr('text-anchor', 'start')
13090             .attr('dy', '.9em')
13091
13092         g.select('.nv-hoverValue .nv-yValue')
13093             .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13094
13095       }
13096
13097
13098       function sparklineHover() {
13099         if (paused) return;
13100
13101         var pos = d3.mouse(this)[0] - margin.left;
13102
13103         function getClosestIndex(data, x) {
13104           var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13105           var closestIndex = 0;
13106           for (var i = 0; i < data.length; i++){
13107             if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13108               distance = Math.abs(sparkline.x()(data[i], i) - x);
13109               closestIndex = i;
13110             }
13111           }
13112           return closestIndex;
13113         }
13114
13115         index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13116
13117         updateValueLine();
13118       }
13119
13120     });
13121
13122     return chart;
13123   }
13124
13125
13126   //============================================================
13127   // Expose Public Variables
13128   //------------------------------------------------------------
13129
13130   // expose chart's sub-components
13131   chart.sparkline = sparkline;
13132
13133   d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13134
13135   chart.options = nv.utils.optionsFunc.bind(chart);
13136   
13137   chart.margin = function(_) {
13138     if (!arguments.length) return margin;
13139     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13140     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13141     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13142     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13143     return chart;
13144   };
13145
13146   chart.width = function(_) {
13147     if (!arguments.length) return width;
13148     width = _;
13149     return chart;
13150   };
13151
13152   chart.height = function(_) {
13153     if (!arguments.length) return height;
13154     height = _;
13155     return chart;
13156   };
13157
13158   chart.xTickFormat = function(_) {
13159     if (!arguments.length) return xTickFormat;
13160     xTickFormat = _;
13161     return chart;
13162   };
13163
13164   chart.yTickFormat = function(_) {
13165     if (!arguments.length) return yTickFormat;
13166     yTickFormat = _;
13167     return chart;
13168   };
13169
13170   chart.showValue = function(_) {
13171     if (!arguments.length) return showValue;
13172     showValue = _;
13173     return chart;
13174   };
13175
13176   chart.alignValue = function(_) {
13177     if (!arguments.length) return alignValue;
13178     alignValue = _;
13179     return chart;
13180   };
13181
13182   chart.rightAlignValue = function(_) {
13183     if (!arguments.length) return rightAlignValue;
13184     rightAlignValue = _;
13185     return chart;
13186   };
13187
13188   chart.noData = function(_) {
13189     if (!arguments.length) return noData;
13190     noData = _;
13191     return chart;
13192   };
13193
13194   //============================================================
13195
13196
13197   return chart;
13198 }
13199
13200 nv.models.stackedArea = function() {
13201   "use strict";
13202   //============================================================
13203   // Public Variables with Default Settings
13204   //------------------------------------------------------------
13205
13206   var margin = {top: 0, right: 0, bottom: 0, left: 0}
13207     , width = 960
13208     , height = 500
13209     , color = nv.utils.defaultColor() // a function that computes the color
13210     , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13211     , getX = function(d) { return d.x } // accessor to get the x value from a data point
13212     , getY = function(d) { return d.y } // accessor to get the y value from a data point
13213     , style = 'stack'
13214     , offset = 'zero'
13215     , order = 'default'
13216     , interpolate = 'linear'  // controls the line interpolation
13217     , clipEdge = false // if true, masks lines within x and y scale
13218     , x //can be accessed via chart.xScale()
13219     , y //can be accessed via chart.yScale()
13220     , scatter = nv.models.scatter()
13221     , dispatch =  d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
13222     ;
13223
13224   scatter
13225     .size(2.2) // default size
13226     .sizeDomain([2.2,2.2]) // all the same size by default
13227     ;
13228
13229   /************************************
13230    * offset:
13231    *   'wiggle' (stream)
13232    *   'zero' (stacked)
13233    *   'expand' (normalize to 100%)
13234    *   'silhouette' (simple centered)
13235    *
13236    * order:
13237    *   'inside-out' (stream)
13238    *   'default' (input order)
13239    ************************************/
13240
13241   //============================================================
13242
13243
13244   function chart(selection) {
13245     selection.each(function(data) {
13246       var availableWidth = width - margin.left - margin.right,
13247           availableHeight = height - margin.top - margin.bottom,
13248           container = d3.select(this);
13249
13250       //------------------------------------------------------------
13251       // Setup Scales
13252
13253       x = scatter.xScale();
13254       y = scatter.yScale();
13255
13256       //------------------------------------------------------------
13257
13258
13259       // Injecting point index into each point because d3.layout.stack().out does not give index
13260       data = data.map(function(aseries, i) {
13261                aseries.seriesIndex = i;
13262                aseries.values = aseries.values.map(function(d, j) {
13263                  d.index = j;
13264                  d.seriesIndex = i;
13265                  return d;
13266                })
13267                return aseries;
13268              });
13269
13270       var dataFiltered = data.filter(function(series) {
13271             return !series.disabled;
13272       });
13273
13274       data = d3.layout.stack()
13275                .order(order)
13276                .offset(offset)
13277                .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
13278                .x(getX)
13279                .y(getY)
13280                .out(function(d, y0, y) {
13281                     var yHeight = (getY(d) === 0) ? 0 : y;
13282                     d.display = {
13283                       y: yHeight,
13284                      y0: y0
13285                     };
13286                 })
13287               (dataFiltered);
13288
13289
13290       //------------------------------------------------------------
13291       // Setup containers and skeleton of chart
13292
13293       var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
13294       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
13295       var defsEnter = wrapEnter.append('defs');
13296       var gEnter = wrapEnter.append('g');
13297       var g = wrap.select('g');
13298
13299       gEnter.append('g').attr('class', 'nv-areaWrap');
13300       gEnter.append('g').attr('class', 'nv-scatterWrap');
13301
13302       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13303
13304       //------------------------------------------------------------
13305
13306
13307       scatter
13308         .width(availableWidth)
13309         .height(availableHeight)
13310         .x(getX)
13311         .y(function(d) { return d.display.y + d.display.y0 })
13312         .forceY([0])
13313         .color(data.map(function(d,i) {
13314           return d.color || color(d, d.seriesIndex);
13315         }));
13316
13317
13318       var scatterWrap = g.select('.nv-scatterWrap')
13319           .datum(data);
13320
13321       scatterWrap.call(scatter);
13322
13323       defsEnter.append('clipPath')
13324           .attr('id', 'nv-edge-clip-' + id)
13325         .append('rect');
13326
13327       wrap.select('#nv-edge-clip-' + id + ' rect')
13328           .attr('width', availableWidth)
13329           .attr('height', availableHeight);
13330
13331       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
13332
13333       var area = d3.svg.area()
13334           .x(function(d,i)  { return x(getX(d,i)) })
13335           .y0(function(d) { 
13336               return y(d.display.y0) 
13337           })
13338           .y1(function(d) { 
13339               return y(d.display.y + d.display.y0) 
13340           })
13341           .interpolate(interpolate);
13342
13343       var zeroArea = d3.svg.area()
13344           .x(function(d,i)  { return x(getX(d,i)) })
13345           .y0(function(d) { return y(d.display.y0) })
13346           .y1(function(d) { return y(d.display.y0) });
13347
13348
13349       var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
13350           .data(function(d) { return d });
13351
13352       path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
13353           .attr('d', function(d,i){
13354             return zeroArea(d.values, d.seriesIndex);
13355           })
13356           .on('mouseover', function(d,i) {
13357             d3.select(this).classed('hover', true);
13358             dispatch.areaMouseover({
13359               point: d,
13360               series: d.key,
13361               pos: [d3.event.pageX, d3.event.pageY],
13362               seriesIndex: i
13363             });
13364           })
13365           .on('mouseout', function(d,i) {
13366             d3.select(this).classed('hover', false);
13367             dispatch.areaMouseout({
13368               point: d,
13369               series: d.key,
13370               pos: [d3.event.pageX, d3.event.pageY],
13371               seriesIndex: i
13372             });
13373           })
13374           .on('click', function(d,i) {
13375             d3.select(this).classed('hover', false);
13376             dispatch.areaClick({
13377               point: d,
13378               series: d.key,
13379               pos: [d3.event.pageX, d3.event.pageY],
13380               seriesIndex: i
13381             });
13382           })
13383       path.exit().transition()
13384           .attr('d', function(d,i) { return zeroArea(d.values,i) })
13385           .remove();
13386       path
13387           .style('fill', function(d,i){ 
13388             return d.color || color(d, d.seriesIndex) 
13389           })
13390           .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
13391       path.transition()
13392           .attr('d', function(d,i) { 
13393             return area(d.values,i) 
13394           });
13395
13396
13397
13398       //============================================================
13399       // Event Handling/Dispatching (in chart's scope)
13400       //------------------------------------------------------------
13401
13402       scatter.dispatch.on('elementMouseover.area', function(e) {
13403         g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
13404       });
13405       scatter.dispatch.on('elementMouseout.area', function(e) {
13406         g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
13407       });
13408
13409       //============================================================
13410
13411     });
13412
13413
13414     return chart;
13415   }
13416
13417
13418   //============================================================
13419   // Event Handling/Dispatching (out of chart's scope)
13420   //------------------------------------------------------------
13421
13422   scatter.dispatch.on('elementClick.area', function(e) {
13423     dispatch.areaClick(e);
13424   })
13425   scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13426         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13427         dispatch.tooltipShow(e);
13428   });
13429   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13430         dispatch.tooltipHide(e);
13431   });
13432
13433   //============================================================
13434
13435
13436   //============================================================
13437   // Global getters and setters
13438   //------------------------------------------------------------
13439
13440   chart.dispatch = dispatch;
13441   chart.scatter = scatter;
13442
13443   d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 
13444     'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
13445
13446   chart.options = nv.utils.optionsFunc.bind(chart);
13447   
13448   chart.x = function(_) {
13449     if (!arguments.length) return getX;
13450     getX = d3.functor(_);
13451     return chart;
13452   };
13453
13454   chart.y = function(_) {
13455     if (!arguments.length) return getY;
13456     getY = d3.functor(_);
13457     return chart;
13458   }
13459
13460   chart.margin = function(_) {
13461     if (!arguments.length) return margin;
13462     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13463     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13464     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13465     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13466     return chart;
13467   };
13468
13469   chart.width = function(_) {
13470     if (!arguments.length) return width;
13471     width = _;
13472     return chart;
13473   };
13474
13475   chart.height = function(_) {
13476     if (!arguments.length) return height;
13477     height = _;
13478     return chart;
13479   };
13480
13481   chart.clipEdge = function(_) {
13482     if (!arguments.length) return clipEdge;
13483     clipEdge = _;
13484     return chart;
13485   };
13486
13487   chart.color = function(_) {
13488     if (!arguments.length) return color;
13489     color = nv.utils.getColor(_);
13490     return chart;
13491   };
13492
13493   chart.offset = function(_) {
13494     if (!arguments.length) return offset;
13495     offset = _;
13496     return chart;
13497   };
13498
13499   chart.order = function(_) {
13500     if (!arguments.length) return order;
13501     order = _;
13502     return chart;
13503   };
13504
13505   //shortcut for offset + order
13506   chart.style = function(_) {
13507     if (!arguments.length) return style;
13508     style = _;
13509
13510     switch (style) {
13511       case 'stack':
13512         chart.offset('zero');
13513         chart.order('default');
13514         break;
13515       case 'stream':
13516         chart.offset('wiggle');
13517         chart.order('inside-out');
13518         break;
13519       case 'stream-center':
13520           chart.offset('silhouette');
13521           chart.order('inside-out');
13522           break;
13523       case 'expand':
13524         chart.offset('expand');
13525         chart.order('default');
13526         break;
13527     }
13528
13529     return chart;
13530   };
13531
13532   chart.interpolate = function(_) {
13533             if (!arguments.length) return interpolate;
13534             interpolate = _;
13535             return chart;
13536   };
13537   //============================================================
13538
13539
13540   return chart;
13541 }
13542
13543 nv.models.stackedAreaChart = function() {
13544   "use strict";
13545   //============================================================
13546   // Public Variables with Default Settings
13547   //------------------------------------------------------------
13548
13549   var stacked = nv.models.stackedArea()
13550     , xAxis = nv.models.axis()
13551     , yAxis = nv.models.axis()
13552     , legend = nv.models.legend()
13553     , controls = nv.models.legend()
13554     , interactiveLayer = nv.interactiveGuideline()
13555     ;
13556
13557   var margin = {top: 30, right: 25, bottom: 50, left: 60}
13558     , width = null
13559     , height = null
13560     , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
13561     , showControls = true
13562     , showLegend = true
13563     , showXAxis = true
13564     , showYAxis = true
13565     , rightAlignYAxis = false
13566     , useInteractiveGuideline = false
13567     , tooltips = true
13568     , tooltip = function(key, x, y, e, graph) {
13569         return '<h3>' + key + '</h3>' +
13570                '<p>' +  y + ' on ' + x + '</p>'
13571       }
13572     , x //can be accessed via chart.xScale()
13573     , y //can be accessed via chart.yScale()
13574     , yAxisTickFormat = d3.format(',.2f')
13575     , state = { style: stacked.style() }
13576     , defaultState = null
13577     , noData = 'No Data Available.'
13578     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
13579     , controlWidth = 250
13580     , cData = ['Stacked','Stream','Expanded']
13581     , transitionDuration = 250
13582     ;
13583
13584   xAxis
13585     .orient('bottom')
13586     .tickPadding(7)
13587     ;
13588   yAxis
13589     .orient((rightAlignYAxis) ? 'right' : 'left')
13590     ;
13591
13592   controls.updateState(false);
13593   //============================================================
13594
13595
13596   //============================================================
13597   // Private Variables
13598   //------------------------------------------------------------
13599
13600   var showTooltip = function(e, offsetElement) {
13601     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
13602         top = e.pos[1] + ( offsetElement.offsetTop || 0),
13603         x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
13604         y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
13605         content = tooltip(e.series.key, x, y, e, chart);
13606
13607     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
13608   };
13609
13610   //============================================================
13611
13612
13613   function chart(selection) {
13614     selection.each(function(data) {
13615       var container = d3.select(this),
13616           that = this;
13617
13618       var availableWidth = (width  || parseInt(container.style('width')) || 960)
13619                              - margin.left - margin.right,
13620           availableHeight = (height || parseInt(container.style('height')) || 400)
13621                              - margin.top - margin.bottom;
13622
13623       chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
13624       chart.container = this;
13625
13626       //set state.disabled
13627       state.disabled = data.map(function(d) { return !!d.disabled });
13628
13629       if (!defaultState) {
13630         var key;
13631         defaultState = {};
13632         for (key in state) {
13633           if (state[key] instanceof Array)
13634             defaultState[key] = state[key].slice(0);
13635           else
13636             defaultState[key] = state[key];
13637         }
13638       }
13639
13640       //------------------------------------------------------------
13641       // Display No Data message if there's nothing to show.
13642
13643       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
13644         var noDataText = container.selectAll('.nv-noData').data([noData]);
13645
13646         noDataText.enter().append('text')
13647           .attr('class', 'nvd3 nv-noData')
13648           .attr('dy', '-.7em')
13649           .style('text-anchor', 'middle');
13650
13651         noDataText
13652           .attr('x', margin.left + availableWidth / 2)
13653           .attr('y', margin.top + availableHeight / 2)
13654           .text(function(d) { return d });
13655
13656         return chart;
13657       } else {
13658         container.selectAll('.nv-noData').remove();
13659       }
13660
13661       //------------------------------------------------------------
13662
13663
13664       //------------------------------------------------------------
13665       // Setup Scales
13666
13667       x = stacked.xScale();
13668       y = stacked.yScale();
13669
13670       //------------------------------------------------------------
13671
13672
13673       //------------------------------------------------------------
13674       // Setup containers and skeleton of chart
13675
13676       var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
13677       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
13678       var g = wrap.select('g');
13679
13680       gEnter.append("rect").style("opacity",0);
13681       gEnter.append('g').attr('class', 'nv-x nv-axis');
13682       gEnter.append('g').attr('class', 'nv-y nv-axis');
13683       gEnter.append('g').attr('class', 'nv-stackedWrap');
13684       gEnter.append('g').attr('class', 'nv-legendWrap');
13685       gEnter.append('g').attr('class', 'nv-controlsWrap');
13686       gEnter.append('g').attr('class', 'nv-interactive');
13687
13688       g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
13689       //------------------------------------------------------------
13690       // Legend
13691
13692       if (showLegend) {
13693         var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
13694         legend
13695           .width(legendWidth);
13696
13697         g.select('.nv-legendWrap')
13698             .datum(data)
13699             .call(legend);
13700
13701         if ( margin.top != legend.height()) {
13702           margin.top = legend.height();
13703           availableHeight = (height || parseInt(container.style('height')) || 400)
13704                              - margin.top - margin.bottom;
13705         }
13706
13707         g.select('.nv-legendWrap')
13708             .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
13709       }
13710
13711       //------------------------------------------------------------
13712
13713
13714       //------------------------------------------------------------
13715       // Controls
13716
13717       if (showControls) {
13718         var controlsData = [
13719           { key: 'Stacked', disabled: stacked.offset() != 'zero' },
13720           { key: 'Stream', disabled: stacked.offset() != 'wiggle' },
13721           { key: 'Expanded', disabled: stacked.offset() != 'expand' }
13722         ];
13723
13724         controlWidth = (cData.length/3) * 260;
13725
13726         controlsData = controlsData.filter(function(d) {
13727           return cData.indexOf(d.key) > -1;
13728         })
13729
13730         controls
13731           .width( controlWidth )
13732           .color(['#444', '#444', '#444']);
13733
13734         g.select('.nv-controlsWrap')
13735             .datum(controlsData)
13736             .call(controls);
13737
13738
13739         if ( margin.top != Math.max(controls.height(), legend.height()) ) {
13740           margin.top = Math.max(controls.height(), legend.height());
13741           availableHeight = (height || parseInt(container.style('height')) || 400)
13742                              - margin.top - margin.bottom;
13743         }
13744
13745
13746         g.select('.nv-controlsWrap')
13747             .attr('transform', 'translate(0,' + (-margin.top) +')');
13748       }
13749
13750       //------------------------------------------------------------
13751
13752
13753       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13754
13755       if (rightAlignYAxis) {
13756           g.select(".nv-y.nv-axis")
13757               .attr("transform", "translate(" + availableWidth + ",0)");
13758       }
13759
13760       //------------------------------------------------------------
13761       // Main Chart Component(s)
13762
13763       //------------------------------------------------------------
13764       //Set up interactive layer
13765       if (useInteractiveGuideline) {
13766         interactiveLayer
13767            .width(availableWidth)
13768            .height(availableHeight)
13769            .margin({left: margin.left, top: margin.top})
13770            .svgContainer(container)
13771            .xScale(x);
13772         wrap.select(".nv-interactive").call(interactiveLayer);
13773       }
13774       
13775       stacked
13776         .width(availableWidth)
13777         .height(availableHeight)
13778
13779       var stackedWrap = g.select('.nv-stackedWrap')
13780           .datum(data);
13781
13782       stackedWrap.transition().call(stacked);
13783
13784       //------------------------------------------------------------
13785
13786
13787       //------------------------------------------------------------
13788       // Setup Axes
13789
13790       if (showXAxis) {
13791         xAxis
13792           .scale(x)
13793           .ticks( availableWidth / 100 )
13794           .tickSize( -availableHeight, 0);
13795
13796         g.select('.nv-x.nv-axis')
13797             .attr('transform', 'translate(0,' + availableHeight + ')');
13798  
13799         g.select('.nv-x.nv-axis')
13800           .transition().duration(0)
13801             .call(xAxis);
13802       }
13803
13804       if (showYAxis) {
13805         yAxis
13806           .scale(y)
13807           .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
13808           .tickSize(-availableWidth, 0)
13809           .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
13810
13811         g.select('.nv-y.nv-axis')
13812           .transition().duration(0)
13813             .call(yAxis);
13814       }
13815
13816       //------------------------------------------------------------
13817
13818
13819       //============================================================
13820       // Event Handling/Dispatching (in chart's scope)
13821       //------------------------------------------------------------
13822
13823       stacked.dispatch.on('areaClick.toggle', function(e) {
13824         if (data.filter(function(d) { return !d.disabled }).length === 1)
13825           data = data.map(function(d) {
13826             d.disabled = false;
13827             return d
13828           });
13829         else
13830           data = data.map(function(d,i) {
13831             d.disabled = (i != e.seriesIndex);
13832             return d
13833           });
13834
13835         state.disabled = data.map(function(d) { return !!d.disabled });
13836         dispatch.stateChange(state);
13837
13838         chart.update();
13839       });
13840
13841       legend.dispatch.on('stateChange', function(newState) {
13842         state.disabled = newState.disabled;
13843         dispatch.stateChange(state);
13844         chart.update();
13845       });
13846
13847       controls.dispatch.on('legendClick', function(d,i) {
13848         if (!d.disabled) return;
13849
13850         controlsData = controlsData.map(function(s) {
13851           s.disabled = true;
13852           return s;
13853         });
13854         d.disabled = false;
13855
13856         switch (d.key) {
13857           case 'Stacked':
13858             stacked.style('stack');
13859             break;
13860           case 'Stream':
13861             stacked.style('stream');
13862             break;
13863           case 'Expanded':
13864             stacked.style('expand');
13865             break;
13866         }
13867
13868         state.style = stacked.style();
13869         dispatch.stateChange(state);
13870
13871         chart.update();
13872       });
13873
13874
13875       interactiveLayer.dispatch.on('elementMousemove', function(e) {
13876           stacked.clearHighlights();
13877           var singlePoint, pointIndex, pointXLocation, allData = [];
13878           data
13879           .filter(function(series, i) { 
13880             series.seriesIndex = i;
13881             return !series.disabled; 
13882           })
13883           .forEach(function(series,i) {
13884               pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
13885               stacked.highlightPoint(i, pointIndex, true);
13886               var point = series.values[pointIndex];
13887               if (typeof point === 'undefined') return;
13888               if (typeof singlePoint === 'undefined') singlePoint = point;
13889               if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
13890               allData.push({
13891                   key: series.key,
13892                   value: chart.y()(point, pointIndex),
13893                   color: color(series,series.seriesIndex)
13894               });
13895           });
13896
13897           var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
13898           interactiveLayer.tooltip
13899                   .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
13900                   .chartContainer(that.parentNode)
13901                   .enabled(tooltips)
13902                   .valueFormatter(function(d,i) {
13903                      return yAxis.tickFormat()(d);
13904                   })
13905                   .data(
13906                       {
13907                         value: xValue,
13908                         series: allData
13909                       }
13910                   )();
13911
13912           interactiveLayer.renderGuideLine(pointXLocation);
13913
13914       });
13915
13916       interactiveLayer.dispatch.on("elementMouseout",function(e) {
13917           dispatch.tooltipHide();
13918           stacked.clearHighlights();
13919       });
13920
13921
13922       dispatch.on('tooltipShow', function(e) {
13923         if (tooltips) showTooltip(e, that.parentNode);
13924       });
13925
13926       // Update chart from a state object passed to event handler
13927       dispatch.on('changeState', function(e) {
13928
13929         if (typeof e.disabled !== 'undefined') {
13930           data.forEach(function(series,i) {
13931             series.disabled = e.disabled[i];
13932           });
13933
13934           state.disabled = e.disabled;
13935         }
13936
13937         if (typeof e.style !== 'undefined') {
13938           stacked.style(e.style);
13939         }
13940
13941         chart.update();
13942       });
13943
13944     });
13945
13946
13947     return chart;
13948   }
13949
13950
13951   //============================================================
13952   // Event Handling/Dispatching (out of chart's scope)
13953   //------------------------------------------------------------
13954
13955   stacked.dispatch.on('tooltipShow', function(e) {
13956     //disable tooltips when value ~= 0
13957     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
13958     /*
13959     if (!Math.round(stacked.y()(e.point) * 100)) {  // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
13960       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
13961       return false;
13962     }
13963    */
13964
13965     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13966     dispatch.tooltipShow(e);
13967   });
13968
13969   stacked.dispatch.on('tooltipHide', function(e) {
13970     dispatch.tooltipHide(e);
13971   });
13972
13973   dispatch.on('tooltipHide', function() {
13974     if (tooltips) nv.tooltip.cleanup();
13975   });
13976
13977   //============================================================
13978
13979
13980   //============================================================
13981   // Expose Public Variables
13982   //------------------------------------------------------------
13983
13984   // expose chart's sub-components
13985   chart.dispatch = dispatch;
13986   chart.stacked = stacked;
13987   chart.legend = legend;
13988   chart.controls = controls;
13989   chart.xAxis = xAxis;
13990   chart.yAxis = yAxis;
13991   chart.interactiveLayer = interactiveLayer;
13992
13993   d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
13994
13995   chart.options = nv.utils.optionsFunc.bind(chart);
13996   
13997   chart.margin = function(_) {
13998     if (!arguments.length) return margin;
13999     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
14000     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
14001     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14002     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
14003     return chart;
14004   };
14005
14006   chart.width = function(_) {
14007     if (!arguments.length) return width;
14008     width = _;
14009     return chart;
14010   };
14011
14012   chart.height = function(_) {
14013     if (!arguments.length) return height;
14014     height = _;
14015     return chart;
14016   };
14017
14018   chart.color = function(_) {
14019     if (!arguments.length) return color;
14020     color = nv.utils.getColor(_);
14021     legend.color(color);
14022     stacked.color(color);
14023     return chart;
14024   };
14025
14026   chart.showControls = function(_) {
14027     if (!arguments.length) return showControls;
14028     showControls = _;
14029     return chart;
14030   };
14031
14032   chart.showLegend = function(_) {
14033     if (!arguments.length) return showLegend;
14034     showLegend = _;
14035     return chart;
14036   };
14037
14038   chart.showXAxis = function(_) {
14039     if (!arguments.length) return showXAxis;
14040     showXAxis = _;
14041     return chart;
14042   };
14043
14044   chart.showYAxis = function(_) {
14045     if (!arguments.length) return showYAxis;
14046     showYAxis = _;
14047     return chart;
14048   };
14049
14050   chart.rightAlignYAxis = function(_) {
14051     if(!arguments.length) return rightAlignYAxis;
14052     rightAlignYAxis = _;
14053     yAxis.orient( (_) ? 'right' : 'left');
14054     return chart;
14055   };
14056
14057   chart.useInteractiveGuideline = function(_) {
14058     if(!arguments.length) return useInteractiveGuideline;
14059     useInteractiveGuideline = _;
14060     if (_ === true) {
14061        chart.interactive(false);
14062        chart.useVoronoi(false);
14063     }
14064     return chart;
14065   };
14066
14067   chart.tooltip = function(_) {
14068     if (!arguments.length) return tooltip;
14069     tooltip = _;
14070     return chart;
14071   };
14072
14073   chart.tooltips = function(_) {
14074     if (!arguments.length) return tooltips;
14075     tooltips = _;
14076     return chart;
14077   };
14078
14079   chart.tooltipContent = function(_) {
14080     if (!arguments.length) return tooltip;
14081     tooltip = _;
14082     return chart;
14083   };
14084
14085   chart.state = function(_) {
14086     if (!arguments.length) return state;
14087     state = _;
14088     return chart;
14089   };
14090
14091   chart.defaultState = function(_) {
14092     if (!arguments.length) return defaultState;
14093     defaultState = _;
14094     return chart;
14095   };
14096
14097   chart.noData = function(_) {
14098     if (!arguments.length) return noData;
14099     noData = _;
14100     return chart;
14101   };
14102
14103   chart.transitionDuration = function(_) {
14104     if (!arguments.length) return transitionDuration;
14105     transitionDuration = _;
14106     return chart;
14107   };
14108
14109   chart.controlsData = function(_) {
14110     if (!arguments.length) return cData;
14111     cData = _;
14112     return chart;
14113   };
14114
14115   yAxis.setTickFormat = yAxis.tickFormat;
14116
14117   yAxis.tickFormat = function(_) {
14118     if (!arguments.length) return yAxisTickFormat;
14119     yAxisTickFormat = _;
14120     return yAxis;
14121   };
14122
14123
14124   //============================================================
14125
14126   return chart;
14127 }
14128 })();