Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / app / fusion / external / d3 / js / nv.d3.js
1 (function(){
2
3 var nv = window.nv || {};
4
5 nv.version = '0.0.1a';
6 nv.dev = true //set false when in production
7
8 window.nv = nv;
9
10 nv.tooltip = {}; // For the tooltip system
11 nv.utils = {}; // Utility subsystem
12 nv.models = {}; //stores all the possible models/components
13 nv.charts = {}; //stores all the ready to use charts
14 nv.graphs = []; //stores all the graphs currently on the page
15 nv.logs = {}; //stores some statistics and potential error messages
16
17 nv.dispatch = d3.dispatch('render_start', 'render_end');
18
19 // *************************************************************************
20 //  Development render timers - disabled if dev = false
21
22 if (nv.dev) {
23   nv.dispatch.on('render_start', function(e) {
24     nv.logs.startTime = +new Date();
25   });
26
27   nv.dispatch.on('render_end', function(e) {
28     nv.logs.endTime = +new Date();
29     nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
30     nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
31   });
32 }
33
34 // ********************************************
35 //  Public Core NV functions
36
37 // Logs all arguments, and returns the last so you can test things in place
38 nv.log = function() {
39   if (nv.dev && console.log && console.log.apply)
40     console.log.apply(console, arguments)
41   else if (nv.dev && console.log && Function.prototype.bind) {
42     var log = Function.prototype.bind.call(console.log, console);
43     log.apply(console, arguments);
44   }
45   return arguments[arguments.length - 1];
46 };
47
48
49 nv.render = function render(step) {
50   step = step || 1; // number of graphs to generate in each timeout loop
51
52   nv.render.active = true;
53   nv.dispatch.render_start();
54
55   setTimeout(function() {
56     var chart, graph;
57
58     for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
59       chart = graph.generate();
60       if (typeof graph.callback == typeof(Function)) graph.callback(chart);
61       nv.graphs.push(chart);
62     }
63
64     nv.render.queue.splice(0, i);
65
66     if (nv.render.queue.length) setTimeout(arguments.callee, 0);
67     else { nv.render.active = false; nv.dispatch.render_end(); }
68   }, 0);
69 };
70
71 nv.render.active = false;
72 nv.render.queue = [];
73
74 nv.addGraph = function(obj) {
75   if (typeof arguments[0] === typeof(Function))
76     obj = {generate: arguments[0], callback: arguments[1]};
77
78   nv.render.queue.push(obj);
79
80   if (!nv.render.active) nv.render();
81 };
82
83 nv.identity = function(d) { return d; };
84
85 nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
86
87 function daysInMonth(month,year) {
88   return (new Date(year, month+1, 0)).getDate();
89 }
90
91 function d3_time_range(floor, step, number) {
92   return function(t0, t1, dt) {
93     var time = floor(t0), times = [];
94     if (time < t0) step(time);
95     if (dt > 1) {
96       while (time < t1) {
97         var date = new Date(+time);
98         if ((number(date) % dt === 0)) times.push(date);
99         step(time);
100       }
101     } else {
102       while (time < t1) { times.push(new Date(+time)); step(time); }
103     }
104     return times;
105   };
106 }
107
108 d3.time.monthEnd = function(date) {
109   return new Date(date.getFullYear(), date.getMonth(), 0);
110 };
111
112 d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
113     date.setUTCDate(date.getUTCDate() + 1);
114     date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
115   }, function(date) {
116     return date.getMonth();
117   }
118 );
119
120
121 /*****
122  * A no-frills tooltip implementation.
123  *****/
124
125
126 (function() {
127
128   var nvtooltip = window.nv.tooltip = {};
129
130   nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
131
132     var container = document.createElement('div');
133         container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
134
135     gravity = gravity || 's';
136     dist = dist || 20;
137
138     var body = parentContainer;
139     if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
140         //If the parent element is an SVG element, place tooltip in the <body> element.
141         body = document.getElementsByTagName('body')[0];
142     }
143
144     container.innerHTML = content;
145     container.style.left = 0;
146     container.style.top = 0;
147     container.style.opacity = 0;
148
149     body.appendChild(container);
150
151     var height = parseInt(container.offsetHeight),
152         width = parseInt(container.offsetWidth),
153         windowWidth = nv.utils.windowSize().width,
154         windowHeight = nv.utils.windowSize().height,
155         scrollTop = window.scrollY,
156         scrollLeft = window.scrollX,
157         left, top;
158
159     windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
160     windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
161
162     var tooltipTop = function ( Elem ) {
163         var offsetTop = top;
164         do {
165             if( !isNaN( Elem.offsetTop ) ) {
166                 offsetTop += (Elem.offsetTop);
167             }
168         } while( Elem = Elem.offsetParent );
169         return offsetTop;
170     }
171
172     var tooltipLeft = function ( Elem ) {
173         var offsetLeft = left;
174         do {
175             if( !isNaN( Elem.offsetLeft ) ) {
176                 offsetLeft += (Elem.offsetLeft);
177             }
178         } while( Elem = Elem.offsetParent );
179         return offsetLeft;
180     }
181
182     switch (gravity) {
183       case 'e':
184         left = pos[0] - width - dist;
185         top = pos[1] - (height / 2);
186         var tLeft = tooltipLeft(container);
187         var tTop = tooltipTop(container);
188         if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
189         if (tTop < scrollTop) top = scrollTop - tTop + top;
190         if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
191         break;
192       case 'w':
193         left = pos[0] + dist;
194         top = pos[1] - (height / 2);
195         if (tLeft + width > windowWidth) left = pos[0] - width - dist;
196         if (tTop < scrollTop) top = scrollTop + 5;
197         if (tTop + height > scrollTop + windowHeight) top = scrollTop - height - 5;
198         break;
199       case 'n':
200         left = pos[0] - (width / 2) - 5;
201         top = pos[1] + dist;
202         var tLeft = tooltipLeft(container);
203         var tTop = tooltipTop(container);
204         if (tLeft < scrollLeft) left = scrollLeft + 5;
205         if (tLeft + width > windowWidth) left = left - width/2 + 5;
206         if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
207         break;
208       case 's':
209         left = pos[0] - (width / 2);
210         top = pos[1] - height - dist;
211         var tLeft = tooltipLeft(container);
212         var tTop = tooltipTop(container);
213         if (tLeft < scrollLeft) left = scrollLeft + 5;
214         if (tLeft + width > windowWidth) left = left - width/2 + 5;
215         if (scrollTop > tTop) top = scrollTop;
216         break;
217     }
218
219
220     container.style.left = left+'px';
221     container.style.top = top+'px';
222     container.style.opacity = 1;
223     container.style.position = 'absolute'; //fix scroll bar issue
224     container.style.pointerEvents = 'none'; //fix scroll bar issue
225
226     return container;
227   };
228
229   nvtooltip.cleanup = function() {
230
231       // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
232       var tooltips = document.getElementsByClassName('nvtooltip');
233       var purging = [];
234       while(tooltips.length) {
235         purging.push(tooltips[0]);
236         tooltips[0].style.transitionDelay = '0 !important';
237         tooltips[0].style.opacity = 0;
238         tooltips[0].className = 'nvtooltip-pending-removal';
239       }
240
241
242       setTimeout(function() {
243
244           while (purging.length) {
245              var removeMe = purging.pop();
246               removeMe.parentNode.removeChild(removeMe);
247           }
248     }, 500);
249   };
250
251
252 })();
253
254 nv.utils.windowSize = function() {
255     // Sane defaults
256     var size = {width: 640, height: 480};
257
258     // Earlier IE uses Doc.body
259     if (document.body && document.body.offsetWidth) {
260         size.width = document.body.offsetWidth;
261         size.height = document.body.offsetHeight;
262     }
263
264     // IE can use depending on mode it is in
265     if (document.compatMode=='CSS1Compat' &&
266         document.documentElement &&
267         document.documentElement.offsetWidth ) {
268         size.width = document.documentElement.offsetWidth;
269         size.height = document.documentElement.offsetHeight;
270     }
271
272     // Most recent browsers use
273     if (window.innerWidth && window.innerHeight) {
274         size.width = window.innerWidth;
275         size.height = window.innerHeight;
276     }
277     return (size);
278 };
279
280
281
282 // Easy way to bind multiple functions to window.onresize
283 // TODO: give a way to remove a function after its bound, other than removing all of them
284 nv.utils.windowResize = function(fun){
285   var oldresize = window.onresize;
286
287   window.onresize = function(e) {
288     if (typeof oldresize == 'function') oldresize(e);
289     fun(e);
290   }
291 }
292
293 // Backwards compatible way to implement more d3-like coloring of graphs.
294 // If passed an array, wrap it in a function which implements the old default
295 // behavior
296 nv.utils.getColor = function(color) {
297     if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
298
299     if( Object.prototype.toString.call( color ) === '[object Array]' )
300         return function(d, i) { return d.color || color[i % color.length]; };
301     else
302         return color;
303         //can't really help it if someone passes rubbish as color
304 }
305
306 // Default color chooser uses the index of an object as before.
307 nv.utils.defaultColor = function() {
308     var colors = d3.scale.category20().range();
309     return function(d, i) { return d.color || colors[i % colors.length] };
310 }
311
312
313 // Returns a color function that takes the result of 'getKey' for each series and
314 // looks for a corresponding color from the dictionary,
315 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
316   getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
317   defaultColors = defaultColors || d3.scale.category20().range(); //default color function
318
319   var defIndex = defaultColors.length; //current default color (going in reverse)
320
321   return function(series, index) {
322     var key = getKey(series);
323
324     if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
325
326     if (typeof dictionary[key] !== "undefined")
327       return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
328     else
329       return defaultColors[--defIndex]; // no match in dictionary, use default color
330   }
331 }
332
333
334
335 // From the PJAX example on d3js.org, while this is not really directly needed
336 // it's a very cool method for doing pjax, I may expand upon it a little bit,
337 // open to suggestions on anything that may be useful
338 nv.utils.pjax = function(links, content) {
339   d3.selectAll(links).on("click", function() {
340     history.pushState(this.href, this.textContent, this.href);
341     load(this.href);
342     d3.event.preventDefault();
343   });
344
345   function load(href) {
346     d3.html(href, function(fragment) {
347       var target = d3.select(content).node();
348       target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
349       nv.utils.pjax(links, content);
350     });
351   }
352
353   d3.select(window).on("popstate", function() {
354     if (d3.event.state) load(d3.event.state);
355   });
356 }
357
358 /* For situations where we want to approximate the width in pixels for an SVG:text element.
359 Most common instance is when the element is in a display:none; container. 
360 Forumla is : text.length * font-size * constant_factor
361 */
362 nv.utils.calcApproxTextWidth = function (svgTextElem) {
363     if (svgTextElem instanceof d3.selection) {
364         var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
365         var textLength = svgTextElem.text().length;
366
367         return textLength * fontSize * 0.5; 
368     }
369     return 0;
370 };
371 nv.models.axis = function() {
372
373   //============================================================
374   // Public Variables with Default Settings
375   //------------------------------------------------------------
376
377   var axis = d3.svg.axis()
378     ;
379
380   var margin = {top: 0, right: 0, bottom: 0, left: 0}
381     , width = 75 //only used for tickLabel currently
382     , height = 60 //only used for tickLabel currently
383     , scale = d3.scale.linear()
384     , axisLabelText = null
385     , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
386     , highlightZero = true
387     , rotateLabels = 0
388     , rotateYLabel = true
389     , staggerLabels = false
390     , isOrdinal = false
391     , ticks = null
392     , logScale=false    
393     ;
394
395   axis
396     .scale(scale)
397     .orient('bottom')
398     .tickFormat(function(d) { return d })
399     ;
400
401   //============================================================
402
403
404   //============================================================
405   // Private Variables
406   //------------------------------------------------------------
407
408   var scale0;
409
410   //============================================================
411
412
413   function chart(selection) {
414     selection.each(function(data) {
415       var container = d3.select(this);
416
417
418       //------------------------------------------------------------
419       // Setup containers and skeleton of chart
420
421       var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
422       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
423       var gEnter = wrapEnter.append('g');
424       var g = wrap.select('g')
425
426       //------------------------------------------------------------
427
428
429       if (ticks !== null)
430         axis.ticks(ticks);
431       else if (axis.orient() == 'top' || axis.orient() == 'bottom')
432         axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
433
434
435       //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
436
437
438       d3.transition(g)
439           .call(axis);
440
441       scale0 = scale0 || axis.scale();
442
443       var fmt = axis.tickFormat();
444       if (fmt == null) {
445         fmt = scale0.tickFormat();
446       }
447
448       var axisLabel = g.selectAll('text.nv-axislabel')
449           .data([axisLabelText || null]);
450       axisLabel.exit().remove();
451       switch (axis.orient()) {
452         case 'top':
453           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
454           var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
455           axisLabel
456               .attr('text-anchor', 'middle')
457               .attr('y', 0)
458               .attr('x', w/2);
459           if (showMaxMin) {
460             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
461                            .data(scale.domain());
462             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
463             axisMaxMin.exit().remove();
464             axisMaxMin
465                 .attr('transform', function(d,i) {
466                   return 'translate(' + scale(d) + ',0)'
467                 })
468               .select('text')
469                 .attr('dy', '0em')
470                 .attr('y', -axis.tickPadding())
471                 .attr('text-anchor', 'middle')
472                 .text(function(d,i) {
473                   var v = fmt(d);
474                   return ('' + v).match('NaN') ? '' : v;
475                 });
476             d3.transition(axisMaxMin)
477                 .attr('transform', function(d,i) {
478                   return 'translate(' + scale.range()[i] + ',0)'
479                 });
480           }
481           break;
482         case 'bottom':
483           var xLabelMargin = 36;
484           var maxTextWidth = 30;
485           var xTicks = g.selectAll('g').select("text");
486           if (rotateLabels%360) {
487             //Calculate the longest xTick width
488             xTicks.each(function(d,i){
489               var width = this.getBBox().width;
490               if(width > maxTextWidth) maxTextWidth = width;
491             });
492             //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
493             var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
494             var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
495             //Rotate all xTicks
496             xTicks
497               .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
498               .attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
499           }
500           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
501           var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
502           axisLabel
503               .attr('text-anchor', 'middle')
504               .attr('y', xLabelMargin)
505               .attr('x', w/2);
506           if (showMaxMin) {
507           //if (showMaxMin && !isOrdinal) {
508             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
509                            //.data(scale.domain())
510                            .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
511             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
512             axisMaxMin.exit().remove();
513             axisMaxMin
514                 .attr('transform', function(d,i) {
515                   return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
516                 })
517               .select('text')
518                 .attr('dy', '.71em')
519                 .attr('y', axis.tickPadding())
520                 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
521                 .attr('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
522                 .text(function(d,i) {
523                   var v = fmt(d);
524                   return ('' + v).match('NaN') ? '' : v;
525                 });
526             d3.transition(axisMaxMin)
527                 .attr('transform', function(d,i) {
528                   //return 'translate(' + scale.range()[i] + ',0)'
529                   //return 'translate(' + scale(d) + ',0)'
530                   return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
531                 });
532           }
533           if (staggerLabels)
534             xTicks
535                 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
536
537           break;
538         case 'right':
539           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
540           axisLabel
541               .attr('text-anchor', rotateYLabel ? 'middle' : 'begin')
542               .attr('transform', rotateYLabel ? 'rotate(90)' : '')
543               .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
544               .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
545           if (showMaxMin) {
546             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
547                            .data(scale.domain());
548             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
549                 .style('opacity', 0);
550             axisMaxMin.exit().remove();
551             axisMaxMin
552                 .attr('transform', function(d,i) {
553                   return 'translate(0,' + scale(d) + ')'
554                 })
555               .select('text')
556                 .attr('dy', '.32em')
557                 .attr('y', 0)
558                 .attr('x', axis.tickPadding())
559                 .attr('text-anchor', 'start')
560                 .text(function(d,i) {
561                   var v = fmt(d);
562                   return ('' + v).match('NaN') ? '' : v;
563                 });
564             d3.transition(axisMaxMin)
565                 .attr('transform', function(d,i) {
566                   return 'translate(0,' + scale.range()[i] + ')'
567                 })
568               .select('text')
569                 .style('opacity', 1);
570           }
571           break;
572         case 'left':
573           /*
574           //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
575           var yTicks = g.selectAll('g').select("text");
576           yTicks.each(function(d,i){
577             var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
578             if(labelPadding > width) width = labelPadding;
579           });
580           */
581           axisLabel.enter().append('text').attr('class', 'nv-axislabel');
582           axisLabel
583               .attr('text-anchor', rotateYLabel ? 'middle' : 'end')
584               .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
585               .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
586               .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
587           if (showMaxMin) {
588             var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
589                            .data(scale.domain());
590             axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
591                 .style('opacity', 0);
592             axisMaxMin.exit().remove();
593             axisMaxMin
594                 .attr('transform', function(d,i) {
595                   return 'translate(0,' + scale0(d) + ')'
596                 })
597               .select('text')
598                 .attr('dy', '.32em')
599                 .attr('y', 0)
600                 .attr('x', -axis.tickPadding())
601                 .attr('text-anchor', 'end')
602                 .text(function(d,i) {
603                   var v = fmt(d);
604                   return ('' + v).match('NaN') ? '' : v;
605                 });
606             d3.transition(axisMaxMin)
607                 .attr('transform', function(d,i) {
608                   return 'translate(0,' + scale.range()[i] + ')'
609                 })
610               .select('text')
611                 .style('opacity', 1);
612           }
613           break;
614       }
615       axisLabel
616           .text(function(d) { return d });
617
618
619       if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
620         //check if max and min overlap other values, if so, hide the values that overlap
621         g.selectAll('g') // the g's wrapping each tick
622             .each(function(d,i) {
623               d3.select(this).select('text').attr('opacity', 1);
624               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!
625                 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
626                   d3.select(this).attr('opacity', 0);
627                 
628                 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
629               }
630             });
631
632         //if Max and Min = 0 only show min, Issue #281
633         if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
634           wrap.selectAll('g.nv-axisMaxMin')
635             .style('opacity', function(d,i) { return !i ? 1 : 0 });
636
637       }
638
639       if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
640         var maxMinRange = [];
641         wrap.selectAll('g.nv-axisMaxMin')
642             .each(function(d,i) {
643               try {
644                   if (i) // i== 1, max position
645                       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)
646                   else // i==0, min position
647                       maxMinRange.push(scale(d) + this.getBBox().width + 4)
648               }catch (err) {
649                   if (i) // i== 1, max position
650                       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)
651                   else // i==0, min position
652                       maxMinRange.push(scale(d) + 4)
653               }
654             });
655         g.selectAll('g') // the g's wrapping each tick
656             .each(function(d,i) {
657               if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
658                 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
659                   d3.select(this).remove();
660                 else
661                   d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
662               }
663             });
664       }
665
666
667       //highlight zero line ... Maybe should not be an option and should just be in CSS?
668       if (highlightZero)
669         g.selectAll('.tick')
670           .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
671             .classed('zero', true);
672
673       //store old scales for use in transitions on update
674       scale0 = scale.copy();
675
676     });
677
678     return chart;
679   }
680
681
682   //============================================================
683   // Expose Public Variables
684   //------------------------------------------------------------
685
686   // expose chart's sub-components
687   chart.axis = axis;
688
689   d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
690   d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
691
692   chart.margin = function(_) {
693     if(!arguments.length) return margin;
694     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
695     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
696     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
697     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
698     return chart;
699   }
700
701   chart.width = function(_) {
702     if (!arguments.length) return width;
703     width = _;
704     return chart;
705   };
706
707   chart.ticks = function(_) {
708     if (!arguments.length) return ticks;
709     ticks = _;
710     return chart;
711   };
712
713   chart.height = function(_) {
714     if (!arguments.length) return height;
715     height = _;
716     return chart;
717   };
718
719   chart.axisLabel = function(_) {
720     if (!arguments.length) return axisLabelText;
721     axisLabelText = _;
722     return chart;
723   }
724
725   chart.showMaxMin = function(_) {
726     if (!arguments.length) return showMaxMin;
727     showMaxMin = _;
728     return chart;
729   }
730
731   chart.highlightZero = function(_) {
732     if (!arguments.length) return highlightZero;
733     highlightZero = _;
734     return chart;
735   }
736
737   chart.scale = function(_) {
738     if (!arguments.length) return scale;
739     scale = _;
740     axis.scale(scale);
741     isOrdinal = typeof scale.rangeBands === 'function';
742     d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
743     return chart;
744   }
745
746   chart.rotateYLabel = function(_) {
747     if(!arguments.length) return rotateYLabel;
748     rotateYLabel = _;
749     return chart;
750   }
751
752   chart.rotateLabels = function(_) {
753     if(!arguments.length) return rotateLabels;
754     rotateLabels = _;
755     return chart;
756   }
757
758   chart.staggerLabels = function(_) {
759     if (!arguments.length) return staggerLabels;
760     staggerLabels = _;
761     return chart;
762   };
763
764
765   //============================================================
766
767
768   return chart;
769 }
770
771 // Chart design based on the recommendations of Stephen Few. Implementation
772 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
773 // http://projects.instantcognition.com/protovis/bulletchart/
774
775 nv.models.bullet = function() {
776
777   //============================================================
778   // Public Variables with Default Settings
779   //------------------------------------------------------------
780
781   var margin = {top: 0, right: 0, bottom: 0, left: 0}
782     , orient = 'left' // TODO top & bottom
783     , reverse = false
784     , ranges = function(d) { return d.ranges }
785     , markers = function(d) { return d.markers }
786     , measures = function(d) { return d.measures }
787     , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
788     , width = 380
789     , height = 30
790     , tickFormat = null
791     , color = nv.utils.getColor(['#1f77b4'])
792     , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
793     ;
794
795   //============================================================
796
797
798   function chart(selection) {
799     selection.each(function(d, i) {
800       var availableWidth = width - margin.left - margin.right,
801           availableHeight = height - margin.top - margin.bottom,
802           container = d3.select(this);
803
804       var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
805           markerz = markers.call(this, d, i).slice().sort(d3.descending),
806           measurez = measures.call(this, d, i).slice().sort(d3.descending);
807
808
809       //------------------------------------------------------------
810       // Setup Scales
811
812       // Compute the new x-scale.
813       var x1 = d3.scale.linear()
814           .domain( d3.extent(d3.merge([forceX, rangez])) )
815           .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
816
817       // Retrieve the old x-scale, if this is an update.
818       var x0 = this.__chart__ || d3.scale.linear()
819           .domain([0, Infinity])
820           .range(x1.range());
821
822       // Stash the new scale.
823       this.__chart__ = x1;
824
825
826       var rangeMin = d3.min(rangez), //rangez[2]
827           rangeMax = d3.max(rangez), //rangez[0]
828           rangeAvg = rangez[1];
829
830       //------------------------------------------------------------
831
832
833       //------------------------------------------------------------
834       // Setup containers and skeleton of chart
835
836       var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
837       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
838       var gEnter = wrapEnter.append('g');
839       var g = wrap.select('g');
840
841       gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
842       gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
843       gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
844       gEnter.append('rect').attr('class', 'nv-measure');
845       gEnter.append('path').attr('class', 'nv-markerTriangle');
846
847       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
848
849       //------------------------------------------------------------
850
851
852
853       var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
854           w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
855       var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
856           xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
857
858
859       g.select('rect.nv-rangeMax')
860           .attr('height', availableHeight)
861           .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
862           .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
863           .datum(rangeMax > 0 ? rangeMax : rangeMin)
864           /*
865           .attr('x', rangeMin < 0 ?
866                          rangeMax > 0 ?
867                              x1(rangeMin)
868                            : x1(rangeMax)
869                        : x1(0))
870                       */
871
872       g.select('rect.nv-rangeAvg')
873           .attr('height', availableHeight)
874           .attr('width', w1(rangeAvg))
875           .attr('x', xp1(rangeAvg))
876           .datum(rangeAvg)
877           /*
878           .attr('width', rangeMax <= 0 ?
879                              x1(rangeMax) - x1(rangeAvg)
880                            : x1(rangeAvg) - x1(rangeMin))
881           .attr('x', rangeMax <= 0 ?
882                          x1(rangeAvg)
883                        : x1(rangeMin))
884                       */
885
886       g.select('rect.nv-rangeMin')
887           .attr('height', availableHeight)
888           .attr('width', w1(rangeMax))
889           .attr('x', xp1(rangeMax))
890           .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
891           .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
892           .datum(rangeMax > 0 ? rangeMin : rangeMax)
893           /*
894           .attr('width', rangeMax <= 0 ?
895                              x1(rangeAvg) - x1(rangeMin)
896                            : x1(rangeMax) - x1(rangeAvg))
897           .attr('x', rangeMax <= 0 ?
898                          x1(rangeMin)
899                        : x1(rangeAvg))
900                       */
901
902       g.select('rect.nv-measure')
903           .style('fill', color)
904           .attr('height', availableHeight / 3)
905           .attr('y', availableHeight / 3)
906           .attr('width', measurez < 0 ?
907                              x1(0) - x1(measurez[0])
908                            : x1(measurez[0]) - x1(0))
909           .attr('x', xp1(measurez))
910           .on('mouseover', function() {
911               dispatch.elementMouseover({
912                 value: measurez[0],
913                 label: 'Current',
914                 pos: [x1(measurez[0]), availableHeight/2]
915               })
916           })
917           .on('mouseout', function() {
918               dispatch.elementMouseout({
919                 value: measurez[0],
920                 label: 'Current'
921               })
922           })
923
924       var h3 =  availableHeight / 6;
925       if (markerz[0]) {
926         g.selectAll('path.nv-markerTriangle')
927             .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
928             .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
929             .on('mouseover', function() {
930               dispatch.elementMouseover({
931                 value: markerz[0],
932                 label: 'Previous',
933                 pos: [x1(markerz[0]), availableHeight/2]
934               })
935             })
936             .on('mouseout', function() {
937               dispatch.elementMouseout({
938                 value: markerz[0],
939                 label: 'Previous'
940               })
941             });
942       } else {
943         g.selectAll('path.nv-markerTriangle').remove();
944       }
945
946
947       wrap.selectAll('.nv-range')
948           .on('mouseover', function(d,i) {
949             var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
950
951             dispatch.elementMouseover({
952               value: d,
953               label: label,
954               pos: [x1(d), availableHeight/2]
955             })
956           })
957           .on('mouseout', function(d,i) {
958             var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
959
960             dispatch.elementMouseout({
961               value: d,
962               label: label
963             })
964           })
965
966 /* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
967       // Update the range rects.
968       var range = g.selectAll('rect.nv-range')
969           .data(rangez);
970
971       range.enter().append('rect')
972           .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
973           .attr('width', w0)
974           .attr('height', availableHeight)
975           .attr('x', reverse ? x0 : 0)
976           .on('mouseover', function(d,i) { 
977               dispatch.elementMouseover({
978                 value: d,
979                 label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
980                 pos: [x1(d), availableHeight/2]
981               })
982           })
983           .on('mouseout', function(d,i) { 
984               dispatch.elementMouseout({
985                 value: d,
986                 label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
987               })
988           })
989
990       d3.transition(range)
991           .attr('x', reverse ? x1 : 0)
992           .attr('width', w1)
993           .attr('height', availableHeight);
994
995
996       // Update the measure rects.
997       var measure = g.selectAll('rect.nv-measure')
998           .data(measurez);
999
1000       measure.enter().append('rect')
1001           .attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
1002           .style('fill', function(d,i) { return color(d,i ) })
1003           .attr('width', w0)
1004           .attr('height', availableHeight / 3)
1005           .attr('x', reverse ? x0 : 0)
1006           .attr('y', availableHeight / 3)
1007           .on('mouseover', function(d) { 
1008               dispatch.elementMouseover({
1009                 value: d,
1010                 label: 'Current', //TODO: make these labels a variable
1011                 pos: [x1(d), availableHeight/2]
1012               })
1013           })
1014           .on('mouseout', function(d) { 
1015               dispatch.elementMouseout({
1016                 value: d,
1017                 label: 'Current' //TODO: make these labels a variable
1018               })
1019           })
1020
1021       d3.transition(measure)
1022           .attr('width', w1)
1023           .attr('height', availableHeight / 3)
1024           .attr('x', reverse ? x1 : 0)
1025           .attr('y', availableHeight / 3);
1026
1027
1028
1029       // Update the marker lines.
1030       var marker = g.selectAll('path.nv-markerTriangle')
1031           .data(markerz);
1032
1033       var h3 =  availableHeight / 6;
1034       marker.enter().append('path')
1035           .attr('class', 'nv-markerTriangle')
1036           .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
1037           .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1038           .on('mouseover', function(d,i) {
1039               dispatch.elementMouseover({
1040                 value: d,
1041                 label: 'Previous',
1042                 pos: [x1(d), availableHeight/2]
1043               })
1044           })
1045           .on('mouseout', function(d,i) {
1046               dispatch.elementMouseout({
1047                 value: d,
1048                 label: 'Previous'
1049               })
1050           });
1051
1052       d3.transition(marker)
1053           .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
1054
1055       marker.exit().remove();
1056 */
1057
1058     });
1059
1060     // d3.timer.flush();  // Not needed?
1061
1062     return chart;
1063   }
1064
1065
1066   //============================================================
1067   // Expose Public Variables
1068   //------------------------------------------------------------
1069
1070   chart.dispatch = dispatch;
1071
1072   // left, right, top, bottom
1073   chart.orient = function(_) {
1074     if (!arguments.length) return orient;
1075     orient = _;
1076     reverse = orient == 'right' || orient == 'bottom';
1077     return chart;
1078   };
1079
1080   // ranges (bad, satisfactory, good)
1081   chart.ranges = function(_) {
1082     if (!arguments.length) return ranges;
1083     ranges = _;
1084     return chart;
1085   };
1086
1087   // markers (previous, goal)
1088   chart.markers = function(_) {
1089     if (!arguments.length) return markers;
1090     markers = _;
1091     return chart;
1092   };
1093
1094   // measures (actual, forecast)
1095   chart.measures = function(_) {
1096     if (!arguments.length) return measures;
1097     measures = _;
1098     return chart;
1099   };
1100
1101   chart.forceX = function(_) {
1102     if (!arguments.length) return forceX;
1103     forceX = _;
1104     return chart;
1105   };
1106
1107   chart.width = function(_) {
1108     if (!arguments.length) return width;
1109     width = _;
1110     return chart;
1111   };
1112
1113   chart.height = function(_) {
1114     if (!arguments.length) return height;
1115     height = _;
1116     return chart;
1117   };
1118
1119   chart.margin = function(_) {
1120     if (!arguments.length) return margin;
1121     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
1122     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
1123     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1124     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
1125     return chart;
1126   };
1127
1128   chart.tickFormat = function(_) {
1129     if (!arguments.length) return tickFormat;
1130     tickFormat = _;
1131     return chart;
1132   };
1133
1134   chart.color = function(_) {
1135     if (!arguments.length) return color;
1136     color = nv.utils.getColor(_);
1137     return chart;
1138   };
1139
1140   //============================================================
1141
1142
1143   return chart;
1144 };
1145
1146
1147
1148 // Chart design based on the recommendations of Stephen Few. Implementation
1149 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1150 // http://projects.instantcognition.com/protovis/bulletchart/
1151 nv.models.bulletChart = function() {
1152
1153   //============================================================
1154   // Public Variables with Default Settings
1155   //------------------------------------------------------------
1156
1157   var bullet = nv.models.bullet()
1158     ;
1159
1160   var orient = 'left' // TODO top & bottom
1161     , reverse = false
1162     , margin = {top: 5, right: 40, bottom: 20, left: 120}
1163     , ranges = function(d) { return d.ranges }
1164     , markers = function(d) { return d.markers }
1165     , measures = function(d) { return d.measures }
1166     , width = null
1167     , height = 55
1168     , tickFormat = null
1169     , tooltips = true
1170     , tooltip = function(key, x, y, e, graph) {
1171         return '<h3>' + x + '</h3>' +
1172                '<p>' + y + '</p>'
1173       }
1174     , noData = 'No Data Available.'
1175     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
1176     ;
1177
1178   //============================================================
1179
1180
1181   //============================================================
1182   // Private Variables
1183   //------------------------------------------------------------
1184
1185   var showTooltip = function(e, offsetElement) {
1186     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
1187         top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
1188         content = tooltip(e.key, e.label, e.value, e, chart);
1189
1190     nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
1191   };
1192
1193   //============================================================
1194
1195
1196   function chart(selection) {
1197     selection.each(function(d, i) {
1198       var container = d3.select(this);
1199
1200       var availableWidth = (width  || parseInt(container.style('width')) || 960)
1201                              - margin.left - margin.right,
1202           availableHeight = height - margin.top - margin.bottom,
1203           that = this;
1204
1205
1206       chart.update = function() { chart(selection) };
1207       chart.container = this;
1208
1209       //------------------------------------------------------------
1210       // Display No Data message if there's nothing to show.
1211
1212       if (!d || !ranges.call(this, d, i)) {
1213         var noDataText = container.selectAll('.nv-noData').data([noData]);
1214
1215         noDataText.enter().append('text')
1216           .attr('class', 'nvd3 nv-noData')
1217           .attr('dy', '-.7em')
1218           .style('text-anchor', 'middle');
1219
1220         noDataText
1221           .attr('x', margin.left + availableWidth / 2)
1222           .attr('y', 18 + margin.top + availableHeight / 2)
1223           .text(function(d) { return d });
1224
1225         return chart;
1226       } else {
1227         container.selectAll('.nv-noData').remove();
1228       }
1229
1230       //------------------------------------------------------------
1231
1232
1233
1234       var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1235           markerz = markers.call(this, d, i).slice().sort(d3.descending),
1236           measurez = measures.call(this, d, i).slice().sort(d3.descending);
1237
1238
1239       //------------------------------------------------------------
1240       // Setup containers and skeleton of chart
1241
1242       var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
1243       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
1244       var gEnter = wrapEnter.append('g');
1245       var g = wrap.select('g');
1246
1247       gEnter.append('g').attr('class', 'nv-bulletWrap');
1248       gEnter.append('g').attr('class', 'nv-titles');
1249
1250       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1251
1252       //------------------------------------------------------------
1253
1254
1255       // Compute the new x-scale.
1256       var x1 = d3.scale.linear()
1257           .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
1258           .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1259
1260       // Retrieve the old x-scale, if this is an update.
1261       var x0 = this.__chart__ || d3.scale.linear()
1262           .domain([0, Infinity])
1263           .range(x1.range());
1264
1265       // Stash the new scale.
1266       this.__chart__ = x1;
1267
1268       /*
1269       // Derive width-scales from the x-scales.
1270       var w0 = bulletWidth(x0),
1271           w1 = bulletWidth(x1);
1272
1273       function bulletWidth(x) {
1274         var x0 = x(0);
1275         return function(d) {
1276           return Math.abs(x(d) - x(0));
1277         };
1278       }
1279
1280       function bulletTranslate(x) {
1281         return function(d) {
1282           return 'translate(' + x(d) + ',0)';
1283         };
1284       }
1285       */
1286
1287       var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1288           w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1289
1290
1291       var title = gEnter.select('.nv-titles').append('g')
1292           .attr('text-anchor', 'end')
1293           .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
1294       title.append('text')
1295           .attr('class', 'nv-title')
1296           .text(function(d) { return d.title; });
1297
1298       title.append('text')
1299           .attr('class', 'nv-subtitle')
1300           .attr('dy', '1em')
1301           .text(function(d) { return d.subtitle; });
1302
1303
1304
1305       bullet
1306         .width(availableWidth)
1307         .height(availableHeight)
1308
1309       var bulletWrap = g.select('.nv-bulletWrap');
1310
1311       d3.transition(bulletWrap).call(bullet);
1312
1313
1314
1315       // Compute the tick format.
1316       var format = tickFormat || x1.tickFormat( availableWidth / 100 );
1317
1318       // Update the tick groups.
1319       var tick = g.selectAll('g.nv-tick')
1320           .data(x1.ticks( availableWidth / 50 ), function(d) {
1321             return this.textContent || format(d);
1322           });
1323
1324       // Initialize the ticks with the old scale, x0.
1325       var tickEnter = tick.enter().append('g')
1326           .attr('class', 'nv-tick')
1327           .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
1328           .style('opacity', 1e-6);
1329
1330       tickEnter.append('line')
1331           .attr('y1', availableHeight)
1332           .attr('y2', availableHeight * 7 / 6);
1333
1334       tickEnter.append('text')
1335           .attr('text-anchor', 'middle')
1336           .attr('dy', '1em')
1337           .attr('y', availableHeight * 7 / 6)
1338           .text(format);
1339
1340
1341       // Transition the updating ticks to the new scale, x1.
1342       var tickUpdate = d3.transition(tick)
1343           .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1344           .style('opacity', 1);
1345
1346       tickUpdate.select('line')
1347           .attr('y1', availableHeight)
1348           .attr('y2', availableHeight * 7 / 6);
1349
1350       tickUpdate.select('text')
1351           .attr('y', availableHeight * 7 / 6);
1352
1353       // Transition the exiting ticks to the new scale, x1.
1354       d3.transition(tick.exit())
1355           .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1356           .style('opacity', 1e-6)
1357           .remove();
1358
1359
1360       //============================================================
1361       // Event Handling/Dispatching (in chart's scope)
1362       //------------------------------------------------------------
1363
1364       dispatch.on('tooltipShow', function(e) {
1365         e.key = d.title;
1366         if (tooltips) showTooltip(e, that.parentNode);
1367       });
1368
1369       //============================================================
1370
1371     });
1372
1373     d3.timer.flush();
1374
1375     return chart;
1376   }
1377
1378
1379   //============================================================
1380   // Event Handling/Dispatching (out of chart's scope)
1381   //------------------------------------------------------------
1382
1383   bullet.dispatch.on('elementMouseover.tooltip', function(e) {
1384     dispatch.tooltipShow(e);
1385   });
1386
1387   bullet.dispatch.on('elementMouseout.tooltip', function(e) {
1388     dispatch.tooltipHide(e);
1389   });
1390
1391   dispatch.on('tooltipHide', function() {
1392     if (tooltips) nv.tooltip.cleanup();
1393   });
1394
1395   //============================================================
1396
1397
1398   //============================================================
1399   // Expose Public Variables
1400   //------------------------------------------------------------
1401
1402   chart.dispatch = dispatch;
1403   chart.bullet = bullet;
1404
1405   d3.rebind(chart, bullet, 'color');
1406
1407   // left, right, top, bottom
1408   chart.orient = function(x) {
1409     if (!arguments.length) return orient;
1410     orient = x;
1411     reverse = orient == 'right' || orient == 'bottom';
1412     return chart;
1413   };
1414
1415   // ranges (bad, satisfactory, good)
1416   chart.ranges = function(x) {
1417     if (!arguments.length) return ranges;
1418     ranges = x;
1419     return chart;
1420   };
1421
1422   // markers (previous, goal)
1423   chart.markers = function(x) {
1424     if (!arguments.length) return markers;
1425     markers = x;
1426     return chart;
1427   };
1428
1429   // measures (actual, forecast)
1430   chart.measures = function(x) {
1431     if (!arguments.length) return measures;
1432     measures = x;
1433     return chart;
1434   };
1435
1436   chart.width = function(x) {
1437     if (!arguments.length) return width;
1438     width = x;
1439     return chart;
1440   };
1441
1442   chart.height = function(x) {
1443     if (!arguments.length) return height;
1444     height = x;
1445     return chart;
1446   };
1447
1448   chart.margin = function(_) {
1449     if (!arguments.length) return margin;
1450     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
1451     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
1452     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1453     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
1454     return chart;
1455   };
1456
1457   chart.tickFormat = function(x) {
1458     if (!arguments.length) return tickFormat;
1459     tickFormat = x;
1460     return chart;
1461   };
1462
1463   chart.tooltips = function(_) {
1464     if (!arguments.length) return tooltips;
1465     tooltips = _;
1466     return chart;
1467   };
1468
1469   chart.tooltipContent = function(_) {
1470     if (!arguments.length) return tooltip;
1471     tooltip = _;
1472     return chart;
1473   };
1474
1475   chart.noData = function(_) {
1476     if (!arguments.length) return noData;
1477     noData = _;
1478     return chart;
1479   };
1480
1481   //============================================================
1482
1483
1484   return chart;
1485 };
1486
1487
1488
1489 nv.models.cumulativeLineChart = function() {
1490
1491   //============================================================
1492   // Public Variables with Default Settings
1493   //------------------------------------------------------------
1494
1495   var lines = nv.models.line()
1496     , xAxis = nv.models.axis()
1497     , yAxis = nv.models.axis()
1498     , legend = nv.models.legend()
1499     , controls = nv.models.legend()
1500     ;
1501
1502   var margin = {top: 30, right: 30, bottom: 50, left: 60}
1503     , color = nv.utils.defaultColor()
1504     , width = null
1505     , height = null
1506     , showLegend = true
1507     , tooltips = true
1508     , showControls = true
1509     , rescaleY = true
1510     , tooltip = function(key, x, y, e, graph) {
1511         return '<h3>' + key + '</h3>' +
1512                '<p>' +  y + ' at ' + x + '</p>'
1513       }
1514     , x //can be accessed via chart.xScale()
1515     , y //can be accessed via chart.yScale()
1516     , id = lines.id()
1517     , state = { index: 0, rescaleY: rescaleY }
1518     , defaultState = null
1519     , noData = 'No Data Available.'
1520     , average = function(d) { return d.average }
1521     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
1522     ;
1523
1524   xAxis
1525     .orient('bottom')
1526     .tickPadding(7)
1527     ;
1528   yAxis
1529     .orient('left')
1530     ;
1531
1532   //============================================================
1533
1534
1535   //============================================================
1536   // Private Variables
1537   //------------------------------------------------------------
1538
1539    var dx = d3.scale.linear()
1540      , index = {i: 0, x: 0}
1541      ;
1542
1543   var showTooltip = function(e, offsetElement) {
1544     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
1545         top = e.pos[1] + ( offsetElement.offsetTop || 0),
1546         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
1547         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
1548         content = tooltip(e.series.key, x, y, e, chart);
1549
1550     nv.tooltip.show([left, top], content, null, null, offsetElement);
1551   };
1552
1553 /*
1554   //Moved to see if we can get better behavior to fix issue #315
1555   var indexDrag = d3.behavior.drag()
1556                     .on('dragstart', dragStart)
1557                     .on('drag', dragMove)
1558                     .on('dragend', dragEnd);
1559
1560   function dragStart(d,i) {
1561     d3.select(chart.container)
1562         .style('cursor', 'ew-resize');
1563   }
1564
1565   function dragMove(d,i) {
1566     d.x += d3.event.dx;
1567     d.i = Math.round(dx.invert(d.x));
1568
1569     d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
1570     chart.update();
1571   }
1572
1573   function dragEnd(d,i) {
1574     d3.select(chart.container)
1575         .style('cursor', 'auto');
1576     chart.update();
1577   }
1578 */
1579
1580   //============================================================
1581
1582
1583   function chart(selection) {
1584     selection.each(function(data) {
1585       var container = d3.select(this).classed('nv-chart-' + id, true),
1586           that = this;
1587
1588       var availableWidth = (width  || parseInt(container.style('width')) || 960)
1589                              - margin.left - margin.right,
1590           availableHeight = (height || parseInt(container.style('height')) || 400)
1591                              - margin.top - margin.bottom;
1592
1593
1594       chart.update = function() { container.transition().call(chart) };
1595       chart.container = this;
1596
1597       //set state.disabled
1598       state.disabled = data.map(function(d) { return !!d.disabled });
1599
1600       if (!defaultState) {
1601         var key;
1602         defaultState = {};
1603         for (key in state) {
1604           if (state[key] instanceof Array)
1605             defaultState[key] = state[key].slice(0);
1606           else
1607             defaultState[key] = state[key];
1608         }
1609       }
1610
1611       var indexDrag = d3.behavior.drag()
1612                         .on('dragstart', dragStart)
1613                         .on('drag', dragMove)
1614                         .on('dragend', dragEnd);
1615
1616
1617       function dragStart(d,i) {
1618         d3.select(chart.container)
1619             .style('cursor', 'ew-resize');
1620       }
1621
1622       function dragMove(d,i) {
1623         index.x = d3.event.x;
1624         index.i = Math.round(dx.invert(index.x));
1625         updateZero();
1626       }
1627
1628       function dragEnd(d,i) {
1629         d3.select(chart.container)
1630             .style('cursor', 'auto');
1631
1632         // update state and send stateChange with new index
1633         state.index = index.i;
1634         dispatch.stateChange(state);
1635       }
1636
1637
1638
1639
1640       //------------------------------------------------------------
1641       // Display No Data message if there's nothing to show.
1642
1643       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
1644         var noDataText = container.selectAll('.nv-noData').data([noData]);
1645
1646         noDataText.enter().append('text')
1647           .attr('class', 'nvd3 nv-noData')
1648           .attr('dy', '-.7em')
1649           .style('text-anchor', 'middle');
1650
1651         noDataText
1652           .attr('x', margin.left + availableWidth / 2)
1653           .attr('y', margin.top + availableHeight / 2)
1654           .text(function(d) { return d });
1655
1656         return chart;
1657       } else {
1658         container.selectAll('.nv-noData').remove();
1659       }
1660
1661       //------------------------------------------------------------
1662
1663
1664       //------------------------------------------------------------
1665       // Setup Scales
1666
1667       x = lines.xScale();
1668       y = lines.yScale();
1669
1670
1671       if (!rescaleY) {
1672         var seriesDomains = data
1673           .filter(function(series) { return !series.disabled })
1674           .map(function(series,i) {
1675             var initialDomain = d3.extent(series.values, lines.y());
1676
1677             //account for series being disabled when losing 95% or more
1678             if (initialDomain[0] < -.95) initialDomain[0] = -.95;
1679
1680             return [
1681               (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
1682               (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
1683             ];
1684           });
1685
1686         var completeDomain = [
1687           d3.min(seriesDomains, function(d) { return d[0] }),
1688           d3.max(seriesDomains, function(d) { return d[1] })
1689         ]
1690
1691         lines.yDomain(completeDomain);
1692       } else {
1693         lines.yDomain(null);
1694       }
1695
1696
1697       dx  .domain([0, data[0].values.length - 1]) //Assumes all series have same length
1698           .range([0, availableWidth])
1699           .clamp(true);
1700
1701       //------------------------------------------------------------
1702
1703
1704       var data = indexify(index.i, data);
1705
1706
1707       //------------------------------------------------------------
1708       // Setup containers and skeleton of chart
1709
1710       var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
1711       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
1712       var g = wrap.select('g');
1713
1714       gEnter.append('g').attr('class', 'nv-x nv-axis');
1715       gEnter.append('g').attr('class', 'nv-y nv-axis');
1716       gEnter.append('g').attr('class', 'nv-background');
1717       gEnter.append('g').attr('class', 'nv-linesWrap');
1718       gEnter.append('g').attr('class', 'nv-avgLinesWrap');
1719       gEnter.append('g').attr('class', 'nv-legendWrap');
1720       gEnter.append('g').attr('class', 'nv-controlsWrap');
1721
1722       //------------------------------------------------------------
1723
1724
1725       //------------------------------------------------------------
1726       // Legend
1727
1728       if (showLegend) {
1729         legend.width(availableWidth);
1730
1731         g.select('.nv-legendWrap')
1732             .datum(data)
1733             .call(legend);
1734
1735         if ( margin.top != legend.height()) {
1736           margin.top = legend.height();
1737           availableHeight = (height || parseInt(container.style('height')) || 400)
1738                              - margin.top - margin.bottom;
1739         }
1740
1741         g.select('.nv-legendWrap')
1742             .attr('transform', 'translate(0,' + (-margin.top) +')')
1743       }
1744
1745       //------------------------------------------------------------
1746
1747
1748       //------------------------------------------------------------
1749       // Controls
1750
1751       if (showControls) {
1752         var controlsData = [
1753           { key: 'Re-scale y-axis', disabled: !rescaleY }
1754         ];
1755
1756         controls.width(140).color(['#444', '#444', '#444']);
1757         g.select('.nv-controlsWrap')
1758             .datum(controlsData)
1759             .attr('transform', 'translate(0,' + (-margin.top) +')')
1760             .call(controls);
1761       }
1762
1763       //------------------------------------------------------------
1764
1765
1766       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1767
1768
1769       // Show error if series goes below 100%
1770       var tempDisabled = data.filter(function(d) { return d.tempDisabled });
1771
1772       wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
1773       if (tempDisabled.length) {
1774         wrap.append('text').attr('class', 'tempDisabled')
1775             .attr('x', availableWidth / 2)
1776             .attr('y', '-.71em')
1777             .style('text-anchor', 'end')
1778             .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
1779       }
1780
1781       //------------------------------------------------------------
1782       // Main Chart Component(s)
1783
1784       gEnter.select('.nv-background')
1785         .append('rect');
1786
1787       g.select('.nv-background rect')
1788           .attr('width', availableWidth)
1789           .attr('height', availableHeight);
1790
1791       lines
1792         //.x(function(d) { return d.x })
1793         .y(function(d) { return d.display.y })
1794         .width(availableWidth)
1795         .height(availableHeight)
1796         .color(data.map(function(d,i) {
1797           return d.color || color(d, i);
1798         }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
1799
1800
1801
1802       var linesWrap = g.select('.nv-linesWrap')
1803           .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));
1804
1805       //d3.transition(linesWrap).call(lines);
1806       linesWrap.call(lines);
1807
1808       /*Handle average lines [AN-612] ----------------------------*/
1809
1810       //Store a series index number in the data array.
1811       data.forEach(function(d,i) {
1812             d.seriesIndex = i;
1813       });
1814
1815       var avgLineData = data.filter(function(d) {
1816           return !d.disabled && !!average(d);
1817       });
1818
1819       var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
1820               .data(avgLineData, function(d) { return d.key; });
1821
1822       avgLines.enter()
1823               .append('line')
1824               .style('stroke-width',2)
1825               .style('stroke-dasharray','10,10')
1826               .style('stroke',function (d,i) {
1827                   return lines.color()(d,d.seriesIndex);
1828               })
1829               .attr('x1',0)
1830               .attr('x2',availableWidth)
1831               .attr('y1', function(d) { return y(average(d)); })
1832               .attr('y2', function(d) { return y(average(d)); });
1833
1834       avgLines
1835               .attr('x1',0)
1836               .attr('x2',availableWidth)
1837               .attr('y1', function(d) { return y(average(d)); })
1838               .attr('y2', function(d) { return y(average(d)); });
1839
1840       avgLines.exit().remove();
1841
1842       //Create index line -----------------------------------------
1843
1844       var indexLine = linesWrap.selectAll('.nv-indexLine')
1845           .data([index]);
1846       indexLine.enter().append('rect').attr('class', 'nv-indexLine')
1847           .attr('width', 3)
1848           .attr('x', -2)
1849           .attr('fill', 'red')
1850           .attr('fill-opacity', .5)
1851           .call(indexDrag)
1852
1853       indexLine
1854           .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
1855           .attr('height', availableHeight)
1856
1857       //------------------------------------------------------------
1858
1859
1860       //------------------------------------------------------------
1861       // Setup Axes
1862
1863       xAxis
1864         .scale(x)
1865         //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
1866         .ticks( Math.min(data[0].values.length,availableWidth/70) )
1867         .tickSize(-availableHeight, 0);
1868
1869       g.select('.nv-x.nv-axis')
1870           .attr('transform', 'translate(0,' + y.range()[0] + ')');
1871       d3.transition(g.select('.nv-x.nv-axis'))
1872           .call(xAxis);
1873
1874
1875       yAxis
1876         .scale(y)
1877         .ticks( availableHeight / 36 )
1878         .tickSize( -availableWidth, 0);
1879
1880       d3.transition(g.select('.nv-y.nv-axis'))
1881           .call(yAxis);
1882
1883       //------------------------------------------------------------
1884
1885
1886       //============================================================
1887       // Event Handling/Dispatching (in chart's scope)
1888       //------------------------------------------------------------
1889
1890
1891       function updateZero() {
1892         indexLine
1893           .data([index]);
1894
1895         container.call(chart);
1896       }
1897
1898       g.select('.nv-background rect')
1899           .on('click', function() {
1900             index.x = d3.mouse(this)[0];
1901             index.i = Math.round(dx.invert(index.x));
1902
1903             // update state and send stateChange with new index
1904             state.index = index.i;
1905             dispatch.stateChange(state);
1906
1907             updateZero();
1908           });
1909
1910       lines.dispatch.on('elementClick', function(e) {
1911         index.i = e.pointIndex;
1912         index.x = dx(index.i);
1913
1914         // update state and send stateChange with new index
1915         state.index = index.i;
1916         dispatch.stateChange(state);
1917
1918         updateZero();
1919       });
1920
1921       controls.dispatch.on('legendClick', function(d,i) { 
1922         d.disabled = !d.disabled;
1923         rescaleY = !d.disabled;
1924
1925         state.rescaleY = rescaleY;
1926         dispatch.stateChange(state);
1927
1928         //selection.transition().call(chart);
1929         chart.update();
1930       });
1931
1932
1933       legend.dispatch.on('legendClick', function(d,i) { 
1934         d.disabled = !d.disabled;
1935
1936         if (!data.filter(function(d) { return !d.disabled }).length) {
1937           data.map(function(d) {
1938             d.disabled = false;
1939             wrap.selectAll('.nv-series').classed('disabled', false);
1940             return d;
1941           });
1942         }
1943
1944         state.disabled = data.map(function(d) { return !!d.disabled });
1945         dispatch.stateChange(state);
1946
1947         //selection.transition().call(chart);
1948         chart.update();
1949       });
1950
1951       legend.dispatch.on('legendDblclick', function(d) {
1952           //Double clicking should always enable current series, and disabled all others.
1953           data.forEach(function(d) {
1954              d.disabled = true;
1955           });
1956           d.disabled = false;  
1957
1958           state.disabled = data.map(function(d) { return !!d.disabled });
1959           dispatch.stateChange(state);
1960           chart.update();
1961       });
1962
1963
1964 /*
1965       //
1966       legend.dispatch.on('legendMouseover', function(d, i) {
1967         d.hover = true;
1968         selection.transition().call(chart)
1969       });
1970
1971       legend.dispatch.on('legendMouseout', function(d, i) {
1972         d.hover = false;
1973         selection.transition().call(chart)
1974       });
1975 */
1976
1977       dispatch.on('tooltipShow', function(e) {
1978         if (tooltips) showTooltip(e, that.parentNode);
1979       });
1980
1981
1982       // Update chart from a state object passed to event handler
1983       dispatch.on('changeState', function(e) {
1984
1985         if (typeof e.disabled !== 'undefined') {
1986           data.forEach(function(series,i) {
1987             series.disabled = e.disabled[i];
1988           });
1989
1990           state.disabled = e.disabled;
1991         }
1992
1993
1994         if (typeof e.index !== 'undefined') {
1995           index.i = e.index;
1996           index.x = dx(index.i);
1997
1998           state.index = e.index;
1999
2000           indexLine
2001             .data([index]);
2002         }
2003
2004
2005         if (typeof e.rescaleY !== 'undefined') {
2006           rescaleY = e.rescaleY;
2007         }
2008
2009         chart.update();
2010       });
2011
2012       //============================================================
2013
2014     });
2015
2016     return chart;
2017   }
2018
2019
2020   //============================================================
2021   // Event Handling/Dispatching (out of chart's scope)
2022   //------------------------------------------------------------
2023
2024   lines.dispatch.on('elementMouseover.tooltip', function(e) {
2025     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
2026     dispatch.tooltipShow(e);
2027   });
2028
2029   lines.dispatch.on('elementMouseout.tooltip', function(e) {
2030     dispatch.tooltipHide(e);
2031   });
2032
2033   dispatch.on('tooltipHide', function() {
2034     if (tooltips) nv.tooltip.cleanup();
2035   });
2036
2037   //============================================================
2038
2039
2040   //============================================================
2041   // Expose Public Variables
2042   //------------------------------------------------------------
2043
2044   // expose chart's sub-components
2045   chart.dispatch = dispatch;
2046   chart.lines = lines;
2047   chart.legend = legend;
2048   chart.xAxis = xAxis;
2049   chart.yAxis = yAxis;
2050
2051   d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
2052
2053   chart.margin = function(_) {
2054     if (!arguments.length) return margin;
2055     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2056     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2057     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2058     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2059     return chart;
2060   };
2061
2062   chart.width = function(_) {
2063     if (!arguments.length) return width;
2064     width = _;
2065     return chart;
2066   };
2067
2068   chart.height = function(_) {
2069     if (!arguments.length) return height;
2070     height = _;
2071     return chart;
2072   };
2073
2074   chart.color = function(_) {
2075     if (!arguments.length) return color;
2076     color = nv.utils.getColor(_);
2077     legend.color(color);
2078     return chart;
2079   };
2080
2081   chart.rescaleY = function(_) {
2082     if (!arguments.length) return rescaleY;
2083     rescaleY = _
2084     return rescaleY;
2085   };
2086
2087   chart.showControls = function(_) {
2088     if (!arguments.length) return showControls;
2089     showControls = _;
2090     return chart;
2091   };
2092
2093   chart.showLegend = function(_) {
2094     if (!arguments.length) return showLegend;
2095     showLegend = _;
2096     return chart;
2097   };
2098
2099   chart.tooltips = function(_) {
2100     if (!arguments.length) return tooltips;
2101     tooltips = _;
2102     return chart;
2103   };
2104
2105   chart.tooltipContent = function(_) {
2106     if (!arguments.length) return tooltip;
2107     tooltip = _;
2108     return chart;
2109   };
2110
2111   chart.state = function(_) {
2112     if (!arguments.length) return state;
2113     state = _;
2114     return chart;
2115   };
2116
2117   chart.defaultState = function(_) {
2118     if (!arguments.length) return defaultState;
2119     defaultState = _;
2120     return chart;
2121   };
2122
2123   chart.noData = function(_) {
2124     if (!arguments.length) return noData;
2125     noData = _;
2126     return chart;
2127   };
2128
2129   chart.average = function(_) {
2130      if(!arguments.length) return average;
2131      average = _;
2132      return chart;
2133   };
2134
2135   //============================================================
2136
2137
2138   //============================================================
2139   // Functions
2140   //------------------------------------------------------------
2141
2142   /* Normalize the data according to an index point. */
2143   function indexify(idx, data) {
2144     return data.map(function(line, i) {
2145       if (!line.values) {
2146          return line;
2147       }
2148       var v = lines.y()(line.values[idx], idx);
2149
2150       //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2151       if (v < -.95) {
2152         //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2153         line.tempDisabled = true;
2154         return line;
2155       }
2156
2157       line.tempDisabled = false;
2158
2159       line.values = line.values.map(function(point, pointIndex) {
2160         point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
2161         return point;
2162       })
2163
2164       return line;
2165     })
2166   }
2167
2168   //============================================================
2169
2170
2171   return chart;
2172 }
2173 //TODO: consider deprecating by adding necessary features to multiBar model
2174 nv.models.discreteBar = function() {
2175
2176   //============================================================
2177   // Public Variables with Default Settings
2178   //------------------------------------------------------------
2179
2180   var margin = {top: 0, right: 0, bottom: 0, left: 0}
2181     , width = 960
2182     , height = 500
2183     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2184     , x = d3.scale.ordinal()
2185     , y = d3.scale.linear()
2186     , getX = function(d) { return d.x }
2187     , getY = function(d) { return d.y }
2188     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2189     , color = nv.utils.defaultColor()
2190     , showValues = false
2191     , valueFormat = d3.format(',.2f')
2192     , xDomain
2193     , yDomain
2194     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2195     , rectClass = 'discreteBar'
2196     ;
2197
2198   //============================================================
2199
2200
2201   //============================================================
2202   // Private Variables
2203   //------------------------------------------------------------
2204
2205   var x0, y0;
2206
2207   //============================================================
2208
2209
2210   function chart(selection) {
2211     selection.each(function(data) {
2212       var availableWidth = width - margin.left - margin.right,
2213           availableHeight = height - margin.top - margin.bottom,
2214           container = d3.select(this);
2215
2216
2217       //add series index to each data point for reference
2218       data = data.map(function(series, i) {
2219         series.values = series.values.map(function(point) {
2220           point.series = i;
2221           return point;
2222         });
2223         return series;
2224       });
2225
2226
2227       //------------------------------------------------------------
2228       // Setup Scales
2229
2230       // remap and flatten the data for use in calculating the scales' domains
2231       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2232             data.map(function(d) {
2233               return d.values.map(function(d,i) {
2234                 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2235               })
2236             });
2237
2238       x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2239           .rangeBands([0, availableWidth], .1);
2240
2241       y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
2242
2243
2244       // If showValues, pad the Y axis range to account for label height
2245       if (showValues) y.range([availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
2246       else y.range([availableHeight, 0]);
2247
2248       //store old scales if they exist
2249       x0 = x0 || x;
2250       y0 = y0 || y.copy().range([y(0),y(0)]);
2251
2252       //------------------------------------------------------------
2253
2254
2255       //------------------------------------------------------------
2256       // Setup containers and skeleton of chart
2257
2258       var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
2259       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
2260       var gEnter = wrapEnter.append('g');
2261       var g = wrap.select('g');
2262
2263       gEnter.append('g').attr('class', 'nv-groups');
2264
2265       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2266
2267       //------------------------------------------------------------
2268
2269
2270
2271       //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
2272       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
2273           .data(function(d) { return d }, function(d) { return d.key });
2274       groups.enter().append('g')
2275           .style('stroke-opacity', 1e-6)
2276           .style('fill-opacity', 1e-6);
2277       d3.transition(groups.exit())
2278           .style('stroke-opacity', 1e-6)
2279           .style('fill-opacity', 1e-6)
2280           .remove();
2281       groups
2282           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
2283           .classed('hover', function(d) { return d.hover });
2284       d3.transition(groups)
2285           .style('stroke-opacity', 1)
2286           .style('fill-opacity', .75);
2287
2288
2289       var bars = groups.selectAll('g.nv-bar')
2290           .data(function(d) { return d.values });
2291
2292       bars.exit().remove();
2293
2294
2295       var barsEnter = bars.enter().append('g')
2296           .attr('transform', function(d,i,j) {
2297               return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' 
2298           })
2299           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
2300             d3.select(this).classed('hover', true);
2301             dispatch.elementMouseover({
2302               value: getY(d,i),
2303               point: d,
2304               series: data[d.series],
2305               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
2306               pointIndex: i,
2307               seriesIndex: d.series,
2308               e: d3.event
2309             });
2310           })
2311           .on('mouseout', function(d,i) {
2312             d3.select(this).classed('hover', false);
2313             dispatch.elementMouseout({
2314               value: getY(d,i),
2315               point: d,
2316               series: data[d.series],
2317               pointIndex: i,
2318               seriesIndex: d.series,
2319               e: d3.event
2320             });
2321           })
2322           .on('click', function(d,i) {
2323             dispatch.elementClick({
2324               value: getY(d,i),
2325               point: d,
2326               series: data[d.series],
2327               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
2328               pointIndex: i,
2329               seriesIndex: d.series,
2330               e: d3.event
2331             });
2332             d3.event.stopPropagation();
2333           })
2334           .on('dblclick', function(d,i) {
2335             dispatch.elementDblClick({
2336               value: getY(d,i),
2337               point: d,
2338               series: data[d.series],
2339               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
2340               pointIndex: i,
2341               seriesIndex: d.series,
2342               e: d3.event
2343             });
2344             d3.event.stopPropagation();
2345           });
2346
2347       barsEnter.append('rect')
2348           .attr('height', 0)
2349           .attr('width', x.rangeBand() * .9 / data.length )
2350
2351       if (showValues) {
2352         barsEnter.append('text')
2353           .attr('text-anchor', 'middle')
2354         bars.select('text')
2355           .attr('x', x.rangeBand() * .9 / 2)
2356           .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
2357           .text(function(d,i) { return valueFormat(getY(d,i)) });
2358       } else {
2359         bars.selectAll('text').remove();
2360       }
2361
2362       bars
2363           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
2364           .style('fill', function(d,i) { return d.color || color(d,i) })
2365           .style('stroke', function(d,i) { return d.color || color(d,i) })
2366         .select('rect')
2367           .attr('class', rectClass)
2368           .attr('width', x.rangeBand() * .9 / data.length);
2369       d3.transition(bars)
2370         //.delay(function(d,i) { return i * 1200 / data[0].values.length })
2371           .attr('transform', function(d,i) {
2372             var left = x(getX(d,i)) + x.rangeBand() * .05,
2373                 top = getY(d,i) < 0 ?
2374                         y(0) :
2375                         y(0) - y(getY(d,i)) < 1 ?
2376                           y(0) - 1 : //make 1 px positive bars show up above y=0
2377                           y(getY(d,i));
2378
2379               return 'translate(' + left + ', ' + top + ')'
2380           })
2381         .select('rect')
2382           .attr('height', function(d,i) {
2383             return  Math.max(Math.abs(y(getY(d,i)) - y(0)) || 1)
2384           });
2385
2386
2387       //store old scales for use in transitions on update
2388       x0 = x.copy();
2389       y0 = y.copy();
2390
2391     });
2392
2393     return chart;
2394   }
2395
2396
2397   //============================================================
2398   // Expose Public Variables
2399   //------------------------------------------------------------
2400
2401   chart.dispatch = dispatch;
2402
2403   chart.x = function(_) {
2404     if (!arguments.length) return getX;
2405     getX = _;
2406     return chart;
2407   };
2408
2409   chart.y = function(_) {
2410     if (!arguments.length) return getY;
2411     getY = _;
2412     return chart;
2413   };
2414
2415   chart.margin = function(_) {
2416     if (!arguments.length) return margin;
2417     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2418     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2419     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2420     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2421     return chart;
2422   };
2423
2424   chart.width = function(_) {
2425     if (!arguments.length) return width;
2426     width = _;
2427     return chart;
2428   };
2429
2430   chart.height = function(_) {
2431     if (!arguments.length) return height;
2432     height = _;
2433     return chart;
2434   };
2435
2436   chart.xScale = function(_) {
2437     if (!arguments.length) return x;
2438     x = _;
2439     return chart;
2440   };
2441
2442   chart.yScale = function(_) {
2443     if (!arguments.length) return y;
2444     y = _;
2445     return chart;
2446   };
2447
2448   chart.xDomain = function(_) {
2449     if (!arguments.length) return xDomain;
2450     xDomain = _;
2451     return chart;
2452   };
2453
2454   chart.yDomain = function(_) {
2455     if (!arguments.length) return yDomain;
2456     yDomain = _;
2457     return chart;
2458   };
2459
2460   chart.forceY = function(_) {
2461     if (!arguments.length) return forceY;
2462     forceY = _;
2463     return chart;
2464   };
2465
2466   chart.color = function(_) {
2467     if (!arguments.length) return color;
2468     color = nv.utils.getColor(_);
2469     return chart;
2470   };
2471
2472   chart.id = function(_) {
2473     if (!arguments.length) return id;
2474     id = _;
2475     return chart;
2476   };
2477
2478   chart.showValues = function(_) {
2479     if (!arguments.length) return showValues;
2480     showValues = _;
2481     return chart;
2482   };
2483
2484   chart.valueFormat= function(_) {
2485     if (!arguments.length) return valueFormat;
2486     valueFormat = _;
2487     return chart;
2488   };
2489
2490   chart.rectClass= function(_) {
2491     if (!arguments.length) return rectClass;
2492     rectClass = _;
2493     return chart;
2494   }
2495   //============================================================
2496
2497
2498   return chart;
2499 }
2500
2501 nv.models.discreteBarChart = function() {
2502
2503   //============================================================
2504   // Public Variables with Default Settings
2505   //------------------------------------------------------------
2506
2507   var discretebar = nv.models.discreteBar()
2508     , xAxis = nv.models.axis()
2509     , yAxis = nv.models.axis()
2510     ;
2511
2512   var margin = {top: 15, right: 10, bottom: 50, left: 60}
2513     , width = null
2514     , height = null
2515     , color = nv.utils.getColor()
2516     , staggerLabels = false
2517     , tooltips = true
2518     , tooltip = function(key, x, y, e, graph) {
2519         return '<h3>' + x + '</h3>' +
2520                '<p>' +  y + '</p>'
2521       }
2522     , x
2523     , y
2524     , noData = "No Data Available."
2525     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
2526     ;
2527
2528   xAxis
2529     .orient('bottom')
2530     .highlightZero(false)
2531     .showMaxMin(false)
2532     .tickFormat(function(d) { return d })
2533     ;
2534   yAxis
2535     .orient('left')
2536     .tickFormat(d3.format(',.1f'))
2537     ;
2538
2539   //============================================================
2540
2541
2542   //============================================================
2543   // Private Variables
2544   //------------------------------------------------------------
2545
2546   var showTooltip = function(e, offsetElement) {
2547     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2548         top = e.pos[1] + ( offsetElement.offsetTop || 0),
2549         x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
2550         y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
2551         content = tooltip(e.series.key, x, y, e, chart);
2552
2553     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
2554   };
2555
2556   //============================================================
2557
2558
2559   function chart(selection) {
2560     selection.each(function(data) {
2561       var container = d3.select(this),
2562           that = this;
2563
2564       var availableWidth = (width  || parseInt(container.style('width')) || 960)
2565                              - margin.left - margin.right,
2566           availableHeight = (height || parseInt(container.style('height')) || 400)
2567                              - margin.top - margin.bottom;
2568
2569
2570       chart.update = function() { dispatch.beforeUpdate(); container.transition().call(chart); };
2571       chart.container = this;
2572
2573
2574       //------------------------------------------------------------
2575       // Display No Data message if there's nothing to show.
2576
2577       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2578         var noDataText = container.selectAll('.nv-noData').data([noData]);
2579
2580         noDataText.enter().append('text')
2581           .attr('class', 'nvd3 nv-noData')
2582           .attr('dy', '-.7em')
2583           .style('text-anchor', 'middle');
2584
2585         noDataText
2586           .attr('x', margin.left + availableWidth / 2)
2587           .attr('y', margin.top + availableHeight / 2)
2588           .text(function(d) { return d });
2589
2590         return chart;
2591       } else {
2592         container.selectAll('.nv-noData').remove();
2593       }
2594
2595       //------------------------------------------------------------
2596
2597
2598       //------------------------------------------------------------
2599       // Setup Scales
2600
2601       x = discretebar.xScale();
2602       y = discretebar.yScale();
2603
2604       //------------------------------------------------------------
2605
2606
2607       //------------------------------------------------------------
2608       // Setup containers and skeleton of chart
2609
2610       var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
2611       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
2612       var defsEnter = gEnter.append('defs');
2613       var g = wrap.select('g');
2614
2615       gEnter.append('g').attr('class', 'nv-x nv-axis');
2616       gEnter.append('g').attr('class', 'nv-y nv-axis');
2617       gEnter.append('g').attr('class', 'nv-barsWrap');
2618
2619       g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2620
2621       //------------------------------------------------------------
2622
2623
2624       //------------------------------------------------------------
2625       // Main Chart Component(s)
2626
2627       discretebar
2628         .width(availableWidth)
2629         .height(availableHeight);
2630
2631
2632       var barsWrap = g.select('.nv-barsWrap')
2633           .datum(data.filter(function(d) { return !d.disabled }))
2634
2635       d3.transition(barsWrap).call(discretebar);
2636
2637       //------------------------------------------------------------
2638
2639
2640
2641       defsEnter.append('clipPath')
2642           .attr('id', 'nv-x-label-clip-' + discretebar.id())
2643         .append('rect');
2644
2645       g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
2646           .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
2647           .attr('height', 16)
2648           .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
2649
2650
2651       //------------------------------------------------------------
2652       // Setup Axes
2653
2654       xAxis
2655         .scale(x)
2656         .ticks( availableWidth / 100 )
2657         .tickSize(-availableHeight, 0);
2658
2659       g.select('.nv-x.nv-axis')
2660           .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
2661       //d3.transition(g.select('.nv-x.nv-axis'))
2662       g.select('.nv-x.nv-axis').transition().duration(0)
2663           .call(xAxis);
2664
2665
2666       var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
2667
2668       if (staggerLabels) {
2669         xTicks
2670             .selectAll('text')
2671             .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2672       }
2673
2674       yAxis
2675         .scale(y)
2676         .ticks( availableHeight / 36 )
2677         .tickSize( -availableWidth, 0);
2678
2679       d3.transition(g.select('.nv-y.nv-axis'))
2680           .call(yAxis);
2681
2682       //------------------------------------------------------------
2683
2684
2685       //============================================================
2686       // Event Handling/Dispatching (in chart's scope)
2687       //------------------------------------------------------------
2688
2689       dispatch.on('tooltipShow', function(e) {
2690         if (tooltips) showTooltip(e, that.parentNode);
2691       });
2692
2693       //============================================================
2694
2695
2696     });
2697
2698     return chart;
2699   }
2700
2701   //============================================================
2702   // Event Handling/Dispatching (out of chart's scope)
2703   //------------------------------------------------------------
2704
2705   discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
2706     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
2707     dispatch.tooltipShow(e);
2708   });
2709
2710   discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
2711     dispatch.tooltipHide(e);
2712   });
2713
2714   dispatch.on('tooltipHide', function() {
2715     if (tooltips) nv.tooltip.cleanup();
2716   });
2717
2718   //============================================================
2719
2720
2721   //============================================================
2722   // Expose Public Variables
2723   //------------------------------------------------------------
2724
2725   // expose chart's sub-components
2726   chart.dispatch = dispatch;
2727   chart.discretebar = discretebar;
2728   chart.xAxis = xAxis;
2729   chart.yAxis = yAxis;
2730
2731   d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
2732
2733   chart.margin = function(_) {
2734     if (!arguments.length) return margin;
2735     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2736     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2737     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2738     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2739     return chart;
2740   };
2741
2742   chart.width = function(_) {
2743     if (!arguments.length) return width;
2744     width = _;
2745     return chart;
2746   };
2747
2748   chart.height = function(_) {
2749     if (!arguments.length) return height;
2750     height = _;
2751     return chart;
2752   };
2753
2754   chart.color = function(_) {
2755     if (!arguments.length) return color;
2756     color = nv.utils.getColor(_);
2757     discretebar.color(color);
2758     return chart;
2759   };
2760
2761   chart.staggerLabels = function(_) {
2762     if (!arguments.length) return staggerLabels;
2763     staggerLabels = _;
2764     return chart;
2765   };
2766
2767   chart.tooltips = function(_) {
2768     if (!arguments.length) return tooltips;
2769     tooltips = _;
2770     return chart;
2771   };
2772
2773   chart.tooltipContent = function(_) {
2774     if (!arguments.length) return tooltip;
2775     tooltip = _;
2776     return chart;
2777   };
2778
2779   chart.noData = function(_) {
2780     if (!arguments.length) return noData;
2781     noData = _;
2782     return chart;
2783   };
2784
2785   //============================================================
2786
2787
2788   return chart;
2789 }
2790
2791 nv.models.distribution = function() {
2792
2793   //============================================================
2794   // Public Variables with Default Settings
2795   //------------------------------------------------------------
2796
2797   var margin = {top: 0, right: 0, bottom: 0, left: 0}
2798     , width = 400 //technically width or height depending on x or y....
2799     , size = 8
2800     , axis = 'x' // 'x' or 'y'... horizontal or vertical
2801     , getData = function(d) { return d[axis] }  // defaults d.x or d.y
2802     , color = nv.utils.defaultColor()
2803     , scale = d3.scale.linear()
2804     , domain
2805     ;
2806
2807   //============================================================
2808
2809
2810   //============================================================
2811   // Private Variables
2812   //------------------------------------------------------------
2813
2814   var scale0;
2815
2816   //============================================================
2817
2818
2819   function chart(selection) {
2820     selection.each(function(data) {
2821       var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
2822           naxis = axis == 'x' ? 'y' : 'x',
2823           container = d3.select(this);
2824
2825
2826       //------------------------------------------------------------
2827       // Setup Scales
2828
2829       scale0 = scale0 || scale;
2830
2831       //------------------------------------------------------------
2832
2833
2834       //------------------------------------------------------------
2835       // Setup containers and skeleton of chart
2836
2837       var wrap = container.selectAll('g.nv-distribution').data([data]);
2838       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
2839       var gEnter = wrapEnter.append('g');
2840       var g = wrap.select('g');
2841
2842       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
2843
2844       //------------------------------------------------------------
2845
2846
2847       var distWrap = g.selectAll('g.nv-dist')
2848           .data(function(d) { return d }, function(d) { return d.key });
2849
2850       distWrap.enter().append('g');
2851       distWrap
2852           .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
2853           .style('stroke', function(d,i) { return color(d, i) });
2854
2855       var dist = distWrap.selectAll('line.nv-dist' + axis)
2856           .data(function(d) { return d.values })
2857       dist.enter().append('line')
2858           .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
2859           .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
2860       d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis))
2861           .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
2862           .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
2863           .style('stroke-opacity', 0)
2864           .remove();
2865       dist
2866           .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
2867           .attr(naxis + '1', 0)
2868           .attr(naxis + '2', size);
2869       d3.transition(dist)
2870           .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
2871           .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
2872
2873
2874       scale0 = scale.copy();
2875
2876     });
2877
2878     return chart;
2879   }
2880
2881
2882   //============================================================
2883   // Expose Public Variables
2884   //------------------------------------------------------------
2885
2886   chart.margin = function(_) {
2887     if (!arguments.length) return margin;
2888     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
2889     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
2890     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2891     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
2892     return chart;
2893   };
2894
2895   chart.width = function(_) {
2896     if (!arguments.length) return width;
2897     width = _;
2898     return chart;
2899   };
2900
2901   chart.axis = function(_) {
2902     if (!arguments.length) return axis;
2903     axis = _;
2904     return chart;
2905   };
2906
2907   chart.size = function(_) {
2908     if (!arguments.length) return size;
2909     size = _;
2910     return chart;
2911   };
2912
2913   chart.getData = function(_) {
2914     if (!arguments.length) return getData;
2915     getData = d3.functor(_);
2916     return chart;
2917   };
2918
2919   chart.scale = function(_) {
2920     if (!arguments.length) return scale;
2921     scale = _;
2922     return chart;
2923   };
2924
2925   chart.color = function(_) {
2926     if (!arguments.length) return color;
2927     color = nv.utils.getColor(_);
2928     return chart;
2929   };
2930
2931   //============================================================
2932
2933
2934   return chart;
2935 }
2936 //TODO: consider deprecating and using multibar with single series for this
2937 nv.models.historicalBar = function() {
2938
2939   //============================================================
2940   // Public Variables with Default Settings
2941   //------------------------------------------------------------
2942
2943   var margin = {top: 0, right: 0, bottom: 0, left: 0}
2944     , width = 960
2945     , height = 500
2946     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2947     , x = d3.scale.linear()
2948     , y = d3.scale.linear()
2949     , getX = function(d) { return d.x }
2950     , getY = function(d) { return d.y }
2951     , forceX = []
2952     , forceY = [0]
2953     , padData = false
2954     , clipEdge = true
2955     , color = nv.utils.defaultColor()
2956     , xDomain
2957     , yDomain
2958     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2959     ;
2960
2961   //============================================================
2962
2963
2964   function chart(selection) {
2965     selection.each(function(data) {
2966       var availableWidth = width - margin.left - margin.right,
2967           availableHeight = height - margin.top - margin.bottom,
2968           container = d3.select(this);
2969
2970
2971       //------------------------------------------------------------
2972       // Setup Scales
2973
2974       x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
2975
2976       if (padData)
2977         x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
2978       else
2979         x.range([0, availableWidth]);
2980
2981       y   .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
2982           .range([availableHeight, 0]);
2983
2984       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
2985       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
2986       if (x.domain()[0] === x.domain()[1])
2987         x.domain()[0] ?
2988             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
2989           : x.domain([-1,1]);
2990
2991       if (y.domain()[0] === y.domain()[1])
2992         y.domain()[0] ?
2993             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
2994           : y.domain([-1,1]);
2995
2996       //------------------------------------------------------------
2997
2998
2999       //------------------------------------------------------------
3000       // Setup containers and skeleton of chart
3001
3002       var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]);
3003       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar');
3004       var defsEnter = wrapEnter.append('defs');
3005       var gEnter = wrapEnter.append('g');
3006       var g = wrap.select('g');
3007
3008       gEnter.append('g').attr('class', 'nv-bars');
3009
3010       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3011
3012       //------------------------------------------------------------
3013
3014
3015       container
3016           .on('click', function(d,i) {
3017             dispatch.chartClick({
3018                 data: d,
3019                 index: i,
3020                 pos: d3.event,
3021                 id: id
3022             });
3023           });
3024
3025
3026       defsEnter.append('clipPath')
3027           .attr('id', 'nv-chart-clip-path-' + id)
3028         .append('rect');
3029
3030       wrap.select('#nv-chart-clip-path-' + id + ' rect')
3031           .attr('width', availableWidth)
3032           .attr('height', availableHeight);
3033
3034       g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3035
3036
3037
3038       var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3039           .data(function(d) { return d });
3040
3041       bars.exit().remove();
3042
3043
3044       var barsEnter = bars.enter().append('rect')
3045           //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3046           .attr('x', 0 )
3047           .attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
3048           .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
3049           .on('mouseover', function(d,i) {
3050             d3.select(this).classed('hover', true);
3051             dispatch.elementMouseover({
3052                 point: d,
3053                 series: data[0],
3054                 pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
3055                 pointIndex: i,
3056                 seriesIndex: 0,
3057                 e: d3.event
3058             });
3059
3060           })
3061           .on('mouseout', function(d,i) {
3062                 d3.select(this).classed('hover', false);
3063                 dispatch.elementMouseout({
3064                     point: d,
3065                     series: data[0],
3066                     pointIndex: i,
3067                     seriesIndex: 0,
3068                     e: d3.event
3069                 });
3070           })
3071           .on('click', function(d,i) {
3072                 dispatch.elementClick({
3073                     //label: d[label],
3074                     value: getY(d,i),
3075                     data: d,
3076                     index: i,
3077                     pos: [x(getX(d,i)), y(getY(d,i))],
3078                     e: d3.event,
3079                     id: id
3080                 });
3081               d3.event.stopPropagation();
3082           })
3083           .on('dblclick', function(d,i) {
3084               dispatch.elementDblClick({
3085                   //label: d[label],
3086                   value: getY(d,i),
3087                   data: d,
3088                   index: i,
3089                   pos: [x(getX(d,i)), y(getY(d,i))],
3090                   e: d3.event,
3091                   id: id
3092               });
3093               d3.event.stopPropagation();
3094           });
3095
3096       bars
3097           .attr('fill', function(d,i) { return color(d, i); })
3098           .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3099           .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })  //TODO: better width calculations that don't assume always uniform data spacing;w
3100           .attr('width', (availableWidth / data[0].values.length) * .9 )
3101
3102
3103       d3.transition(bars)
3104           //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
3105           .attr('y', function(d,i) {
3106             return getY(d,i) < 0 ?
3107                     y(0) :
3108                     y(0) - y(getY(d,i)) < 1 ?
3109                       y(0) - 1 :
3110                       y(getY(d,i))
3111           })
3112           .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) });
3113           //.order();  // not sure if this makes any sense for this model
3114
3115     });
3116
3117     return chart;
3118   }
3119
3120
3121   //============================================================
3122   // Expose Public Variables
3123   //------------------------------------------------------------
3124
3125   chart.dispatch = dispatch;
3126
3127   chart.x = function(_) {
3128     if (!arguments.length) return getX;
3129     getX = _;
3130     return chart;
3131   };
3132
3133   chart.y = function(_) {
3134     if (!arguments.length) return getY;
3135     getY = _;
3136     return chart;
3137   };
3138
3139   chart.margin = function(_) {
3140     if (!arguments.length) return margin;
3141     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3142     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3143     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3144     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3145     return chart;
3146   };
3147
3148   chart.width = function(_) {
3149     if (!arguments.length) return width;
3150     width = _;
3151     return chart;
3152   };
3153
3154   chart.height = function(_) {
3155     if (!arguments.length) return height;
3156     height = _;
3157     return chart;
3158   };
3159
3160   chart.xScale = function(_) {
3161     if (!arguments.length) return x;
3162     x = _;
3163     return chart;
3164   };
3165
3166   chart.yScale = function(_) {
3167     if (!arguments.length) return y;
3168     y = _;
3169     return chart;
3170   };
3171
3172   chart.xDomain = function(_) {
3173     if (!arguments.length) return xDomain;
3174     xDomain = _;
3175     return chart;
3176   };
3177
3178   chart.yDomain = function(_) {
3179     if (!arguments.length) return yDomain;
3180     yDomain = _;
3181     return chart;
3182   };
3183
3184   chart.forceX = function(_) {
3185     if (!arguments.length) return forceX;
3186     forceX = _;
3187     return chart;
3188   };
3189
3190   chart.forceY = function(_) {
3191     if (!arguments.length) return forceY;
3192     forceY = _;
3193     return chart;
3194   };
3195
3196   chart.padData = function(_) {
3197     if (!arguments.length) return padData;
3198     padData = _;
3199     return chart;
3200   };
3201
3202   chart.clipEdge = function(_) {
3203     if (!arguments.length) return clipEdge;
3204     clipEdge = _;
3205     return chart;
3206   };
3207
3208   chart.color = function(_) {
3209     if (!arguments.length) return color;
3210     color = nv.utils.getColor(_);
3211     return chart;
3212   };
3213
3214   chart.id = function(_) {
3215     if (!arguments.length) return id;
3216     id = _;
3217     return chart;
3218   };
3219
3220   //============================================================
3221
3222
3223   return chart;
3224 }
3225
3226 nv.models.historicalBarChart = function() {
3227
3228   //============================================================
3229   // Public Variables with Default Settings
3230   //------------------------------------------------------------
3231
3232   var bars = nv.models.historicalBar()
3233     , xAxis = nv.models.axis()
3234     , yAxis = nv.models.axis()
3235     , legend = nv.models.legend()
3236     ;
3237
3238   var margin = {top: 30, right: 90, bottom: 50, left: 90}
3239     , color = nv.utils.defaultColor()
3240     , width = null
3241     , height = null
3242     , showLegend = false
3243     , showXAxis = true
3244     , showYAxis = true
3245     , rightAlignYAxis = false
3246     , tooltips = true
3247     , tooltip = function(key, x, y, e, graph) {
3248         return '<h3>' + key + '</h3>' +
3249                '<p>' +  y + ' at ' + x + '</p>'
3250       }
3251     , x
3252     , y
3253     , state = {}
3254     , defaultState = null
3255     , noData = 'No Data Available.'
3256     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
3257     ;
3258
3259   xAxis
3260     .orient('bottom')
3261     .tickPadding(7)
3262     ;
3263   yAxis
3264     .orient( (rightAlignYAxis) ? 'right' : 'left')
3265     ;
3266
3267   //============================================================
3268
3269
3270   //============================================================
3271   // Private Variables
3272   //------------------------------------------------------------
3273
3274   var showTooltip = function(e, offsetElement) {
3275
3276     // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
3277     if (offsetElement) {
3278       var svg = d3.select(offsetElement).select('svg');
3279       var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
3280       if (viewBox) {
3281         viewBox = viewBox.split(' ');
3282         var ratio = parseInt(svg.style('width')) / viewBox[2];
3283         e.pos[0] = e.pos[0] * ratio;
3284         e.pos[1] = e.pos[1] * ratio;
3285       }
3286     }
3287
3288     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3289         top = e.pos[1] + ( offsetElement.offsetTop || 0),
3290         x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
3291         y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
3292         content = tooltip(e.series.key, x, y, e, chart);
3293
3294     nv.tooltip.show([left, top], content, null, null, offsetElement);
3295   };
3296
3297   //============================================================
3298
3299
3300   function chart(selection) {
3301     selection.each(function(data) {
3302       var container = d3.select(this),
3303           that = this;
3304
3305       var availableWidth = (width  || parseInt(container.style('width')) || 960)
3306                              - margin.left - margin.right,
3307           availableHeight = (height || parseInt(container.style('height')) || 400)
3308                              - margin.top - margin.bottom;
3309
3310
3311       chart.update = function() { chart(selection) };
3312       chart.container = this;
3313
3314       //set state.disabled
3315       state.disabled = data.map(function(d) { return !!d.disabled });
3316
3317       if (!defaultState) {
3318         var key;
3319         defaultState = {};
3320         for (key in state) {
3321           if (state[key] instanceof Array)
3322             defaultState[key] = state[key].slice(0);
3323           else
3324             defaultState[key] = state[key];
3325         }
3326       }
3327
3328       //------------------------------------------------------------
3329       // Display noData message if there's nothing to show.
3330
3331       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3332         var noDataText = container.selectAll('.nv-noData').data([noData]);
3333
3334         noDataText.enter().append('text')
3335           .attr('class', 'nvd3 nv-noData')
3336           .attr('dy', '-.7em')
3337           .style('text-anchor', 'middle');
3338
3339         noDataText
3340           .attr('x', margin.left + availableWidth / 2)
3341           .attr('y', margin.top + availableHeight / 2)
3342           .text(function(d) { return d });
3343
3344         return chart;
3345       } else {
3346         container.selectAll('.nv-noData').remove();
3347       }
3348
3349       //------------------------------------------------------------
3350
3351
3352       //------------------------------------------------------------
3353       // Setup Scales
3354
3355       x = bars.xScale();
3356       y = bars.yScale();
3357
3358       //------------------------------------------------------------
3359
3360
3361       //------------------------------------------------------------
3362       // Setup containers and skeleton of chart
3363
3364       var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
3365       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
3366       var g = wrap.select('g');
3367
3368       gEnter.append('g').attr('class', 'nv-x nv-axis');
3369       gEnter.append('g').attr('class', 'nv-y nv-axis');
3370       gEnter.append('g').attr('class', 'nv-barsWrap');
3371       gEnter.append('g').attr('class', 'nv-legendWrap');
3372
3373       //------------------------------------------------------------
3374
3375
3376       //------------------------------------------------------------
3377       // Legend
3378
3379       if (showLegend) {
3380         legend.width(availableWidth);
3381
3382         g.select('.nv-legendWrap')
3383             .datum(data)
3384             .call(legend);
3385
3386         if ( margin.top != legend.height()) {
3387           margin.top = legend.height();
3388           availableHeight = (height || parseInt(container.style('height')) || 400)
3389                              - margin.top - margin.bottom;
3390         }
3391
3392         wrap.select('.nv-legendWrap')
3393             .attr('transform', 'translate(0,' + (-margin.top) +')')
3394       }
3395
3396       //------------------------------------------------------------
3397
3398       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3399
3400       if (rightAlignYAxis) {
3401         g.select(".nv-y.nv-axis")
3402             .attr("transform", "translate(" + availableWidth + ",0)");
3403       }
3404
3405       //------------------------------------------------------------
3406       // Main Chart Component(s)
3407
3408       bars
3409         .width(availableWidth)
3410         .height(availableHeight)
3411         .color(data.map(function(d,i) {
3412           return d.color || color(d, i);
3413         }).filter(function(d,i) { return !data[i].disabled }));
3414
3415
3416       var barsWrap = g.select('.nv-barsWrap')
3417           .datum(data.filter(function(d) { return !d.disabled }))
3418
3419       d3.transition(barsWrap).call(bars);
3420
3421       //------------------------------------------------------------
3422
3423
3424       //------------------------------------------------------------
3425       // Setup Axes
3426
3427       if (showXAxis) {
3428         xAxis
3429           .scale(x)
3430           .tickSize(-availableHeight, 0);
3431
3432         g.select('.nv-x.nv-axis')
3433             .attr('transform', 'translate(0,' + y.range()[0] + ')');
3434         g.select('.nv-x.nv-axis')
3435           .transition()
3436             .call(xAxis);
3437       }
3438
3439       if (showYAxis) {
3440         yAxis
3441           .scale(y)
3442           .ticks( availableHeight / 36 )
3443           .tickSize( -availableWidth, 0);
3444
3445         g.select('.nv-y.nv-axis')
3446           .transition().duration(0)
3447             .call(yAxis);
3448       }
3449       //------------------------------------------------------------
3450
3451
3452       //============================================================
3453       // Event Handling/Dispatching (in chart's scope)
3454       //------------------------------------------------------------
3455
3456       legend.dispatch.on('legendClick', function(d,i) { 
3457         d.disabled = !d.disabled;
3458
3459         if (!data.filter(function(d) { return !d.disabled }).length) {
3460           data.map(function(d) {
3461             d.disabled = false;
3462             wrap.selectAll('.nv-series').classed('disabled', false);
3463             return d;
3464           });
3465         }
3466
3467         state.disabled = data.map(function(d) { return !!d.disabled });
3468         dispatch.stateChange(state);
3469
3470         selection.transition().call(chart);
3471       });
3472
3473       legend.dispatch.on('legendDblclick', function(d) {
3474           //Double clicking should always enable current series, and disabled all others.
3475           data.forEach(function(d) {
3476              d.disabled = true;
3477           });
3478           d.disabled = false;  
3479
3480           state.disabled = data.map(function(d) { return !!d.disabled });
3481           dispatch.stateChange(state);
3482           chart.update();
3483       });
3484
3485
3486 /*
3487       legend.dispatch.on('legendMouseover', function(d, i) {
3488         d.hover = true;
3489         selection.transition().call(chart)
3490       });
3491
3492       legend.dispatch.on('legendMouseout', function(d, i) {
3493         d.hover = false;
3494         selection.transition().call(chart)
3495       });
3496 */
3497
3498       dispatch.on('tooltipShow', function(e) {
3499         if (tooltips) showTooltip(e, that.parentNode);
3500       });
3501
3502
3503       dispatch.on('changeState', function(e) {
3504
3505         if (typeof e.disabled !== 'undefined') {
3506           data.forEach(function(series,i) {
3507             series.disabled = e.disabled[i];
3508           });
3509
3510           state.disabled = e.disabled;
3511         }
3512
3513         selection.call(chart);
3514       });
3515
3516       //============================================================
3517
3518     });
3519
3520     return chart;
3521   }
3522
3523
3524   //============================================================
3525   // Event Handling/Dispatching (out of chart's scope)
3526   //------------------------------------------------------------
3527
3528   bars.dispatch.on('elementMouseover.tooltip', function(e) {
3529     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
3530     dispatch.tooltipShow(e);
3531   });
3532
3533   bars.dispatch.on('elementMouseout.tooltip', function(e) {
3534     dispatch.tooltipHide(e);
3535   });
3536
3537   dispatch.on('tooltipHide', function() {
3538     if (tooltips) nv.tooltip.cleanup();
3539   });
3540
3541   //============================================================
3542
3543
3544   //============================================================
3545   // Expose Public Variables
3546   //------------------------------------------------------------
3547
3548   // expose chart's sub-components
3549   chart.dispatch = dispatch;
3550   chart.bars = bars;
3551   chart.legend = legend;
3552   chart.xAxis = xAxis;
3553   chart.yAxis = yAxis;
3554
3555   d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate');
3556
3557   chart.margin = function(_) {
3558     if (!arguments.length) return margin;
3559     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3560     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3561     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3562     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3563     return chart;
3564   };
3565
3566   chart.width = function(_) {
3567     if (!arguments.length) return width;
3568     width = _;
3569     return chart;
3570   };
3571
3572   chart.height = function(_) {
3573     if (!arguments.length) return height;
3574     height = _;
3575     return chart;
3576   };
3577
3578   chart.color = function(_) {
3579     if (!arguments.length) return color;
3580     color = nv.utils.getColor(_);
3581     legend.color(color);
3582     return chart;
3583   };
3584
3585   chart.showLegend = function(_) {
3586     if (!arguments.length) return showLegend;
3587     showLegend = _;
3588     return chart;
3589   };
3590
3591   chart.showXAxis = function(_) {
3592     if (!arguments.length) return showXAxis;
3593     showXAxis = _;
3594     return chart;
3595   };
3596
3597   chart.showYAxis = function(_) {
3598     if (!arguments.length) return showYAxis;
3599     showYAxis = _;
3600     return chart;
3601   };
3602
3603   chart.rightAlignYAxis = function(_) {
3604     if(!arguments.length) return rightAlignYAxis;
3605     rightAlignYAxis = _;
3606     yAxis.orient( (_) ? 'right' : 'left');
3607     return chart;
3608   };
3609
3610   chart.tooltips = function(_) {
3611     if (!arguments.length) return tooltips;
3612     tooltips = _;
3613     return chart;
3614   };
3615
3616   chart.tooltipContent = function(_) {
3617     if (!arguments.length) return tooltip;
3618     tooltip = _;
3619     return chart;
3620   };
3621
3622   chart.state = function(_) {
3623     if (!arguments.length) return state;
3624     state = _;
3625     return chart;
3626   };
3627
3628   chart.defaultState = function(_) {
3629     if (!arguments.length) return defaultState;
3630     defaultState = _;
3631     return chart;
3632   };
3633
3634   chart.noData = function(_) {
3635     if (!arguments.length) return noData;
3636     noData = _;
3637     return chart;
3638   };
3639
3640   //============================================================
3641
3642
3643   return chart;
3644 }
3645 nv.models.indentedTree = function() {
3646
3647   //============================================================
3648   // Public Variables with Default Settings
3649   //------------------------------------------------------------
3650
3651   var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
3652     , width = 960
3653     , height = 500
3654     , color = nv.utils.defaultColor()
3655     , id = Math.floor(Math.random() * 10000)
3656     , header = true
3657     , filterZero = false
3658     , noData = "No Data Available."
3659     , childIndent = 20
3660     , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
3661     , tableClass = null
3662     , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
3663     , iconClose = 'images/grey-minus.png'
3664     , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
3665     ;
3666
3667   //============================================================
3668
3669   var idx = 0;
3670
3671   function chart(selection) {
3672     selection.each(function(data) {
3673       var depth = 1,
3674           container = d3.select(this);
3675
3676       var tree = d3.layout.tree()
3677           .children(function(d) { return d.values })
3678           .size([height, childIndent]); //Not sure if this is needed now that the result is HTML
3679
3680       chart.update = function() { container.transition().duration(600).call(chart) };
3681
3682
3683       //------------------------------------------------------------
3684       // Display No Data message if there's nothing to show.
3685       if (!data[0]) data[0] = {key: noData};
3686
3687       //------------------------------------------------------------
3688
3689
3690       var nodes = tree.nodes(data[0]);
3691
3692       // nodes.map(function(d) {
3693       //   d.id = i++;
3694       // })
3695
3696       //------------------------------------------------------------
3697       // Setup containers and skeleton of chart
3698
3699       var wrap = d3.select(this).selectAll('div').data([[nodes]]);
3700       var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
3701       var tableEnter = wrapEnter.append('table');
3702       var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
3703
3704       //------------------------------------------------------------
3705
3706
3707       if (header) {
3708         var thead = tableEnter.append('thead');
3709
3710         var theadRow1 = thead.append('tr');
3711
3712         columns.forEach(function(column) {
3713           theadRow1
3714             .append('th')
3715               .attr('width', column.width ? column.width : '10%')
3716               .style('text-align', column.type == 'numeric' ? 'right' : 'left')
3717             .append('span')
3718               .text(column.label);
3719         });
3720       }
3721
3722
3723       var tbody = table.selectAll('tbody')
3724                     .data(function(d) { return d });
3725       tbody.enter().append('tbody');
3726
3727
3728
3729       //compute max generations
3730       depth = d3.max(nodes, function(node) { return node.depth });
3731       tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
3732
3733
3734       // Update the nodes…
3735       var node = tbody.selectAll('tr')
3736           // .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)});
3737           .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) :  true; } )}, function(d,i) { return d.id || (d.id || ++idx)});
3738           //.style('display', 'table-row'); //TODO: see if this does anything
3739
3740       node.exit().remove();
3741
3742       node.select('img.nv-treeicon')
3743           .attr('src', icon)
3744           .classed('folded', folded);
3745
3746       var nodeEnter = node.enter().append('tr');
3747
3748
3749       columns.forEach(function(column, index) {
3750
3751         var nodeName = nodeEnter.append('td')
3752             .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
3753             .style('text-align', column.type == 'numeric' ? 'right' : 'left');
3754
3755
3756         if (index == 0) {
3757           nodeName.append('img')
3758               .classed('nv-treeicon', true)
3759               .classed('nv-folded', folded)
3760               .attr('src', icon)
3761               .style('width', '14px')
3762               .style('height', '14px')
3763               .style('padding', '0 1px')
3764               .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
3765               .on('click', click);
3766         }
3767
3768
3769         nodeName.append('span')
3770             .attr('class', d3.functor(column.classes) )
3771             .text(function(d) { return column.format ? column.format(d) :
3772                                         (d[column.key] || '-') });
3773
3774         if  (column.showCount) {
3775           nodeName.append('span')
3776               .attr('class', 'nv-childrenCount');
3777
3778           node.selectAll('span.nv-childrenCount').text(function(d) {
3779                 return ((d.values && d.values.length) || (d._values && d._values.length)) ?                                   //If this is a parent
3780                     '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) :  true; }).length)) //If children are in values check its children and filter
3781                     || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) :  true; }).length)     //Otherwise, do the same, but with the other name, _values...
3782                     || 0) + ')'                                                                                               //This is the catch-all in case there are no children after a filter
3783                     : ''                                                                                                     //If this is not a parent, just give an empty string
3784             });
3785         }
3786
3787         if (column.click)
3788           nodeName.select('span').on('click', column.click);
3789
3790       });
3791
3792       node
3793         .order()
3794         .on('click', function(d) { 
3795           dispatch.elementClick({
3796             row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
3797             data: d,
3798             pos: [d.x, d.y]
3799           });
3800         })
3801         .on('dblclick', function(d) { 
3802           dispatch.elementDblclick({
3803             row: this,
3804             data: d,
3805             pos: [d.x, d.y]
3806           });
3807         })
3808         .on('mouseover', function(d) { 
3809           dispatch.elementMouseover({
3810             row: this,
3811             data: d,
3812             pos: [d.x, d.y]
3813           });
3814         })
3815         .on('mouseout', function(d) { 
3816           dispatch.elementMouseout({
3817             row: this,
3818             data: d,
3819             pos: [d.x, d.y]
3820           });
3821         });
3822
3823
3824
3825
3826       // Toggle children on click.
3827       function click(d, _, unshift) {
3828         d3.event.stopPropagation();
3829
3830         if(d3.event.shiftKey && !unshift) {
3831           //If you shift-click, it'll toggle fold all the children, instead of itself
3832           d3.event.shiftKey = false;
3833           d.values && d.values.forEach(function(node){
3834             if (node.values || node._values) {
3835               click(node, 0, true);
3836             }
3837           });
3838           return true;
3839         }
3840         if(!hasChildren(d)) {
3841           //download file
3842           //window.location.href = d.url;
3843           return true;
3844         }
3845         if (d.values) {
3846           d._values = d.values;
3847           d.values = null;
3848         } else {
3849           d.values = d._values;
3850           d._values = null;
3851         }
3852         chart.update();
3853       }
3854
3855
3856       function icon(d) {
3857         return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
3858       }
3859
3860       function folded(d) {
3861         return (d._values && d._values.length);
3862       }
3863
3864       function hasChildren(d) {
3865         var values = d.values || d._values;
3866
3867         return (values && values.length);
3868       }
3869
3870
3871     });
3872
3873     return chart;
3874   }
3875
3876
3877   //============================================================
3878   // Expose Public Variables
3879   //------------------------------------------------------------
3880
3881   chart.margin = function(_) {
3882     if (!arguments.length) return margin;
3883     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
3884     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
3885     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3886     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
3887     return chart;
3888   };
3889
3890   chart.width = function(_) {
3891     if (!arguments.length) return width;
3892     width = _;
3893     return chart;
3894   };
3895
3896   chart.height = function(_) {
3897     if (!arguments.length) return height;
3898     height = _;
3899     return chart;
3900   };
3901
3902   chart.color = function(_) {
3903     if (!arguments.length) return color;
3904     color = nv.utils.getColor(_);
3905     scatter.color(color);
3906     return chart;
3907   };
3908
3909   chart.id = function(_) {
3910     if (!arguments.length) return id;
3911     id = _;
3912     return chart;
3913   };
3914
3915   chart.header = function(_) {
3916     if (!arguments.length) return header;
3917     header = _;
3918     return chart;
3919   };
3920
3921   chart.noData = function(_) {
3922     if (!arguments.length) return noData;
3923     noData = _;
3924     return chart;
3925   };
3926
3927   chart.filterZero = function(_) {
3928     if (!arguments.length) return filterZero;
3929     filterZero = _;
3930     return chart;
3931   };
3932
3933   chart.columns = function(_) {
3934     if (!arguments.length) return columns;
3935     columns = _;
3936     return chart;
3937   };
3938
3939   chart.tableClass = function(_) {
3940     if (!arguments.length) return tableClass;
3941     tableClass = _;
3942     return chart;
3943   };
3944
3945   chart.iconOpen = function(_){
3946      if (!arguments.length) return iconOpen;
3947     iconOpen = _;
3948     return chart;
3949   }
3950
3951   chart.iconClose = function(_){
3952      if (!arguments.length) return iconClose;
3953     iconClose = _;
3954     return chart;
3955   }
3956
3957   //============================================================
3958
3959
3960   return chart;
3961 };nv.models.legend = function() {
3962
3963   //============================================================
3964   // Public Variables with Default Settings
3965   //------------------------------------------------------------
3966
3967   var margin = {top: 5, right: 0, bottom: 5, left: 0}
3968     , width = 400
3969     , height = 20
3970     , getKey = function(d) { return d.key }
3971     , color = nv.utils.defaultColor()
3972     , align = true
3973     , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout')
3974     ;
3975
3976   //============================================================
3977
3978
3979   function chart(selection) {
3980     selection.each(function(data) {
3981       var availableWidth = width - margin.left - margin.right,
3982           container = d3.select(this);
3983
3984
3985       //------------------------------------------------------------
3986       // Setup containers and skeleton of chart
3987
3988       var wrap = container.selectAll('g.nv-legend').data([data]);
3989       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
3990       var g = wrap.select('g');
3991
3992       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3993
3994       //------------------------------------------------------------
3995
3996
3997       var series = g.selectAll('.nv-series')
3998           .data(function(d) { return d });
3999       var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4000           .on('mouseover', function(d,i) {
4001             dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
4002           })
4003           .on('mouseout', function(d,i) {
4004             dispatch.legendMouseout(d,i);
4005           })
4006           .on('click', function(d,i) {
4007             dispatch.legendClick(d,i);
4008           })
4009           .on('dblclick', function(d,i) {
4010             dispatch.legendDblclick(d,i);
4011           });
4012       seriesEnter.append('circle')
4013           .style('stroke-width', 2)
4014           .attr('r', 5);
4015       seriesEnter.append('text')
4016           .attr('text-anchor', 'start')
4017           .attr('dy', '.32em')
4018           .attr('dx', '8');
4019       series.classed('disabled', function(d) { return d.disabled });
4020       series.exit().remove();
4021       series.select('circle')
4022           .style('fill', function(d,i) { return d.color || color(d,i)})
4023           .style('stroke', function(d,i) { return d.color || color(d, i) });
4024       series.select('text').text(getKey);
4025
4026
4027       //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4028
4029       // NEW ALIGNING CODE, TODO: clean up
4030       if (align) {
4031
4032         var seriesWidths = [];
4033         series.each(function(d,i) {
4034               var legendText = d3.select(this).select('text');
4035               var svgComputedTextLength = legendText.node().getComputedTextLength() 
4036                                          || nv.utils.calcApproxTextWidth(legendText);
4037               seriesWidths.push(svgComputedTextLength + 28); // 28 is ~ the width of the circle plus some padding
4038             });
4039
4040         //nv.log('Series Widths: ', JSON.stringify(seriesWidths));
4041
4042         var seriesPerRow = 0;
4043         var legendWidth = 0;
4044         var columnWidths = [];
4045
4046         while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4047           columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4048           legendWidth += seriesWidths[seriesPerRow++];
4049         }
4050
4051
4052         while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4053           columnWidths = [];
4054           seriesPerRow--;
4055
4056           for (k = 0; k < seriesWidths.length; k++) {
4057             if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4058               columnWidths[k % seriesPerRow] = seriesWidths[k];
4059           }
4060
4061           legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4062                           return prev + cur;
4063                         });
4064         }
4065         //console.log(columnWidths, legendWidth, seriesPerRow);
4066
4067         var xPositions = [];
4068         for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4069             xPositions[i] = curX;
4070             curX += columnWidths[i];
4071         }
4072
4073         series
4074             .attr('transform', function(d, i) {
4075               return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4076             });
4077
4078         //position legend as far right as possible within the total width
4079         g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4080
4081         height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4082
4083       } else {
4084
4085         var ypos = 5,
4086             newxpos = 5,
4087             maxwidth = 0,
4088             xpos;
4089         series
4090             .attr('transform', function(d, i) {
4091               var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4092               xpos = newxpos;
4093
4094               if (width < margin.left + margin.right + xpos + length) {
4095                 newxpos = xpos = 5;
4096                 ypos += 20;
4097               }
4098
4099               newxpos += length;
4100               if (newxpos > maxwidth) maxwidth = newxpos;
4101
4102               return 'translate(' + xpos + ',' + ypos + ')';
4103             });
4104
4105         //position legend as far right as possible within the total width
4106         g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4107
4108         height = margin.top + margin.bottom + ypos + 15;
4109
4110       }
4111
4112     });
4113
4114     return chart;
4115   }
4116
4117
4118   //============================================================
4119   // Expose Public Variables
4120   //------------------------------------------------------------
4121
4122   chart.dispatch = dispatch;
4123
4124   chart.margin = function(_) {
4125     if (!arguments.length) return margin;
4126     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4127     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4128     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4129     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4130     return chart;
4131   };
4132
4133   chart.width = function(_) {
4134     if (!arguments.length) return width;
4135     width = _;
4136     return chart;
4137   };
4138
4139   chart.height = function(_) {
4140     if (!arguments.length) return height;
4141     height = _;
4142     return chart;
4143   };
4144
4145   chart.key = function(_) {
4146     if (!arguments.length) return getKey;
4147     getKey = _;
4148     return chart;
4149   };
4150
4151   chart.color = function(_) {
4152     if (!arguments.length) return color;
4153     color = nv.utils.getColor(_);
4154     return chart;
4155   };
4156
4157   chart.align = function(_) {
4158     if (!arguments.length) return align;
4159     align = _;
4160     return chart;
4161   };
4162
4163   //============================================================
4164
4165
4166   return chart;
4167 }
4168
4169 nv.models.line = function() {
4170
4171   //============================================================
4172   // Public Variables with Default Settings
4173   //------------------------------------------------------------
4174
4175   var  scatter = nv.models.scatter()
4176     ;
4177
4178   var margin = {top: 0, right: 0, bottom: 0, left: 0}
4179     , width = 960
4180     , height = 500
4181     , color = nv.utils.defaultColor() // a function that returns a color
4182     , getX = function(d) { return d.x } // accessor to get the x value from a data point
4183     , getY = function(d) { return d.y } // accessor to get the y value from a data point
4184     , 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
4185     , isArea = function(d) { return d.area } // decides if a line is an area or just a line
4186     , clipEdge = false // if true, masks lines within x and y scale
4187     , x //can be accessed via chart.xScale()
4188     , y //can be accessed via chart.yScale()
4189     , interpolate = "linear" // controls the line interpolation
4190     ;
4191
4192   scatter
4193     .size(16) // default size
4194     .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
4195     ;
4196     
4197   //============================================================
4198
4199
4200   //============================================================
4201   // Private Variables
4202   //------------------------------------------------------------
4203
4204   var x0, y0 //used to store previous scales
4205       ;
4206
4207   //============================================================
4208
4209
4210   function chart(selection) {
4211     selection.each(function(data) {
4212       var availableWidth = width - margin.left - margin.right,
4213           availableHeight = height - margin.top - margin.bottom,
4214           container = d3.select(this);
4215
4216       //------------------------------------------------------------
4217       // Setup Scales
4218
4219       x = scatter.xScale();
4220       y = scatter.yScale();
4221
4222       x0 = x0 || x;
4223       y0 = y0 || y;
4224
4225       //------------------------------------------------------------
4226
4227
4228       //------------------------------------------------------------
4229       // Setup containers and skeleton of chart
4230
4231       var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
4232       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
4233       var defsEnter = wrapEnter.append('defs');
4234       var gEnter = wrapEnter.append('g');
4235       var g = wrap.select('g')
4236
4237       gEnter.append('g').attr('class', 'nv-groups');
4238       gEnter.append('g').attr('class', 'nv-scatterWrap');
4239
4240       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4241
4242       //------------------------------------------------------------
4243
4244
4245
4246
4247       scatter
4248         .width(availableWidth)
4249         .height(availableHeight)
4250
4251       var scatterWrap = wrap.select('.nv-scatterWrap');
4252           //.datum(data); // Data automatically trickles down from the wrap
4253
4254       d3.transition(scatterWrap).call(scatter);
4255
4256
4257
4258       defsEnter.append('clipPath')
4259           .attr('id', 'nv-edge-clip-' + scatter.id())
4260         .append('rect');
4261
4262       wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
4263           .attr('width', availableWidth)
4264           .attr('height', availableHeight);
4265
4266       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4267       scatterWrap
4268           .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4269
4270
4271
4272
4273       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
4274           .data(function(d) { return d }, function(d) { return d.key });
4275       groups.enter().append('g')
4276           .style('stroke-opacity', 1e-6)
4277           .style('fill-opacity', 1e-6);
4278       d3.transition(groups.exit())
4279           .style('stroke-opacity', 1e-6)
4280           .style('fill-opacity', 1e-6)
4281           .remove();
4282       groups
4283           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
4284           .classed('hover', function(d) { return d.hover })
4285           .style('fill', function(d,i){ return color(d, i) })
4286           .style('stroke', function(d,i){ return color(d, i)});
4287       d3.transition(groups)
4288           .style('stroke-opacity', 1)
4289           .style('fill-opacity', .5);
4290
4291
4292
4293       var areaPaths = groups.selectAll('path.nv-area')
4294           .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
4295       areaPaths.enter().append('path')
4296           .attr('class', 'nv-area')
4297           .attr('d', function(d) {
4298             return d3.svg.area()
4299                 .interpolate(interpolate)
4300                 .defined(defined)
4301                 .x(function(d,i) { return x0(getX(d,i)) })
4302                 .y0(function(d,i) { return y0(getY(d,i)) })
4303                 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4304                 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4305                 .apply(this, [d.values])
4306           });
4307       d3.transition(groups.exit().selectAll('path.nv-area'))
4308           .attr('d', function(d) {
4309             return d3.svg.area()
4310                 .interpolate(interpolate)
4311                 .defined(defined)
4312                 .x(function(d,i) { return x(getX(d,i)) })
4313                 .y0(function(d,i) { return y(getY(d,i)) })
4314                 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4315                 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4316                 .apply(this, [d.values])
4317           });
4318       d3.transition(areaPaths)
4319           .attr('d', function(d) {
4320             return d3.svg.area()
4321                 .interpolate(interpolate)
4322                 .defined(defined)
4323                 .x(function(d,i) { return x(getX(d,i)) })
4324                 .y0(function(d,i) { return y(getY(d,i)) })
4325                 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4326                 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4327                 .apply(this, [d.values])
4328           });
4329
4330
4331
4332       var linePaths = groups.selectAll('path.nv-line')
4333           .data(function(d) { return [d.values] });
4334       linePaths.enter().append('path')
4335           .attr('class', 'nv-line')
4336           .attr('d',
4337             d3.svg.line()
4338               .interpolate(interpolate)
4339               .defined(defined)
4340               .x(function(d,i) { return x0(getX(d,i)) })
4341               .y(function(d,i) { return y0(getY(d,i)) })
4342           );
4343       d3.transition(groups.exit().selectAll('path.nv-line'))
4344           .attr('d',
4345             d3.svg.line()
4346               .interpolate(interpolate)
4347               .defined(defined)
4348               .x(function(d,i) { return x(getX(d,i)) })
4349               .y(function(d,i) { return y(getY(d,i)) })
4350           );
4351       d3.transition(linePaths)
4352           .attr('d',
4353             d3.svg.line()
4354               .interpolate(interpolate)
4355               .defined(defined)
4356               .x(function(d,i) { return x(getX(d,i)) })
4357               .y(function(d,i) { return y(getY(d,i)) })
4358           );
4359
4360
4361
4362       //store old scales for use in transitions on update
4363       x0 = x.copy();
4364       y0 = y.copy();
4365
4366     });
4367
4368     return chart;
4369   }
4370
4371
4372   //============================================================
4373   // Expose Public Variables
4374   //------------------------------------------------------------
4375
4376   chart.dispatch = scatter.dispatch;
4377   chart.scatter = scatter;
4378
4379   d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData');
4380
4381   chart.margin = function(_) {
4382     if (!arguments.length) return margin;
4383     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4384     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4385     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4386     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4387     return chart;
4388   };
4389
4390   chart.width = function(_) {
4391     if (!arguments.length) return width;
4392     width = _;
4393     return chart;
4394   };
4395
4396   chart.height = function(_) {
4397     if (!arguments.length) return height;
4398     height = _;
4399     return chart;
4400   };
4401
4402   chart.x = function(_) {
4403     if (!arguments.length) return getX;
4404     getX = _;
4405     scatter.x(_);
4406     return chart;
4407   };
4408
4409   chart.y = function(_) {
4410     if (!arguments.length) return getY;
4411     getY = _;
4412     scatter.y(_);
4413     return chart;
4414   };
4415
4416   chart.clipEdge = function(_) {
4417     if (!arguments.length) return clipEdge;
4418     clipEdge = _;
4419     return chart;
4420   };
4421
4422   chart.color = function(_) {
4423     if (!arguments.length) return color;
4424     color = nv.utils.getColor(_);
4425     scatter.color(color);
4426     return chart;
4427   };
4428
4429   chart.interpolate = function(_) {
4430     if (!arguments.length) return interpolate;
4431     interpolate = _;
4432     return chart;
4433   };
4434
4435   chart.defined = function(_) {
4436     if (!arguments.length) return defined;
4437     defined = _;
4438     return chart;
4439   };
4440
4441   chart.isArea = function(_) {
4442     if (!arguments.length) return isArea;
4443     isArea = d3.functor(_);
4444     return chart;
4445   };
4446
4447   //============================================================
4448
4449
4450   return chart;
4451 }
4452
4453 nv.models.lineChart = function() {
4454
4455   //============================================================
4456   // Public Variables with Default Settings
4457   //------------------------------------------------------------
4458
4459   var lines = nv.models.line()
4460     , xAxis = nv.models.axis()
4461     , yAxis = nv.models.axis()
4462     , legend = nv.models.legend()
4463     ;
4464
4465 //set margin.right to 23 to fit dates on the x-axis within the chart
4466   var margin = {top: 30, right: 20, bottom: 50, left: 60}
4467     , color = nv.utils.defaultColor()
4468     , width = null
4469     , height = null
4470     , showLegend = true
4471     , showXAxis = true
4472     , showYAxis = true
4473     , rightAlignYAxis = false
4474     , tooltips = true
4475     , tooltip = function(key, x, y, e, graph) {
4476         return '<h3>' + key + '</h3>' +
4477                '<p>' +  y + ' at ' + x + '</p>'
4478       }
4479     , x
4480     , y
4481     , state = {}
4482     , defaultState = null
4483     , noData = 'No Data Available.'
4484     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4485     ;
4486
4487   xAxis
4488     .orient('bottom')
4489     .tickPadding(7)
4490     ;
4491   yAxis
4492     .orient((rightAlignYAxis) ? 'right' : 'left')
4493     ;
4494
4495   //============================================================
4496
4497
4498   //============================================================
4499   // Private Variables
4500   //------------------------------------------------------------
4501
4502   var showTooltip = function(e, offsetElement) {
4503
4504     // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4505     if (offsetElement) {
4506       var svg = d3.select(offsetElement).select('svg');
4507       var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4508       if (viewBox) {
4509         viewBox = viewBox.split(' ');
4510         var ratio = parseInt(svg.style('width')) / viewBox[2];
4511         e.pos[0] = e.pos[0] * ratio;
4512         e.pos[1] = e.pos[1] * ratio;
4513       }
4514     }
4515
4516     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4517         top = e.pos[1] + ( offsetElement.offsetTop || 0),
4518         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
4519         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
4520         content = tooltip(e.series.key, x, y, e, chart);
4521
4522     nv.tooltip.show([left, top], content, null, null, offsetElement);
4523   };
4524
4525   //============================================================
4526
4527
4528   function chart(selection) {
4529     selection.each(function(data) {
4530       var container = d3.select(this),
4531           that = this;
4532
4533       var availableWidth = (width  || parseInt(container.style('width')) || 960)
4534                              - margin.left - margin.right,
4535           availableHeight = (height || parseInt(container.style('height')) || 400)
4536                              - margin.top - margin.bottom;
4537
4538
4539       chart.update = function() { container.transition().call(chart) };
4540       chart.container = this;
4541
4542       //set state.disabled
4543       state.disabled = data.map(function(d) { return !!d.disabled });
4544
4545       if (!defaultState) {
4546         var key;
4547         defaultState = {};
4548         for (key in state) {
4549           if (state[key] instanceof Array)
4550             defaultState[key] = state[key].slice(0);
4551           else
4552             defaultState[key] = state[key];
4553         }
4554       }
4555
4556       //------------------------------------------------------------
4557       // Display noData message if there's nothing to show.
4558
4559       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4560         var noDataText = container.selectAll('.nv-noData').data([noData]);
4561
4562         noDataText.enter().append('text')
4563           .attr('class', 'nvd3 nv-noData')
4564           .attr('dy', '-.7em')
4565           .style('text-anchor', 'middle');
4566
4567         noDataText
4568           .attr('x', margin.left + availableWidth / 2)
4569           .attr('y', margin.top + availableHeight / 2)
4570           .text(function(d) { return d });
4571
4572         return chart;
4573       } else {
4574         container.selectAll('.nv-noData').remove();
4575       }
4576
4577       //------------------------------------------------------------
4578
4579
4580       //------------------------------------------------------------
4581       // Setup Scales
4582
4583       x = lines.xScale();
4584       y = lines.yScale();
4585
4586       //------------------------------------------------------------
4587
4588
4589       //------------------------------------------------------------
4590       // Setup containers and skeleton of chart
4591
4592       var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
4593       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
4594       var g = wrap.select('g');
4595
4596       gEnter.append('g').attr('class', 'nv-x nv-axis');
4597       gEnter.append('g').attr('class', 'nv-y nv-axis');
4598       gEnter.append('g').attr('class', 'nv-linesWrap');
4599       gEnter.append('g').attr('class', 'nv-legendWrap');
4600
4601       //------------------------------------------------------------
4602
4603
4604       //------------------------------------------------------------
4605       // Legend
4606
4607       if (showLegend) {
4608         legend.width(availableWidth);
4609
4610         g.select('.nv-legendWrap')
4611             .datum(data)
4612             .call(legend);
4613
4614         if ( margin.top != legend.height()) {
4615           margin.top = legend.height();
4616           availableHeight = (height || parseInt(container.style('height')) || 400)
4617                              - margin.top - margin.bottom;
4618         }
4619
4620         wrap.select('.nv-legendWrap')
4621             .attr('transform', 'translate(0,' + (-margin.top) +')')
4622       }
4623
4624       //------------------------------------------------------------
4625
4626       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4627
4628       if (rightAlignYAxis) {
4629           g.select(".nv-y.nv-axis")
4630               .attr("transform", "translate(" + availableWidth + ",0)");
4631       }
4632
4633       //------------------------------------------------------------
4634       // Main Chart Component(s)
4635
4636       lines
4637         .width(availableWidth)
4638         .height(availableHeight)
4639         .color(data.map(function(d,i) {
4640           return d.color || color(d, i);
4641         }).filter(function(d,i) { return !data[i].disabled }));
4642
4643
4644       var linesWrap = g.select('.nv-linesWrap')
4645           .datum(data.filter(function(d) { return !d.disabled }))
4646
4647       d3.transition(linesWrap).call(lines);
4648
4649       //------------------------------------------------------------
4650
4651
4652       //------------------------------------------------------------
4653       // Setup Axes
4654
4655       if (showXAxis) {
4656         xAxis
4657           .scale(x)
4658           .ticks( availableWidth / 100 )
4659           .tickSize(-availableHeight, 0);
4660
4661         g.select('.nv-x.nv-axis')
4662             .attr('transform', 'translate(0,' + y.range()[0] + ')');
4663         d3.transition(g.select('.nv-x.nv-axis'))
4664             .call(xAxis);
4665       }
4666
4667       if (showYAxis) {
4668         yAxis
4669           .scale(y)
4670           .ticks( availableHeight / 36 )
4671           .tickSize( -availableWidth, 0);
4672
4673         d3.transition(g.select('.nv-y.nv-axis'))
4674             .call(yAxis);
4675       }
4676       //------------------------------------------------------------
4677
4678
4679       //============================================================
4680       // Event Handling/Dispatching (in chart's scope)
4681       //------------------------------------------------------------
4682
4683       legend.dispatch.on('legendClick', function(d,i) { 
4684         d.disabled = !d.disabled;
4685
4686         if (!data.filter(function(d) { return !d.disabled }).length) {
4687           data.map(function(d) {
4688             d.disabled = false;
4689             wrap.selectAll('.nv-series').classed('disabled', false);
4690             return d;
4691           });
4692         }
4693
4694         state.disabled = data.map(function(d) { return !!d.disabled });
4695         dispatch.stateChange(state);
4696
4697         // container.transition().call(chart);
4698         chart.update();
4699       });
4700
4701       legend.dispatch.on('legendDblclick', function(d) {
4702           //Double clicking should always enable current series, and disabled all others.
4703           data.forEach(function(d) {
4704              d.disabled = true;
4705           });
4706           d.disabled = false;  
4707
4708           state.disabled = data.map(function(d) { return !!d.disabled });
4709           dispatch.stateChange(state);
4710           chart.update();
4711       });
4712
4713
4714 /*
4715       legend.dispatch.on('legendMouseover', function(d, i) {
4716         d.hover = true;
4717         selection.transition().call(chart)
4718       });
4719
4720       legend.dispatch.on('legendMouseout', function(d, i) {
4721         d.hover = false;
4722         selection.transition().call(chart)
4723       });
4724 */
4725
4726       dispatch.on('tooltipShow', function(e) {
4727         if (tooltips) showTooltip(e, that.parentNode);
4728       });
4729
4730
4731       dispatch.on('changeState', function(e) {
4732
4733         if (typeof e.disabled !== 'undefined') {
4734           data.forEach(function(series,i) {
4735             series.disabled = e.disabled[i];
4736           });
4737
4738           state.disabled = e.disabled;
4739         }
4740
4741         chart.update();
4742       });
4743
4744       //============================================================
4745
4746     });
4747
4748     return chart;
4749   }
4750
4751
4752   //============================================================
4753   // Event Handling/Dispatching (out of chart's scope)
4754   //------------------------------------------------------------
4755
4756   lines.dispatch.on('elementMouseover.tooltip', function(e) {
4757     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
4758     dispatch.tooltipShow(e);
4759   });
4760
4761   lines.dispatch.on('elementMouseout.tooltip', function(e) {
4762     dispatch.tooltipHide(e);
4763   });
4764
4765   dispatch.on('tooltipHide', function() {
4766     if (tooltips) nv.tooltip.cleanup();
4767   });
4768
4769   //============================================================
4770
4771
4772   //============================================================
4773   // Expose Public Variables
4774   //------------------------------------------------------------
4775
4776   // expose chart's sub-components
4777   chart.dispatch = dispatch;
4778   chart.lines = lines;
4779   chart.legend = legend;
4780   chart.xAxis = xAxis;
4781   chart.yAxis = yAxis;
4782
4783   d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate');
4784
4785   chart.margin = function(_) {
4786     if (!arguments.length) return margin;
4787     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
4788     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
4789     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4790     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
4791     return chart;
4792   };
4793
4794   chart.width = function(_) {
4795     if (!arguments.length) return width;
4796     width = _;
4797     return chart;
4798   };
4799
4800   chart.height = function(_) {
4801     if (!arguments.length) return height;
4802     height = _;
4803     return chart;
4804   };
4805
4806   chart.color = function(_) {
4807     if (!arguments.length) return color;
4808     color = nv.utils.getColor(_);
4809     legend.color(color);
4810     return chart;
4811   };
4812
4813   chart.showLegend = function(_) {
4814     if (!arguments.length) return showLegend;
4815     showLegend = _;
4816     return chart;
4817   };
4818
4819   chart.showXAxis = function(_) {
4820     if (!arguments.length) return showXAxis;
4821     showXAxis = _;
4822     return chart;
4823   };
4824
4825   chart.showYAxis = function(_) {
4826     if (!arguments.length) return showYAxis;
4827     showYAxis = _;
4828     return chart;
4829   };
4830
4831   chart.rightAlignYAxis = function(_) {
4832     if(!arguments.length) return rightAlignYAxis;
4833     rightAlignYAxis = _;
4834     yAxis.orient( (_) ? 'right' : 'left');
4835     return chart;
4836   };
4837
4838   chart.tooltips = function(_) {
4839     if (!arguments.length) return tooltips;
4840     tooltips = _;
4841     return chart;
4842   };
4843
4844   chart.tooltipContent = function(_) {
4845     if (!arguments.length) return tooltip;
4846     tooltip = _;
4847     return chart;
4848   };
4849
4850   chart.state = function(_) {
4851     if (!arguments.length) return state;
4852     state = _;
4853     return chart;
4854   };
4855
4856   chart.defaultState = function(_) {
4857     if (!arguments.length) return defaultState;
4858     defaultState = _;
4859     return chart;
4860   };
4861
4862   chart.noData = function(_) {
4863     if (!arguments.length) return noData;
4864     noData = _;
4865     return chart;
4866   };
4867
4868   //============================================================
4869
4870
4871   return chart;
4872 }
4873
4874 nv.models.linePlusBarChart = function() {
4875
4876   //============================================================
4877   // Public Variables with Default Settings
4878   //------------------------------------------------------------
4879
4880   var lines = nv.models.line()
4881     , bars = nv.models.historicalBar()
4882     , xAxis = nv.models.axis()
4883     , y1Axis = nv.models.axis()
4884     , y2Axis = nv.models.axis()
4885     , legend = nv.models.legend()
4886     ;
4887
4888   var margin = {top: 30, right: 60, bottom: 50, left: 60}
4889     , width = null
4890     , height = null
4891     , getX = function(d) { return d.x }
4892     , getY = function(d) { return d.y }
4893     , color = nv.utils.defaultColor()
4894     , showLegend = true
4895     , tooltips = true
4896     , tooltip = function(key, x, y, e, graph) {
4897         return '<h3>' + key + '</h3>' +
4898                '<p>' +  y + ' at ' + x + '</p>';
4899       }
4900     , x
4901     , y1
4902     , y2
4903     , state = {}
4904     , defaultState = null
4905     , noData = "No Data Available."
4906     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4907     ;
4908
4909   bars
4910     .padData(true)
4911     ;
4912   lines
4913     .clipEdge(false)
4914     .padData(true)
4915     ;
4916   xAxis
4917     .orient('bottom')
4918     .tickPadding(7)
4919     .highlightZero(false)
4920     ;
4921   y1Axis
4922     .orient('left')
4923     ;
4924   y2Axis
4925     .orient('right')
4926     ;
4927
4928   //============================================================
4929
4930
4931   //============================================================
4932   // Private Variables
4933   //------------------------------------------------------------
4934
4935   var showTooltip = function(e, offsetElement) {
4936       var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4937           top = e.pos[1] + ( offsetElement.offsetTop || 0),
4938           x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
4939           y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
4940           content = tooltip(e.series.key, x, y, e, chart);
4941
4942       nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
4943     }
4944     ;
4945
4946   //------------------------------------------------------------
4947
4948
4949
4950   function chart(selection) {
4951     selection.each(function(data) {
4952       var container = d3.select(this),
4953           that = this;
4954
4955       var availableWidth = (width  || parseInt(container.style('width')) || 960)
4956                              - margin.left - margin.right,
4957           availableHeight = (height || parseInt(container.style('height')) || 400)
4958                              - margin.top - margin.bottom;
4959
4960       chart.update = function() { container.transition().call(chart); };
4961       // chart.container = this;
4962
4963       //set state.disabled
4964       state.disabled = data.map(function(d) { return !!d.disabled });
4965
4966       if (!defaultState) {
4967         var key;
4968         defaultState = {};
4969         for (key in state) {
4970           if (state[key] instanceof Array)
4971             defaultState[key] = state[key].slice(0);
4972           else
4973             defaultState[key] = state[key];
4974         }
4975       }
4976
4977       //------------------------------------------------------------
4978       // Display No Data message if there's nothing to show.
4979
4980       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4981         var noDataText = container.selectAll('.nv-noData').data([noData]);
4982
4983         noDataText.enter().append('text')
4984           .attr('class', 'nvd3 nv-noData')
4985           .attr('dy', '-.7em')
4986           .style('text-anchor', 'middle');
4987
4988         noDataText
4989           .attr('x', margin.left + availableWidth / 2)
4990           .attr('y', margin.top + availableHeight / 2)
4991           .text(function(d) { return d });
4992
4993         return chart;
4994       } else {
4995         container.selectAll('.nv-noData').remove();
4996       }
4997
4998       //------------------------------------------------------------
4999
5000
5001       //------------------------------------------------------------
5002       // Setup Scales
5003
5004       var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5005       var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5006
5007       //x = xAxis.scale();
5008        x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
5009       //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
5010       y1 = bars.yScale();
5011       y2 = lines.yScale();
5012
5013       //------------------------------------------------------------
5014
5015       //------------------------------------------------------------
5016       // Setup containers and skeleton of chart
5017
5018       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5019       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5020       var g = wrap.select('g');
5021
5022       gEnter.append('g').attr('class', 'nv-x nv-axis');
5023       gEnter.append('g').attr('class', 'nv-y1 nv-axis');
5024       gEnter.append('g').attr('class', 'nv-y2 nv-axis');
5025       gEnter.append('g').attr('class', 'nv-barsWrap');
5026       gEnter.append('g').attr('class', 'nv-linesWrap');
5027       gEnter.append('g').attr('class', 'nv-legendWrap');
5028
5029       //------------------------------------------------------------
5030
5031
5032       //------------------------------------------------------------
5033       // Legend
5034
5035       if (showLegend) {
5036         legend.width( availableWidth / 2 );
5037
5038         g.select('.nv-legendWrap')
5039             .datum(data.map(function(series) {
5040               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5041               series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5042               return series;
5043             }))
5044           .call(legend);
5045
5046         if ( margin.top != legend.height()) {
5047           margin.top = legend.height();
5048           availableHeight = (height || parseInt(container.style('height')) || 400)
5049                              - margin.top - margin.bottom;
5050         }
5051
5052         g.select('.nv-legendWrap')
5053             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5054       }
5055
5056       //------------------------------------------------------------
5057
5058
5059       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5060
5061
5062       //------------------------------------------------------------
5063       // Main Chart Component(s)
5064
5065
5066       lines
5067         .width(availableWidth)
5068         .height(availableHeight)
5069         .color(data.map(function(d,i) {
5070           return d.color || color(d, i);
5071         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
5072
5073       bars
5074         .width(availableWidth)
5075         .height(availableHeight)
5076         .color(data.map(function(d,i) {
5077           return d.color || color(d, i);
5078         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
5079
5080
5081
5082       var barsWrap = g.select('.nv-barsWrap')
5083           .datum(dataBars.length ? dataBars : [{values:[]}])
5084
5085       var linesWrap = g.select('.nv-linesWrap')
5086           .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
5087           //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
5088
5089       d3.transition(barsWrap).call(bars);
5090       d3.transition(linesWrap).call(lines);
5091
5092       //------------------------------------------------------------
5093
5094
5095       //------------------------------------------------------------
5096       // Setup Axes
5097
5098       xAxis
5099         .scale(x)
5100         .ticks( availableWidth / 100 )
5101         .tickSize(-availableHeight, 0);
5102
5103       g.select('.nv-x.nv-axis')
5104           .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5105       d3.transition(g.select('.nv-x.nv-axis'))
5106           .call(xAxis);
5107
5108
5109       y1Axis
5110         .scale(y1)
5111         .ticks( availableHeight / 36 )
5112         .tickSize(-availableWidth, 0);
5113
5114       d3.transition(g.select('.nv-y1.nv-axis'))
5115           .style('opacity', dataBars.length ? 1 : 0)
5116           .call(y1Axis);
5117
5118
5119       y2Axis
5120         .scale(y2)
5121         .ticks( availableHeight / 36 )
5122         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
5123
5124       g.select('.nv-y2.nv-axis')
5125           .style('opacity', dataLines.length ? 1 : 0)
5126           .attr('transform', 'translate(' + availableWidth + ',0)');
5127           //.attr('transform', 'translate(' + x.range()[1] + ',0)');
5128
5129       d3.transition(g.select('.nv-y2.nv-axis'))
5130           .call(y2Axis);
5131
5132       //------------------------------------------------------------
5133
5134
5135       //============================================================
5136       // Event Handling/Dispatching (in chart's scope)
5137       //------------------------------------------------------------
5138
5139       legend.dispatch.on('legendClick', function(d,i) { 
5140         d.disabled = !d.disabled;
5141
5142         if (!data.filter(function(d) { return !d.disabled }).length) {
5143           data.map(function(d) {
5144             d.disabled = false;
5145             wrap.selectAll('.nv-series').classed('disabled', false);
5146             return d;
5147           });
5148         }
5149
5150         state.disabled = data.map(function(d) { return !!d.disabled });
5151         dispatch.stateChange(state);
5152
5153         chart.update();
5154       });
5155
5156       legend.dispatch.on('legendDblclick', function(d) {
5157           //Double clicking should always enable current series, and disabled all others.
5158           data.forEach(function(d) {
5159              d.disabled = true;
5160           });
5161           d.disabled = false;  
5162
5163           state.disabled = data.map(function(d) { return !!d.disabled });
5164           dispatch.stateChange(state);
5165           chart.update();
5166       });
5167
5168
5169       dispatch.on('tooltipShow', function(e) {
5170         if (tooltips) showTooltip(e, that.parentNode);
5171       });
5172
5173
5174       // Update chart from a state object passed to event handler
5175       dispatch.on('changeState', function(e) {
5176
5177         if (typeof e.disabled !== 'undefined') {
5178           data.forEach(function(series,i) {
5179             series.disabled = e.disabled[i];
5180           });
5181
5182           state.disabled = e.disabled;
5183         }
5184
5185         chart.update();
5186       });
5187
5188       //============================================================
5189
5190
5191     });
5192
5193     return chart;
5194   }
5195
5196
5197   //============================================================
5198   // Event Handling/Dispatching (out of chart's scope)
5199   //------------------------------------------------------------
5200
5201   lines.dispatch.on('elementMouseover.tooltip', function(e) {
5202     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5203     dispatch.tooltipShow(e);
5204   });
5205
5206   lines.dispatch.on('elementMouseout.tooltip', function(e) {
5207     dispatch.tooltipHide(e);
5208   });
5209
5210   bars.dispatch.on('elementMouseover.tooltip', function(e) {
5211     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5212     dispatch.tooltipShow(e);
5213   });
5214
5215   bars.dispatch.on('elementMouseout.tooltip', function(e) {
5216     dispatch.tooltipHide(e);
5217   });
5218
5219   dispatch.on('tooltipHide', function() {
5220     if (tooltips) nv.tooltip.cleanup();
5221   });
5222
5223   //============================================================
5224
5225
5226   //============================================================
5227   // Expose Public Variables
5228   //------------------------------------------------------------
5229
5230   // expose chart's sub-components
5231   chart.dispatch = dispatch;
5232   chart.legend = legend;
5233   chart.lines = lines;
5234   chart.bars = bars;
5235   chart.xAxis = xAxis;
5236   chart.y1Axis = y1Axis;
5237   chart.y2Axis = y2Axis;
5238
5239   d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
5240   //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
5241   //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
5242
5243   chart.x = function(_) {
5244     if (!arguments.length) return getX;
5245     getX = _;
5246     lines.x(_);
5247     bars.x(_);
5248     return chart;
5249   };
5250
5251   chart.y = function(_) {
5252     if (!arguments.length) return getY;
5253     getY = _;
5254     lines.y(_);
5255     bars.y(_);
5256     return chart;
5257   };
5258
5259   chart.margin = function(_) {
5260     if (!arguments.length) return margin;
5261     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5262     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5263     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5264     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5265     return chart;
5266   };
5267
5268   chart.width = function(_) {
5269     if (!arguments.length) return width;
5270     width = _;
5271     return chart;
5272   };
5273
5274   chart.height = function(_) {
5275     if (!arguments.length) return height;
5276     height = _;
5277     return chart;
5278   };
5279
5280   chart.color = function(_) {
5281     if (!arguments.length) return color;
5282     color = nv.utils.getColor(_);
5283     legend.color(color);
5284     return chart;
5285   };
5286
5287   chart.showLegend = function(_) {
5288     if (!arguments.length) return showLegend;
5289     showLegend = _;
5290     return chart;
5291   };
5292
5293   chart.tooltips = function(_) {
5294     if (!arguments.length) return tooltips;
5295     tooltips = _;
5296     return chart;
5297   };
5298
5299   chart.tooltipContent = function(_) {
5300     if (!arguments.length) return tooltip;
5301     tooltip = _;
5302     return chart;
5303   };
5304
5305   chart.state = function(_) {
5306     if (!arguments.length) return state;
5307     state = _;
5308     return chart;
5309   };
5310
5311   chart.defaultState = function(_) {
5312     if (!arguments.length) return defaultState;
5313     defaultState = _;
5314     return chart;
5315   };
5316
5317   chart.noData = function(_) {
5318     if (!arguments.length) return noData;
5319     noData = _;
5320     return chart;
5321   };
5322
5323   //============================================================
5324
5325
5326   return chart;
5327 }
5328
5329 nv.models.lineWithFocusChart = function() {
5330
5331   //============================================================
5332   // Public Variables with Default Settings
5333   //------------------------------------------------------------
5334
5335   var lines = nv.models.line()
5336     , lines2 = nv.models.line()
5337     , xAxis = nv.models.axis()
5338     , yAxis = nv.models.axis()
5339     , x2Axis = nv.models.axis()
5340     , y2Axis = nv.models.axis()
5341     , legend = nv.models.legend()
5342     , brush = d3.svg.brush()
5343     ;
5344
5345   var margin = {top: 30, right: 30, bottom: 30, left: 60}
5346     , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5347     , color = nv.utils.defaultColor()
5348     , width = null
5349     , height = null
5350     , height2 = 100
5351     , x
5352     , y
5353     , x2
5354     , y2
5355     , showLegend = true
5356     , brushExtent = null
5357     , tooltips = true
5358     , tooltip = function(key, x, y, e, graph) {
5359         return '<h3>' + key + '</h3>' +
5360                '<p>' +  y + ' at ' + x + '</p>'
5361       }
5362     , noData = "No Data Available."
5363     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
5364     ;
5365
5366   lines
5367     .clipEdge(true)
5368     ;
5369   lines2
5370     .interactive(false)
5371     ;
5372   xAxis
5373     .orient('bottom')
5374     .tickPadding(5)
5375     ;
5376   yAxis
5377     .orient('left')
5378     ;
5379   x2Axis
5380     .orient('bottom')
5381     .tickPadding(5)
5382     ;
5383   y2Axis
5384     .orient('left')
5385     ;
5386   //============================================================
5387
5388
5389   //============================================================
5390   // Private Variables
5391   //------------------------------------------------------------
5392
5393   var showTooltip = function(e, offsetElement) {
5394     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5395         top = e.pos[1] + ( offsetElement.offsetTop || 0),
5396         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5397         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5398         content = tooltip(e.series.key, x, y, e, chart);
5399
5400     nv.tooltip.show([left, top], content, null, null, offsetElement);
5401   };
5402
5403   //============================================================
5404
5405
5406   function chart(selection) {
5407     selection.each(function(data) {
5408       var container = d3.select(this),
5409           that = this;
5410
5411       var availableWidth = (width  || parseInt(container.style('width')) || 960)
5412                              - margin.left - margin.right,
5413           availableHeight1 = (height || parseInt(container.style('height')) || 400)
5414                              - margin.top - margin.bottom - height2,
5415           availableHeight2 = height2 - margin2.top - margin2.bottom;
5416
5417       chart.update = function() { container.transition().call(chart) };
5418       chart.container = this;
5419
5420
5421       //------------------------------------------------------------
5422       // Display No Data message if there's nothing to show.
5423
5424       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5425         var noDataText = container.selectAll('.nv-noData').data([noData]);
5426
5427         noDataText.enter().append('text')
5428           .attr('class', 'nvd3 nv-noData')
5429           .attr('dy', '-.7em')
5430           .style('text-anchor', 'middle');
5431
5432         noDataText
5433           .attr('x', margin.left + availableWidth / 2)
5434           .attr('y', margin.top + availableHeight1 / 2)
5435           .text(function(d) { return d });
5436
5437         return chart;
5438       } else {
5439         container.selectAll('.nv-noData').remove();
5440       }
5441
5442       //------------------------------------------------------------
5443
5444
5445       //------------------------------------------------------------
5446       // Setup Scales
5447
5448       x = lines.xScale();
5449       y = lines.yScale();
5450       x2 = lines2.xScale();
5451       y2 = lines2.yScale();
5452
5453       //------------------------------------------------------------
5454
5455
5456       //------------------------------------------------------------
5457       // Setup containers and skeleton of chart
5458
5459       var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
5460       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
5461       var g = wrap.select('g');
5462
5463       gEnter.append('g').attr('class', 'nv-legendWrap');
5464
5465       var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
5466       focusEnter.append('g').attr('class', 'nv-x nv-axis');
5467       focusEnter.append('g').attr('class', 'nv-y nv-axis');
5468       focusEnter.append('g').attr('class', 'nv-linesWrap');
5469
5470       var contextEnter = gEnter.append('g').attr('class', 'nv-context');
5471       contextEnter.append('g').attr('class', 'nv-x nv-axis');
5472       contextEnter.append('g').attr('class', 'nv-y nv-axis');
5473       contextEnter.append('g').attr('class', 'nv-linesWrap');
5474       contextEnter.append('g').attr('class', 'nv-brushBackground');
5475       contextEnter.append('g').attr('class', 'nv-x nv-brush');
5476
5477       //------------------------------------------------------------
5478
5479
5480       //------------------------------------------------------------
5481       // Legend
5482
5483       if (showLegend) {
5484         legend.width(availableWidth);
5485
5486         g.select('.nv-legendWrap')
5487             .datum(data)
5488             .call(legend);
5489
5490         if ( margin.top != legend.height()) {
5491           margin.top = legend.height();
5492           availableHeight1 = (height || parseInt(container.style('height')) || 400)
5493                              - margin.top - margin.bottom - height2;
5494         }
5495
5496         g.select('.nv-legendWrap')
5497             .attr('transform', 'translate(0,' + (-margin.top) +')')
5498       }
5499
5500       //------------------------------------------------------------
5501
5502
5503       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5504
5505
5506       //------------------------------------------------------------
5507       // Main Chart Component(s)
5508
5509       lines
5510         .width(availableWidth)
5511         .height(availableHeight1)
5512         .color(
5513           data
5514             .map(function(d,i) {
5515               return d.color || color(d, i);
5516             })
5517             .filter(function(d,i) {
5518               return !data[i].disabled;
5519           })
5520         );
5521
5522       lines2
5523         .defined(lines.defined())
5524         .width(availableWidth)
5525         .height(availableHeight2)
5526         .color(
5527           data
5528             .map(function(d,i) {
5529               return d.color || color(d, i);
5530             })
5531             .filter(function(d,i) {
5532               return !data[i].disabled;
5533           })
5534         );
5535
5536       g.select('.nv-context')
5537           .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
5538
5539       var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
5540           .datum(data.filter(function(d) { return !d.disabled }))
5541
5542       d3.transition(contextLinesWrap).call(lines2);
5543
5544       //------------------------------------------------------------
5545
5546
5547       /*
5548       var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5549           .datum(data.filter(function(d) { return !d.disabled }))
5550
5551       d3.transition(focusLinesWrap).call(lines);
5552      */
5553
5554
5555       //------------------------------------------------------------
5556       // Setup Main (Focus) Axes
5557
5558       xAxis
5559         .scale(x)
5560         .ticks( availableWidth / 100 )
5561         .tickSize(-availableHeight1, 0);
5562
5563       yAxis
5564         .scale(y)
5565         .ticks( availableHeight1 / 36 )
5566         .tickSize( -availableWidth, 0);
5567
5568       g.select('.nv-focus .nv-x.nv-axis')
5569           .attr('transform', 'translate(0,' + availableHeight1 + ')');
5570
5571       //------------------------------------------------------------
5572
5573
5574       //------------------------------------------------------------
5575       // Setup Brush
5576
5577       brush
5578         .x(x2)
5579         .on('brush', onBrush);
5580
5581       if (brushExtent) brush.extent(brushExtent);
5582
5583       var brushBG = g.select('.nv-brushBackground').selectAll('g')
5584           .data([brushExtent || brush.extent()])
5585
5586       var brushBGenter = brushBG.enter()
5587           .append('g');
5588
5589       brushBGenter.append('rect')
5590           .attr('class', 'left')
5591           .attr('x', 0)
5592           .attr('y', 0)
5593           .attr('height', availableHeight2);
5594
5595       brushBGenter.append('rect')
5596           .attr('class', 'right')
5597           .attr('x', 0)
5598           .attr('y', 0)
5599           .attr('height', availableHeight2);
5600
5601       gBrush = g.select('.nv-x.nv-brush')
5602           .call(brush);
5603       gBrush.selectAll('rect')
5604           //.attr('y', -5)
5605           .attr('height', availableHeight2);
5606       gBrush.selectAll('.resize').append('path').attr('d', resizePath);
5607
5608       onBrush();
5609
5610       //------------------------------------------------------------
5611
5612
5613       //------------------------------------------------------------
5614       // Setup Secondary (Context) Axes
5615
5616       x2Axis
5617         .scale(x2)
5618         .ticks( availableWidth / 100 )
5619         .tickSize(-availableHeight2, 0);
5620
5621       g.select('.nv-context .nv-x.nv-axis')
5622           .attr('transform', 'translate(0,' + y2.range()[0] + ')');
5623       d3.transition(g.select('.nv-context .nv-x.nv-axis'))
5624           .call(x2Axis);
5625
5626
5627       y2Axis
5628         .scale(y2)
5629         .ticks( availableHeight2 / 36 )
5630         .tickSize( -availableWidth, 0);
5631
5632       d3.transition(g.select('.nv-context .nv-y.nv-axis'))
5633           .call(y2Axis);
5634
5635       g.select('.nv-context .nv-x.nv-axis')
5636           .attr('transform', 'translate(0,' + y2.range()[0] + ')');
5637
5638       //------------------------------------------------------------
5639
5640
5641       //============================================================
5642       // Event Handling/Dispatching (in chart's scope)
5643       //------------------------------------------------------------
5644
5645       legend.dispatch.on('legendClick', function(d,i) {
5646         d.disabled = !d.disabled;
5647
5648         if (!data.filter(function(d) { return !d.disabled }).length) {
5649           data.map(function(d) {
5650             d.disabled = false;
5651             wrap.selectAll('.nv-series').classed('disabled', false);
5652             return d;
5653           });
5654         }
5655
5656         container.transition().call(chart);
5657       });
5658
5659       dispatch.on('tooltipShow', function(e) {
5660         if (tooltips) showTooltip(e, that.parentNode);
5661       });
5662
5663       //============================================================
5664
5665
5666       //============================================================
5667       // Functions
5668       //------------------------------------------------------------
5669
5670       // Taken from crossfilter (http://square.github.com/crossfilter/)
5671       function resizePath(d) {
5672         var e = +(d == 'e'),
5673             x = e ? 1 : -1,
5674             y = availableHeight2 / 3;
5675         return 'M' + (.5 * x) + ',' + y
5676             + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
5677             + 'V' + (2 * y - 6)
5678             + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
5679             + 'Z'
5680             + 'M' + (2.5 * x) + ',' + (y + 8)
5681             + 'V' + (2 * y - 8)
5682             + 'M' + (4.5 * x) + ',' + (y + 8)
5683             + 'V' + (2 * y - 8);
5684       }
5685
5686
5687       function updateBrushBG() {
5688         if (!brush.empty()) brush.extent(brushExtent);
5689         brushBG
5690             .data([brush.empty() ? x2.domain() : brushExtent])
5691             .each(function(d,i) {
5692               var leftWidth = x2(d[0]) - x.range()[0],
5693                   rightWidth = x.range()[1] - x2(d[1]);
5694               d3.select(this).select('.left')
5695                 .attr('width',  leftWidth < 0 ? 0 : leftWidth);
5696
5697               d3.select(this).select('.right')
5698                 .attr('x', x2(d[1]))
5699                 .attr('width', rightWidth < 0 ? 0 : rightWidth);
5700             });
5701       }
5702
5703
5704       function onBrush() {
5705         brushExtent = brush.empty() ? null : brush.extent();
5706         extent = brush.empty() ? x2.domain() : brush.extent();
5707
5708
5709         dispatch.brush({extent: extent, brush: brush});
5710
5711
5712         updateBrushBG();
5713
5714         // Update Main (Focus)
5715         var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5716             .datum(
5717               data
5718                 .filter(function(d) { return !d.disabled })
5719                 .map(function(d,i) {
5720                   return {
5721                     key: d.key,
5722                     values: d.values.filter(function(d,i) {
5723                       return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
5724                     })
5725                   }
5726                 })
5727             );
5728         d3.transition(focusLinesWrap).call(lines);
5729
5730
5731         // Update Main (Focus) Axes
5732         d3.transition(g.select('.nv-focus .nv-x.nv-axis'))
5733             .call(xAxis);
5734         d3.transition(g.select('.nv-focus .nv-y.nv-axis'))
5735             .call(yAxis);
5736       }
5737
5738       //============================================================
5739
5740
5741     });
5742
5743     return chart;
5744   }
5745
5746
5747   //============================================================
5748   // Event Handling/Dispatching (out of chart's scope)
5749   //------------------------------------------------------------
5750
5751   lines.dispatch.on('elementMouseover.tooltip', function(e) {
5752     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
5753     dispatch.tooltipShow(e);
5754   });
5755
5756   lines.dispatch.on('elementMouseout.tooltip', function(e) {
5757     dispatch.tooltipHide(e);
5758   });
5759
5760   dispatch.on('tooltipHide', function() {
5761     if (tooltips) nv.tooltip.cleanup();
5762   });
5763
5764   //============================================================
5765
5766
5767   //============================================================
5768   // Expose Public Variables
5769   //------------------------------------------------------------
5770
5771   // expose chart's sub-components
5772   chart.dispatch = dispatch;
5773   chart.legend = legend;
5774   chart.lines = lines;
5775   chart.lines2 = lines2;
5776   chart.xAxis = xAxis;
5777   chart.yAxis = yAxis;
5778   chart.x2Axis = x2Axis;
5779   chart.y2Axis = y2Axis;
5780
5781   d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
5782
5783   chart.x = function(_) {
5784     if (!arguments.length) return lines.x;
5785     lines.x(_);
5786     lines2.x(_);
5787     return chart;
5788   };
5789
5790   chart.y = function(_) {
5791     if (!arguments.length) return lines.y;
5792     lines.y(_);
5793     lines2.y(_);
5794     return chart;
5795   };
5796
5797   chart.margin = function(_) {
5798     if (!arguments.length) return margin;
5799     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
5800     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
5801     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5802     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
5803     return chart;
5804   };
5805
5806   chart.margin2 = function(_) {
5807     if (!arguments.length) return margin2;
5808     margin2 = _;
5809     return chart;
5810   };
5811
5812   chart.width = function(_) {
5813     if (!arguments.length) return width;
5814     width = _;
5815     return chart;
5816   };
5817
5818   chart.height = function(_) {
5819     if (!arguments.length) return height;
5820     height = _;
5821     return chart;
5822   };
5823
5824   chart.height2 = function(_) {
5825     if (!arguments.length) return height2;
5826     height2 = _;
5827     return chart;
5828   };
5829
5830   chart.color = function(_) {
5831     if (!arguments.length) return color;
5832     color =nv.utils.getColor(_);
5833     legend.color(color);
5834     return chart;
5835   };
5836
5837   chart.showLegend = function(_) {
5838     if (!arguments.length) return showLegend;
5839     showLegend = _;
5840     return chart;
5841   };
5842
5843   chart.tooltips = function(_) {
5844     if (!arguments.length) return tooltips;
5845     tooltips = _;
5846     return chart;
5847   };
5848
5849   chart.tooltipContent = function(_) {
5850     if (!arguments.length) return tooltip;
5851     tooltip = _;
5852     return chart;
5853   };
5854
5855   chart.interpolate = function(_) {
5856     if (!arguments.length) return lines.interpolate();
5857     lines.interpolate(_);
5858     lines2.interpolate(_);
5859     return chart;
5860   };
5861
5862   chart.noData = function(_) {
5863     if (!arguments.length) return noData;
5864     noData = _;
5865     return chart;
5866   };
5867
5868   // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
5869   chart.xTickFormat = function(_) {
5870     if (!arguments.length) return xAxis.tickFormat();
5871     xAxis.tickFormat(_);
5872     x2Axis.tickFormat(_);
5873     return chart;
5874   };
5875
5876   chart.yTickFormat = function(_) {
5877     if (!arguments.length) return yAxis.tickFormat();
5878     yAxis.tickFormat(_);
5879     y2Axis.tickFormat(_);
5880     return chart;
5881   };
5882
5883   //============================================================
5884
5885
5886   return chart;
5887 }
5888
5889 nv.models.linePlusBarWithFocusChart = function() {
5890
5891   //============================================================
5892   // Public Variables with Default Settings
5893   //------------------------------------------------------------
5894
5895   var lines = nv.models.line()
5896     , lines2 = nv.models.line()
5897     , bars = nv.models.historicalBar()
5898     , bars2 = nv.models.historicalBar()
5899     , xAxis = nv.models.axis()
5900     , x2Axis = nv.models.axis()
5901     , y1Axis = nv.models.axis()
5902     , y2Axis = nv.models.axis()
5903     , y3Axis = nv.models.axis()
5904     , y4Axis = nv.models.axis()
5905     , legend = nv.models.legend()
5906     , brush = d3.svg.brush()
5907     ;
5908
5909   var margin = {top: 30, right: 30, bottom: 30, left: 60}
5910     , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5911     , width = null
5912     , height = null
5913     , height2 = 100
5914     , getX = function(d) { return d.x }
5915     , getY = function(d) { return d.y }
5916     , color = nv.utils.defaultColor()
5917     , showLegend = true
5918     , extent
5919     , brushExtent = null
5920     , tooltips = true
5921     , tooltip = function(key, x, y, e, graph) {
5922         return '<h3>' + key + '</h3>' +
5923                '<p>' +  y + ' at ' + x + '</p>';
5924       }
5925     , x
5926     , x2
5927     , y1
5928     , y2
5929     , y3
5930     , y4
5931     , noData = "No Data Available."
5932     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
5933     ;
5934
5935   lines
5936     .clipEdge(true)
5937     ;
5938   lines2
5939     .interactive(false)
5940     ;
5941   xAxis
5942     .orient('bottom')
5943     .tickPadding(5)
5944     ;
5945   y1Axis
5946     .orient('left')
5947     ;
5948   y2Axis
5949     .orient('right')
5950     ;
5951   x2Axis
5952     .orient('bottom')
5953     .tickPadding(5)
5954     ;
5955   y3Axis
5956     .orient('left')
5957     ;
5958   y4Axis
5959     .orient('right')
5960     ;
5961
5962   //============================================================
5963
5964
5965   //============================================================
5966   // Private Variables
5967   //------------------------------------------------------------
5968
5969   var showTooltip = function(e, offsetElement) {
5970     if (extent) {
5971         e.pointIndex += Math.ceil(extent[0]);
5972     }
5973     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5974         top = e.pos[1] + ( offsetElement.offsetTop || 0),
5975         x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5976         y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5977         content = tooltip(e.series.key, x, y, e, chart);
5978
5979     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5980   };
5981
5982   //------------------------------------------------------------
5983
5984
5985
5986   function chart(selection) {
5987     selection.each(function(data) {
5988       var container = d3.select(this),
5989           that = this;
5990
5991       var availableWidth = (width  || parseInt(container.style('width')) || 960)
5992                              - margin.left - margin.right,
5993           availableHeight1 = (height || parseInt(container.style('height')) || 400)
5994                              - margin.top - margin.bottom - height2,
5995           availableHeight2 = height2 - margin2.top - margin2.bottom;
5996
5997       chart.update = function() { container.transition().call(chart); };
5998       chart.container = this;
5999
6000
6001       //------------------------------------------------------------
6002       // Display No Data message if there's nothing to show.
6003
6004       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6005         var noDataText = container.selectAll('.nv-noData').data([noData]);
6006
6007         noDataText.enter().append('text')
6008           .attr('class', 'nvd3 nv-noData')
6009           .attr('dy', '-.7em')
6010           .style('text-anchor', 'middle');
6011
6012         noDataText
6013           .attr('x', margin.left + availableWidth / 2)
6014           .attr('y', margin.top + availableHeight1 / 2)
6015           .text(function(d) { return d });
6016
6017         return chart;
6018       } else {
6019         container.selectAll('.nv-noData').remove();
6020       }
6021
6022       //------------------------------------------------------------
6023
6024
6025       //------------------------------------------------------------
6026       // Setup Scales
6027
6028       var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6029       var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6030
6031       x = bars.xScale();
6032       x2 = x2Axis.scale();
6033       y1 = bars.yScale();
6034       y2 = lines.yScale();
6035       y3 = bars2.yScale();
6036       y4 = lines2.yScale();
6037
6038       var series1 = data
6039         .filter(function(d) { return !d.disabled && d.bar })
6040         .map(function(d) {
6041           return d.values.map(function(d,i) {
6042             return { x: getX(d,i), y: getY(d,i) }
6043           })
6044         });
6045
6046       var series2 = data
6047         .filter(function(d) { return !d.disabled && !d.bar })
6048         .map(function(d) {
6049           return d.values.map(function(d,i) {
6050             return { x: getX(d,i), y: getY(d,i) }
6051           })
6052         });
6053
6054       x   .range([0, availableWidth]);
6055       
6056       x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6057           .range([0, availableWidth]);
6058
6059
6060       //------------------------------------------------------------
6061
6062
6063       //------------------------------------------------------------
6064       // Setup containers and skeleton of chart
6065
6066       var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6067       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6068       var g = wrap.select('g');
6069
6070       gEnter.append('g').attr('class', 'nv-legendWrap');
6071       
6072       var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6073       focusEnter.append('g').attr('class', 'nv-x nv-axis');
6074       focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6075       focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6076       focusEnter.append('g').attr('class', 'nv-barsWrap');
6077       focusEnter.append('g').attr('class', 'nv-linesWrap');
6078
6079       var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6080       contextEnter.append('g').attr('class', 'nv-x nv-axis');
6081       contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6082       contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6083       contextEnter.append('g').attr('class', 'nv-barsWrap');
6084       contextEnter.append('g').attr('class', 'nv-linesWrap');
6085       contextEnter.append('g').attr('class', 'nv-brushBackground');
6086       contextEnter.append('g').attr('class', 'nv-x nv-brush');
6087
6088
6089       //------------------------------------------------------------
6090
6091
6092       //------------------------------------------------------------
6093       // Legend
6094
6095       if (showLegend) {
6096         legend.width( availableWidth / 2 );
6097
6098         g.select('.nv-legendWrap')
6099             .datum(data.map(function(series) {
6100               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6101               series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6102               return series;
6103             }))
6104           .call(legend);
6105
6106         if ( margin.top != legend.height()) {
6107           margin.top = legend.height();
6108           availableHeight1 = (height || parseInt(container.style('height')) || 400)
6109                              - margin.top - margin.bottom - height2;
6110         }
6111
6112         g.select('.nv-legendWrap')
6113             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6114       }
6115
6116       //------------------------------------------------------------
6117
6118
6119       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6120
6121
6122       //------------------------------------------------------------
6123       // Context Components
6124
6125       bars2
6126         .width(availableWidth)
6127         .height(availableHeight2)
6128         .color(data.map(function(d,i) {
6129           return d.color || color(d, i);
6130         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6131
6132       lines2
6133         .width(availableWidth)
6134         .height(availableHeight2)
6135         .color(data.map(function(d,i) {
6136           return d.color || color(d, i);
6137         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6138         
6139       var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6140           .datum(dataBars.length ? dataBars : [{values:[]}]);
6141
6142       var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6143           .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
6144           
6145       g.select('.nv-context')
6146           .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6147
6148       d3.transition(bars2Wrap).call(bars2);
6149       d3.transition(lines2Wrap).call(lines2);
6150
6151       //------------------------------------------------------------
6152
6153
6154
6155       //------------------------------------------------------------
6156       // Setup Brush
6157
6158       brush
6159         .x(x2)
6160         .on('brush', onBrush);
6161
6162       if (brushExtent) brush.extent(brushExtent);
6163
6164       var brushBG = g.select('.nv-brushBackground').selectAll('g')
6165           .data([brushExtent || brush.extent()])
6166
6167       var brushBGenter = brushBG.enter()
6168           .append('g');
6169
6170       brushBGenter.append('rect')
6171           .attr('class', 'left')
6172           .attr('x', 0)
6173           .attr('y', 0)
6174           .attr('height', availableHeight2);
6175
6176       brushBGenter.append('rect')
6177           .attr('class', 'right')
6178           .attr('x', 0)
6179           .attr('y', 0)
6180           .attr('height', availableHeight2);
6181
6182       var gBrush = g.select('.nv-x.nv-brush')
6183           .call(brush);
6184       gBrush.selectAll('rect')
6185           //.attr('y', -5)
6186           .attr('height', availableHeight2);
6187       gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6188
6189       //------------------------------------------------------------
6190
6191       //------------------------------------------------------------
6192       // Setup Secondary (Context) Axes
6193
6194       x2Axis
6195         .ticks( availableWidth / 100 )
6196         .tickSize(-availableHeight2, 0);
6197
6198       g.select('.nv-context .nv-x.nv-axis')
6199           .attr('transform', 'translate(0,' + y3.range()[0] + ')');
6200       d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6201           .call(x2Axis);
6202
6203
6204       y3Axis
6205         .scale(y3)
6206         .ticks( availableHeight2 / 36 )
6207         .tickSize( -availableWidth, 0);
6208
6209       g.select('.nv-context .nv-y1.nv-axis')
6210           .style('opacity', dataBars.length ? 1 : 0)
6211           .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6212           
6213       d3.transition(g.select('.nv-context .nv-y1.nv-axis'))
6214           .call(y3Axis);
6215           
6216
6217       y4Axis
6218         .scale(y4)
6219         .ticks( availableHeight2 / 36 )
6220         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6221
6222       g.select('.nv-context .nv-y2.nv-axis')
6223           .style('opacity', dataLines.length ? 1 : 0)
6224           .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6225
6226       d3.transition(g.select('.nv-context .nv-y2.nv-axis'))
6227           .call(y4Axis);
6228           
6229       //------------------------------------------------------------
6230
6231       //============================================================
6232       // Event Handling/Dispatching (in chart's scope)
6233       //------------------------------------------------------------
6234
6235       legend.dispatch.on('legendClick', function(d,i) { 
6236         d.disabled = !d.disabled;
6237
6238         if (!data.filter(function(d) { return !d.disabled }).length) {
6239           data.map(function(d) {
6240             d.disabled = false;
6241             wrap.selectAll('.nv-series').classed('disabled', false);
6242             return d;
6243           });
6244         }
6245
6246         chart.update();
6247       });
6248
6249       dispatch.on('tooltipShow', function(e) {
6250         if (tooltips) showTooltip(e, that.parentNode);
6251       });
6252
6253       //============================================================
6254
6255
6256       //============================================================
6257       // Functions
6258       //------------------------------------------------------------
6259
6260       // Taken from crossfilter (http://square.github.com/crossfilter/)
6261       function resizePath(d) {
6262         var e = +(d == 'e'),
6263             x = e ? 1 : -1,
6264             y = availableHeight2 / 3;
6265         return 'M' + (.5 * x) + ',' + y
6266             + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6267             + 'V' + (2 * y - 6)
6268             + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6269             + 'Z'
6270             + 'M' + (2.5 * x) + ',' + (y + 8)
6271             + 'V' + (2 * y - 8)
6272             + 'M' + (4.5 * x) + ',' + (y + 8)
6273             + 'V' + (2 * y - 8);
6274       }
6275
6276
6277       function updateBrushBG() {
6278         if (!brush.empty()) brush.extent(brushExtent);
6279         brushBG
6280             .data([brush.empty() ? x2.domain() : brushExtent])
6281             .each(function(d,i) {
6282               var leftWidth = x2(d[0]) - x2.range()[0],
6283                   rightWidth = x2.range()[1] - x2(d[1]);
6284               d3.select(this).select('.left')
6285                 .attr('width',  leftWidth < 0 ? 0 : leftWidth);
6286
6287               d3.select(this).select('.right')
6288                 .attr('x', x2(d[1]))
6289                 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6290             });
6291       }
6292
6293
6294       function onBrush() {
6295         brushExtent = brush.empty() ? null : brush.extent();
6296         extent = brush.empty() ? x2.domain() : brush.extent();
6297
6298
6299         dispatch.brush({extent: extent, brush: brush});
6300
6301         updateBrushBG();
6302
6303
6304         //------------------------------------------------------------
6305         // Prepare Main (Focus) Bars and Lines
6306         
6307         bars
6308         .width(availableWidth)
6309         .height(availableHeight1)
6310         .color(data.map(function(d,i) {
6311           return d.color || color(d, i);
6312         }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6313
6314
6315         lines
6316         .width(availableWidth)
6317         .height(availableHeight1)
6318         .color(data.map(function(d,i) {
6319           return d.color || color(d, i);
6320         }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6321
6322         var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
6323             .datum(!dataBars.length ? [{values:[]}] :
6324               dataBars
6325                 .map(function(d,i) {
6326                   return {
6327                     key: d.key,
6328                     values: d.values.filter(function(d,i) {
6329                       return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
6330                     })
6331                   }
6332                 })
6333             );
6334         
6335         var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6336             .datum(dataLines[0].disabled ? [{values:[]}] :
6337               dataLines
6338                 .map(function(d,i) {
6339                   return {
6340                     key: d.key,
6341                     values: d.values.filter(function(d,i) {
6342                       return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6343                     })
6344                   }
6345                 })
6346              );
6347                  
6348         //------------------------------------------------------------
6349         
6350         
6351         //------------------------------------------------------------
6352         // Update Main (Focus) X Axis
6353
6354         if (dataBars.length) {
6355             x = bars.xScale();
6356         } else {
6357             x = lines.xScale();
6358         }
6359         
6360         xAxis
6361         .scale(x)
6362         .ticks( availableWidth / 100 )
6363         .tickSize(-availableHeight1, 0);
6364
6365         xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
6366         
6367         d3.transition(g.select('.nv-x.nv-axis'))
6368           .call(xAxis);
6369         //------------------------------------------------------------
6370         
6371         
6372         //------------------------------------------------------------
6373         // Update Main (Focus) Bars and Lines
6374
6375         d3.transition(focusBarsWrap).call(bars);
6376         d3.transition(focusLinesWrap).call(lines);
6377         
6378         //------------------------------------------------------------
6379         
6380           
6381         //------------------------------------------------------------
6382         // Setup and Update Main (Focus) Y Axes
6383         
6384         g.select('.nv-focus .nv-x.nv-axis')
6385           .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6386
6387
6388         y1Axis
6389         .scale(y1)
6390         .ticks( availableHeight1 / 36 )
6391         .tickSize(-availableWidth, 0);
6392
6393         g.select('.nv-focus .nv-y1.nv-axis')
6394           .style('opacity', dataBars.length ? 1 : 0);
6395
6396
6397         y2Axis
6398         .scale(y2)
6399         .ticks( availableHeight1 / 36 )
6400         .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6401
6402         g.select('.nv-focus .nv-y2.nv-axis')
6403           .style('opacity', dataLines.length ? 1 : 0)
6404           .attr('transform', 'translate(' + x.range()[1] + ',0)');
6405
6406         d3.transition(g.select('.nv-focus .nv-y1.nv-axis'))
6407             .call(y1Axis);
6408         d3.transition(g.select('.nv-focus .nv-y2.nv-axis'))
6409             .call(y2Axis);
6410       }
6411
6412       //============================================================
6413
6414       onBrush();
6415
6416     });
6417
6418     return chart;
6419   }
6420
6421
6422   //============================================================
6423   // Event Handling/Dispatching (out of chart's scope)
6424   //------------------------------------------------------------
6425
6426   lines.dispatch.on('elementMouseover.tooltip', function(e) {
6427     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6428     dispatch.tooltipShow(e);
6429   });
6430
6431   lines.dispatch.on('elementMouseout.tooltip', function(e) {
6432     dispatch.tooltipHide(e);
6433   });
6434
6435   bars.dispatch.on('elementMouseover.tooltip', function(e) {
6436     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
6437     dispatch.tooltipShow(e);
6438   });
6439
6440   bars.dispatch.on('elementMouseout.tooltip', function(e) {
6441     dispatch.tooltipHide(e);
6442   });
6443
6444   dispatch.on('tooltipHide', function() {
6445     if (tooltips) nv.tooltip.cleanup();
6446   });
6447
6448   //============================================================
6449
6450
6451   //============================================================
6452   // Expose Public Variables
6453   //------------------------------------------------------------
6454
6455   // expose chart's sub-components
6456   chart.dispatch = dispatch;
6457   chart.legend = legend;
6458   chart.lines = lines;
6459   chart.lines2 = lines2;
6460   chart.bars = bars;
6461   chart.bars2 = bars2;
6462   chart.xAxis = xAxis;
6463   chart.x2Axis = x2Axis;
6464   chart.y1Axis = y1Axis;
6465   chart.y2Axis = y2Axis;
6466   chart.y3Axis = y3Axis;
6467   chart.y4Axis = y4Axis;
6468
6469   d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6470   //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6471   //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6472
6473   chart.x = function(_) {
6474     if (!arguments.length) return getX;
6475     getX = _;
6476     lines.x(_);
6477     bars.x(_);
6478     return chart;
6479   };
6480
6481   chart.y = function(_) {
6482     if (!arguments.length) return getY;
6483     getY = _;
6484     lines.y(_);
6485     bars.y(_);
6486     return chart;
6487   };
6488
6489   chart.margin = function(_) {
6490     if (!arguments.length) return margin;
6491     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
6492     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
6493     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6494     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
6495     return chart;
6496   };
6497
6498   chart.width = function(_) {
6499     if (!arguments.length) return width;
6500     width = _;
6501     return chart;
6502   };
6503
6504   chart.height = function(_) {
6505     if (!arguments.length) return height;
6506     height = _;
6507     return chart;
6508   };
6509
6510   chart.color = function(_) {
6511     if (!arguments.length) return color;
6512     color = nv.utils.getColor(_);
6513     legend.color(color);
6514     return chart;
6515   };
6516
6517   chart.showLegend = function(_) {
6518     if (!arguments.length) return showLegend;
6519     showLegend = _;
6520     return chart;
6521   };
6522
6523   chart.tooltips = function(_) {
6524     if (!arguments.length) return tooltips;
6525     tooltips = _;
6526     return chart;
6527   };
6528
6529   chart.tooltipContent = function(_) {
6530     if (!arguments.length) return tooltip;
6531     tooltip = _;
6532     return chart;
6533   };
6534
6535   chart.noData = function(_) {
6536     if (!arguments.length) return noData;
6537     noData = _;
6538     return chart;
6539   };
6540
6541   chart.brushExtent = function(_) {
6542     if (!arguments.length) return brushExtent;
6543     brushExtent = _;
6544     return chart;
6545   };
6546
6547
6548   //============================================================
6549
6550
6551   return chart;
6552 }
6553
6554 nv.models.multiBar = function() {
6555
6556   //============================================================
6557   // Public Variables with Default Settings
6558   //------------------------------------------------------------
6559
6560   var margin = {top: 0, right: 0, bottom: 0, left: 0}
6561     , width = 960
6562     , height = 500
6563     , x = d3.scale.ordinal()
6564     , y = d3.scale.linear()
6565     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
6566     , getX = function(d) { return d.x }
6567     , getY = function(d) { return d.y }
6568     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
6569     , clipEdge = true
6570     , stacked = false
6571     , color = nv.utils.defaultColor()
6572     , hideable = false
6573     , barColor = null // adding the ability to set the color for each rather than the whole group
6574     , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
6575     , delay = 1200
6576     , drawTime = 500
6577     , xDomain
6578     , yDomain
6579     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
6580     ;
6581
6582   //============================================================
6583
6584
6585   //============================================================
6586   // Private Variables
6587   //------------------------------------------------------------
6588
6589   var x0, y0 //used to store previous scales
6590       ;
6591
6592   //============================================================
6593
6594
6595   function chart(selection) {
6596     selection.each(function(data) {
6597       var availableWidth = width - margin.left - margin.right,
6598           availableHeight = height - margin.top - margin.bottom,
6599           container = d3.select(this);
6600
6601       if(hideable && data.length) hideable = [{
6602         values: data[0].values.map(function(d) {
6603         return {
6604           x: d.x,
6605           y: 0,
6606           series: d.series,
6607           size: 0.01
6608         };}
6609       )}];
6610
6611       if (stacked)
6612         data = d3.layout.stack()
6613                  .offset('zero')
6614                  .values(function(d){ return d.values })
6615                  .y(getY)
6616                  (!data.length && hideable ? hideable : data);
6617
6618
6619       //add series index to each data point for reference
6620       data = data.map(function(series, i) {
6621         series.values = series.values.map(function(point) {
6622           point.series = i;
6623           return point;
6624         });
6625         return series;
6626       });
6627
6628
6629       //------------------------------------------------------------
6630       // HACK for negative value stacking
6631       if (stacked)
6632         data[0].values.map(function(d,i) {
6633           var posBase = 0, negBase = 0;
6634           data.map(function(d) {
6635             var f = d.values[i]
6636             f.size = Math.abs(f.y);
6637             if (f.y<0)  {
6638               f.y1 = negBase;
6639               negBase = negBase - f.size;
6640             } else
6641             { 
6642               f.y1 = f.size + posBase;
6643               posBase = posBase + f.size;
6644             }
6645           });
6646         });
6647
6648       //------------------------------------------------------------
6649       // Setup Scales
6650
6651       // remap and flatten the data for use in calculating the scales' domains
6652       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
6653             data.map(function(d) {
6654               return d.values.map(function(d,i) {
6655                 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
6656               })
6657             });
6658
6659       x   .domain(d3.merge(seriesData).map(function(d) { return d.x }))
6660           .rangeBands([0, availableWidth], .1);
6661
6662       //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
6663       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)))
6664           .range([availableHeight, 0]);
6665
6666       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
6667       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
6668       if (x.domain()[0] === x.domain()[1])
6669         x.domain()[0] ?
6670             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
6671           : x.domain([-1,1]);
6672
6673       if (y.domain()[0] === y.domain()[1])
6674         y.domain()[0] ?
6675             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
6676           : y.domain([-1,1]);
6677
6678
6679       x0 = x0 || x;
6680       y0 = y0 || y;
6681
6682       //------------------------------------------------------------
6683
6684
6685       //------------------------------------------------------------
6686       // Setup containers and skeleton of chart
6687
6688       var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
6689       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
6690       var defsEnter = wrapEnter.append('defs');
6691       var gEnter = wrapEnter.append('g');
6692       var g = wrap.select('g')
6693
6694       gEnter.append('g').attr('class', 'nv-groups');
6695
6696       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6697
6698       //------------------------------------------------------------
6699
6700
6701
6702       defsEnter.append('clipPath')
6703           .attr('id', 'nv-edge-clip-' + id)
6704         .append('rect');
6705       wrap.select('#nv-edge-clip-' + id + ' rect')
6706           .attr('width', availableWidth)
6707           .attr('height', availableHeight);
6708
6709       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
6710
6711
6712
6713       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
6714           .data(function(d) { return d }, function(d) { return d.key });
6715       groups.enter().append('g')
6716           .style('stroke-opacity', 1e-6)
6717           .style('fill-opacity', 1e-6);
6718
6719
6720       groups.exit()
6721         .selectAll('rect.nv-bar')
6722         .transition()
6723         .delay(function(d,i) { return i * delay/ data[0].values.length })
6724           .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
6725           .attr('height', 0)
6726           .remove();
6727       groups
6728           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
6729           .classed('hover', function(d) { return d.hover })
6730           .style('fill', function(d,i){ return color(d, i) })
6731           .style('stroke', function(d,i){ return color(d, i) });
6732       d3.transition(groups)
6733           .style('stroke-opacity', 1)
6734           .style('fill-opacity', .75);
6735
6736
6737       var bars = groups.selectAll('rect.nv-bar')
6738           .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
6739
6740       bars.exit().remove();
6741
6742
6743       var barsEnter = bars.enter().append('rect')
6744           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6745           .attr('x', function(d,i,j) {
6746               return stacked ? 0 : (j * x.rangeBand() / data.length )
6747           })
6748           .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
6749           .attr('height', 0)
6750           .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
6751       bars
6752           .style('fill', function(d,i,j){ return color(d, j, i);  })
6753           .style('stroke', function(d,i,j){ return color(d, j, i); })
6754           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
6755             d3.select(this).classed('hover', true);
6756             dispatch.elementMouseover({
6757               value: getY(d,i),
6758               point: d,
6759               series: data[d.series],
6760               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
6761               pointIndex: i,
6762               seriesIndex: d.series,
6763               e: d3.event
6764             });
6765           })
6766           .on('mouseout', function(d,i) {
6767             d3.select(this).classed('hover', false);
6768             dispatch.elementMouseout({
6769               value: getY(d,i),
6770               point: d,
6771               series: data[d.series],
6772               pointIndex: i,
6773               seriesIndex: d.series,
6774               e: d3.event
6775             });
6776           })
6777           .on('click', function(d,i) {
6778             dispatch.elementClick({
6779               value: getY(d,i),
6780               point: d,
6781               series: data[d.series],
6782               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
6783               pointIndex: i,
6784               seriesIndex: d.series,
6785               e: d3.event
6786             });
6787             d3.event.stopPropagation();
6788           })
6789           .on('dblclick', function(d,i) {
6790             dispatch.elementDblClick({
6791               value: getY(d,i),
6792               point: d,
6793               series: data[d.series],
6794               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
6795               pointIndex: i,
6796               seriesIndex: d.series,
6797               e: d3.event
6798             });
6799             d3.event.stopPropagation();
6800           });
6801       bars
6802           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6803           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
6804
6805       if (barColor) {
6806         if (!disabled) disabled = data.map(function() { return true });
6807         bars
6808           //.style('fill', barColor)
6809           //.style('stroke', barColor)
6810           //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); })
6811           //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); })
6812           .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(); })
6813           .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(); });
6814       }
6815
6816
6817       if (stacked)
6818             bars.transition()
6819           
6820             .delay(function(d,i) { return i * delay / data[0].values.length })
6821             .attr('y', function(d,i) {
6822
6823               return y((stacked ? d.y1 : 0));
6824             })
6825             .attr('height', function(d,i) {
6826                           if(d.y == null) return 0;     
6827               return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
6828             })
6829             .each('end', function() {
6830               d3.select(this).transition().duration(drawTime)
6831                 .attr('x', function(d,i) {
6832                   return stacked ? 0 : (d.series * x.rangeBand() / data.length )
6833                 })
6834                 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
6835             })
6836       else
6837         d3.transition(bars).duration(drawTime)
6838           .delay(function(d,i) { return i * delay/ data[0].values.length })
6839             .attr('x', function(d,i) {
6840               return d.series * x.rangeBand() / data.length
6841             })
6842             .attr('width', x.rangeBand() / data.length)
6843             .each('end', function() {
6844               d3.select(this).transition().duration(drawTime)
6845                 .attr('y', function(d,i) {
6846                   return getY(d,i) < 0 ?
6847                           y(0) :
6848                           y(0) - y(getY(d,i)) < 1 ?
6849                             y(0) - 1 :
6850                           y(getY(d,i)) || 0;
6851               })
6852               .attr('height', function(d,i) {
6853                               if(d.y == null) return 0;
6854                   return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
6855                 });
6856             })
6857
6858
6859       //store old scales for use in transitions on update
6860       x0 = x.copy();
6861       y0 = y.copy();
6862
6863     });
6864
6865     return chart;
6866   }
6867
6868
6869   //============================================================
6870   // Expose Public Variables
6871   //------------------------------------------------------------
6872
6873   chart.dispatch = dispatch;
6874
6875   chart.x = function(_) {
6876     if (!arguments.length) return getX;
6877     getX = _;
6878     return chart;
6879   };
6880
6881   chart.y = function(_) {
6882     if (!arguments.length) return getY;
6883     getY = _;
6884     return chart;
6885   };
6886
6887   chart.margin = function(_) {
6888     if (!arguments.length) return margin;
6889     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
6890     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
6891     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6892     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
6893     return chart;
6894   };
6895
6896   chart.width = function(_) {
6897     if (!arguments.length) return width;
6898     width = _;
6899     return chart;
6900   };
6901
6902   chart.height = function(_) {
6903     if (!arguments.length) return height;
6904     height = _;
6905     return chart;
6906   };
6907
6908   chart.xScale = function(_) {
6909     if (!arguments.length) return x;
6910     x = _;
6911     return chart;
6912   };
6913
6914   chart.yScale = function(_) {
6915     if (!arguments.length) return y;
6916     y = _;
6917     return chart;
6918   };
6919
6920   chart.xDomain = function(_) {
6921     if (!arguments.length) return xDomain;
6922     xDomain = _;
6923     return chart;
6924   };
6925
6926   chart.yDomain = function(_) {
6927     if (!arguments.length) return yDomain;
6928     yDomain = _;
6929     return chart;
6930   };
6931
6932   chart.forceY = function(_) {
6933     if (!arguments.length) return forceY;
6934     forceY = _;
6935     return chart;
6936   };
6937
6938   chart.stacked = function(_) {
6939     if (!arguments.length) return stacked;
6940     stacked = _;
6941     return chart;
6942   };
6943
6944   chart.clipEdge = function(_) {
6945     if (!arguments.length) return clipEdge;
6946     clipEdge = _;
6947     return chart;
6948   };
6949
6950   chart.color = function(_) {
6951     if (!arguments.length) return color;
6952     color = nv.utils.getColor(_);
6953     return chart;
6954   };
6955
6956   chart.barColor = function(_) {
6957     if (!arguments.length) return barColor;
6958     barColor = nv.utils.getColor(_);
6959     return chart;
6960   };
6961
6962   chart.disabled = function(_) {
6963     if (!arguments.length) return disabled;
6964     disabled = _;
6965     return chart;
6966   };
6967
6968   chart.id = function(_) {
6969     if (!arguments.length) return id;
6970     id = _;
6971     return chart;
6972   };
6973
6974   chart.hideable = function(_) {
6975     if (!arguments.length) return hideable;
6976     hideable = _;
6977     return chart;
6978   };
6979
6980   chart.delay = function(_) {
6981     if (!arguments.length) return delay;
6982     delay = _;
6983     return chart;
6984   };
6985
6986   chart.drawTime = function(_) {
6987     if (!arguments.length) return drawTime;
6988     drawTime = _;
6989     return chart;
6990   };
6991
6992   //============================================================
6993
6994
6995   return chart;
6996 }
6997
6998 nv.models.multiBarChart = function() {
6999
7000   //============================================================
7001   // Public Variables with Default Settings
7002   //------------------------------------------------------------
7003
7004   var multibar = nv.models.multiBar()
7005     , xAxis = nv.models.axis()
7006     , yAxis = nv.models.axis()
7007     , legend = nv.models.legend()
7008     , controls = nv.models.legend()
7009     ;
7010
7011   var margin = {top: 30, right: 20, bottom: 50, left: 60}
7012     , width = null
7013     , height = null
7014     , color = nv.utils.defaultColor()
7015     , showControls = true
7016     , showLegend = true
7017         , logScale = false
7018     , reduceXTicks = true // if false a tick will show for every data point
7019     , staggerLabels = false
7020     , rotateLabels = 0
7021     , tooltips = true
7022     , tooltip = function(key, x, y, e, graph, logScale) {
7023     //alert(y+ " " + logScale);
7024         if(logScale) {
7025                     var fmt = d3.format(',.2f');
7026                         return '<h3>' + key + '</h3>' +
7027                '<p>' +  fmt(Math.pow(10,y)) + ' on ' + x + '</p>'
7028             } else {
7029         return '<h3>' + key + '</h3>' +
7030                '<p>' +  y + ' on ' + x + '</p>'
7031       }
7032
7033       }
7034     , x //can be accessed via chart.xScale()
7035     , y //can be accessed via chart.yScale()
7036     , state = { stacked: false }
7037     , defaultState = null
7038     , noData = "No Data Available."
7039     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
7040     , controlWidth = function() { return showControls ? 180 : 0 }
7041     ;
7042   multibar
7043     .stacked(false)
7044     ;
7045   xAxis
7046     .orient('bottom')
7047     .tickPadding(7)
7048     .highlightZero(true)
7049     .showMaxMin(false)
7050     .tickFormat(function(d) { return d })
7051     ;
7052   yAxis
7053     .orient('left')
7054     .tickFormat(d3.format(',.1f'))
7055     ;
7056
7057   //============================================================
7058
7059
7060   //============================================================
7061   // Private Variables
7062   //------------------------------------------------------------
7063
7064   var showTooltip = function(e, offsetElement) {
7065     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7066         top = e.pos[1] + ( offsetElement.offsetTop || 0),
7067         x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7068         y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7069         content = tooltip(e.series.key, x, y, e, chart, logScale);
7070
7071     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
7072   };
7073
7074   //============================================================
7075
7076
7077   function chart(selection) {
7078     selection.each(function(data) {
7079       var container = d3.select(this),
7080           that = this;
7081
7082       var availableWidth = (width  || parseInt(container.style('width')) || 960)
7083                              - margin.left - margin.right,
7084           availableHeight = (height || parseInt(container.style('height')) || 400)
7085                              - margin.top - margin.bottom;
7086
7087       chart.update = function() { container.transition().call(chart) };
7088       chart.container = this;
7089
7090       //set state.disabled
7091       state.disabled = data.map(function(d) { return !!d.disabled });
7092
7093       if (!defaultState) {
7094         var key;
7095         defaultState = {};
7096         for (key in state) {
7097           if (state[key] instanceof Array)
7098             defaultState[key] = state[key].slice(0);
7099           else
7100             defaultState[key] = state[key];
7101         }
7102       }
7103       //------------------------------------------------------------
7104       // Display noData message if there's nothing to show.
7105
7106       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7107         var noDataText = container.selectAll('.nv-noData').data([noData]);
7108
7109         noDataText.enter().append('text')
7110           .attr('class', 'nvd3 nv-noData')
7111           .attr('dy', '-.7em')
7112           .style('text-anchor', 'middle');
7113
7114         noDataText
7115           .attr('x', margin.left + availableWidth / 2)
7116           .attr('y', margin.top + availableHeight / 2)
7117           .text(function(d) { return d });
7118
7119         return chart;
7120       } else {
7121         container.selectAll('.nv-noData').remove();
7122       }
7123
7124       //------------------------------------------------------------
7125
7126
7127       //------------------------------------------------------------
7128       // Setup Scales
7129
7130       x = multibar.xScale();
7131       y = multibar.yScale();
7132
7133       //------------------------------------------------------------
7134
7135
7136       //------------------------------------------------------------
7137       // Setup containers and skeleton of chart
7138
7139       var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
7140       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
7141       var g = wrap.select('g');
7142
7143       gEnter.append('g').attr('class', 'nv-x nv-axis');
7144       gEnter.append('g').attr('class', 'nv-y nv-axis');
7145       gEnter.append('g').attr('class', 'nv-barsWrap');
7146       gEnter.append('g').attr('class', 'nv-legendWrap');
7147       gEnter.append('g').attr('class', 'nv-controlsWrap');
7148
7149       //------------------------------------------------------------
7150
7151
7152       //------------------------------------------------------------
7153       // Legend
7154
7155       if (showLegend) {
7156         legend.width(availableWidth - controlWidth());
7157
7158         if (multibar.barColor())
7159           data.forEach(function(series,i) {
7160             series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
7161           })
7162
7163         g.select('.nv-legendWrap')
7164             .datum(data)
7165             .call(legend);
7166
7167         if ( margin.top != legend.height()) {
7168           margin.top = legend.height();
7169           availableHeight = (height || parseInt(container.style('height')) || 400)
7170                              - margin.top - margin.bottom;
7171         }
7172
7173         g.select('.nv-legendWrap')
7174             .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
7175       }
7176
7177       //------------------------------------------------------------
7178
7179
7180       //------------------------------------------------------------
7181       // Controls
7182
7183       if (showControls) {
7184         var controlsData = [
7185           { key: 'Grouped', disabled: multibar.stacked() },
7186           { key: 'Stacked', disabled: !multibar.stacked() }
7187         ];
7188
7189         controls.width(controlWidth()).color(['#444', '#444', '#444']);
7190         g.select('.nv-controlsWrap')
7191             .datum(controlsData)
7192             .attr('transform', 'translate(0,' + (-margin.top) +')')
7193             .call(controls);
7194       }
7195
7196       //------------------------------------------------------------
7197
7198
7199       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7200
7201
7202       //------------------------------------------------------------
7203       // Main Chart Component(s)
7204
7205       multibar
7206         .disabled(data.map(function(series) { return series.disabled }))
7207         .width(availableWidth)
7208         .height(availableHeight)
7209         .color(data.map(function(d,i) {
7210           return d.color || color(d, i);
7211         }).filter(function(d,i) { return !data[i].disabled }))
7212
7213
7214       var barsWrap = g.select('.nv-barsWrap')
7215           .datum(data.filter(function(d) { return !d.disabled }))
7216
7217       d3.transition(barsWrap).call(multibar);
7218
7219       //------------------------------------------------------------
7220
7221
7222       //------------------------------------------------------------
7223       // Setup Axes
7224
7225       xAxis
7226         .scale(x)
7227         .ticks( availableWidth / 100 )
7228         .tickSize(-availableHeight, 0);
7229
7230       g.select('.nv-x.nv-axis')
7231           .attr('transform', 'translate(0,' + y.range()[0] + ')');
7232       d3.transition(g.select('.nv-x.nv-axis'))
7233           .call(xAxis);
7234
7235       var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
7236
7237       xTicks
7238           .selectAll('line, text')
7239           .style('opacity', 1)
7240
7241       if (staggerLabels) {
7242           var getTranslate = function(x,y) {
7243               return "translate(" + x + "," + y + ")";
7244           };
7245
7246           var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
7247           // Issue #140
7248           xTicks
7249             .selectAll("text")
7250             .attr('transform', function(d,i,j) { 
7251                 return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
7252               });
7253
7254           var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
7255           g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
7256             .attr("transform", function(d,i) {
7257                 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
7258             });
7259       }
7260
7261
7262       if (reduceXTicks)
7263         xTicks
7264           .filter(function(d,i) {
7265               return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
7266             })
7267           .selectAll('text, line')
7268           .style('opacity', 0);
7269
7270       if(rotateLabels)
7271         xTicks
7272           .selectAll('text')
7273           .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
7274           .attr('text-anchor', rotateLabels > 0 ? 'start' : 'end');
7275       
7276       g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
7277           .style('opacity', 1);
7278
7279       yAxis
7280         .scale(y)
7281         .ticks( availableHeight / 36 )
7282         .tickSize( -availableWidth, 0);
7283
7284       d3.transition(g.select('.nv-y.nv-axis'))
7285           .call(yAxis);
7286
7287       //------------------------------------------------------------
7288
7289
7290
7291       //============================================================
7292       // Event Handling/Dispatching (in chart's scope)
7293       //------------------------------------------------------------
7294
7295       legend.dispatch.on('legendClick', function(d,i) {
7296         d.disabled = !d.disabled;
7297
7298         if (!data.filter(function(d) { return !d.disabled }).length) {
7299           data.map(function(d) {
7300             d.disabled = false;
7301             wrap.selectAll('.nv-series').classed('disabled', false);
7302             return d;
7303           });
7304         }
7305
7306         state.disabled = data.map(function(d) { return !!d.disabled });
7307         dispatch.stateChange(state);
7308
7309         chart.update();
7310       });
7311
7312       legend.dispatch.on('legendDblclick', function(d) {
7313           //Double clicking should always enable current series, and disabled all others.
7314           data.forEach(function(d) {
7315              d.disabled = true;
7316           });
7317           d.disabled = false;  
7318
7319           state.disabled = data.map(function(d) { return !!d.disabled });
7320           dispatch.stateChange(state);
7321           chart.update();
7322       });
7323
7324
7325       controls.dispatch.on('legendClick', function(d,i) {
7326         if (!d.disabled) return;
7327         controlsData = controlsData.map(function(s) {
7328           s.disabled = true;
7329           return s;
7330         });
7331         d.disabled = false;
7332
7333         switch (d.key) {
7334           case 'Grouped':
7335             multibar.stacked(false);
7336             break;
7337           case 'Stacked':
7338             multibar.stacked(true);
7339             break;
7340         }
7341
7342         state.stacked = multibar.stacked();
7343         dispatch.stateChange(state);
7344
7345         chart.update();
7346       });
7347
7348       dispatch.on('tooltipShow', function(e) {
7349         if (tooltips) showTooltip(e, that.parentNode)
7350       });
7351
7352       // Update chart from a state object passed to event handler
7353       dispatch.on('changeState', function(e) {
7354
7355         if (typeof e.disabled !== 'undefined') {
7356           data.forEach(function(series,i) {
7357             series.disabled = e.disabled[i];
7358           });
7359
7360           state.disabled = e.disabled;
7361         }
7362
7363         if (typeof e.stacked !== 'undefined') {
7364           multibar.stacked(e.stacked);
7365           state.stacked = e.stacked;
7366         }
7367
7368         chart.update();
7369       });
7370
7371       //============================================================
7372
7373
7374     });
7375
7376     return chart;
7377   }
7378
7379
7380   //============================================================
7381   // Event Handling/Dispatching (out of chart's scope)
7382   //------------------------------------------------------------
7383
7384   multibar.dispatch.on('elementMouseover.tooltip', function(e) {
7385     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
7386     dispatch.tooltipShow(e);
7387   });
7388
7389   multibar.dispatch.on('elementMouseout.tooltip', function(e) {
7390     dispatch.tooltipHide(e);
7391   });
7392   dispatch.on('tooltipHide', function() {
7393     if (tooltips) nv.tooltip.cleanup();
7394   });
7395
7396   //============================================================
7397
7398
7399   //============================================================
7400   // Expose Public Variables
7401   //------------------------------------------------------------
7402
7403   // expose chart's sub-components
7404   chart.dispatch = dispatch;
7405   chart.multibar = multibar;
7406   chart.legend = legend;
7407   chart.xAxis = xAxis;
7408   chart.yAxis = yAxis;
7409
7410   d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay', 'barColor');
7411
7412   chart.margin = function(_) {
7413     if (!arguments.length) return margin;
7414     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7415     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7416     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7417     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7418     return chart;
7419   };
7420
7421   chart.width = function(_) {
7422     if (!arguments.length) return width;
7423     width = _;
7424     return chart;
7425   };
7426
7427   chart.height = function(_) {
7428     if (!arguments.length) return height;
7429     height = _;
7430     return chart;
7431   };
7432
7433   chart.color = function(_) {
7434     if (!arguments.length) return color;
7435     color = nv.utils.getColor(_);
7436     legend.color(color);
7437     return chart;
7438   };
7439
7440   chart.showControls = function(_) {
7441     if (!arguments.length) return showControls;
7442     showControls = _;
7443     return chart;
7444   };
7445
7446   chart.showLegend = function(_) {
7447     if (!arguments.length) return showLegend;
7448     showLegend = _;
7449     return chart;
7450   };
7451
7452   chart.logScale = function(_) {
7453     if (!arguments.length) return logScale;
7454     logScale = _;
7455     return chart;
7456   };
7457
7458   chart.reduceXTicks= function(_) {
7459     if (!arguments.length) return reduceXTicks;
7460     reduceXTicks = _;
7461     return chart;
7462   };
7463
7464   chart.rotateLabels = function(_) {
7465     if (!arguments.length) return rotateLabels;
7466     rotateLabels = _;
7467     return chart;
7468   }
7469
7470   chart.staggerLabels = function(_) {
7471     if (!arguments.length) return staggerLabels;
7472     staggerLabels = _;
7473     return chart;
7474   };
7475
7476   chart.tooltip = function(_) {
7477     if (!arguments.length) return tooltip;
7478     tooltip = _;
7479     return chart;
7480   };
7481
7482   chart.tooltips = function(_) {
7483     if (!arguments.length) return tooltips;
7484     tooltips = _;
7485     return chart;
7486   };
7487
7488   chart.tooltipContent = function(_) {
7489     if (!arguments.length) return tooltip;
7490     tooltip = _;
7491     return chart;
7492   };
7493
7494   chart.state = function(_) {
7495     if (!arguments.length) return state;
7496     state = _;
7497     return chart;
7498   };
7499
7500   chart.defaultState = function(_) {
7501     if (!arguments.length) return defaultState;
7502     defaultState = _;
7503     return chart;
7504   };
7505   
7506   chart.noData = function(_) {
7507     if (!arguments.length) return noData;
7508     noData = _;
7509     return chart;
7510   };
7511
7512   //============================================================
7513
7514
7515   return chart;
7516 }
7517
7518 nv.models.multiBarHorizontal = function() {
7519
7520   //============================================================
7521   // Public Variables with Default Settings
7522   //------------------------------------------------------------
7523
7524   var margin = {top: 0, right: 0, bottom: 0, left: 0}
7525     , width = 960
7526     , height = 500
7527     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7528     , x = d3.scale.ordinal()
7529     , y = d3.scale.linear()
7530     , getX = function(d) { return d.x }
7531     , getY = function(d) { return d.y }
7532     , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7533     , color = nv.utils.defaultColor()
7534     , barColor = null // adding the ability to set the color for each rather than the whole group
7535     , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7536     , stacked = false
7537     , showValues = false
7538     , valuePadding = 60
7539     , valueFormat = d3.format(',.2f')
7540     , delay = 1200
7541     , xDomain
7542     , yDomain
7543     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7544     ;
7545
7546   //============================================================
7547
7548
7549   //============================================================
7550   // Private Variables
7551   //------------------------------------------------------------
7552
7553   var x0, y0 //used to store previous scales
7554       ;
7555
7556   //============================================================
7557
7558
7559   function chart(selection) {
7560     selection.each(function(data) {
7561       var availableWidth = width - margin.left - margin.right,
7562           availableHeight = height - margin.top - margin.bottom,
7563           container = d3.select(this);
7564
7565
7566       if (stacked)
7567         data = d3.layout.stack()
7568                  .offset('zero')
7569                  .values(function(d){ return d.values })
7570                  .y(getY)
7571                  (data);
7572
7573
7574       //add series index to each data point for reference
7575       data = data.map(function(series, i) {
7576         series.values = series.values.map(function(point) {
7577           point.series = i;
7578           return point;
7579         });
7580         return series;
7581       });
7582
7583
7584
7585       //------------------------------------------------------------
7586       // HACK for negative value stacking
7587       if (stacked)
7588         data[0].values.map(function(d,i) {
7589           var posBase = 0, negBase = 0;
7590           data.map(function(d) {
7591             var f = d.values[i]
7592             f.size = Math.abs(f.y);
7593             if (f.y<0)  {
7594               f.y1 = negBase - f.size;
7595               negBase = negBase - f.size;
7596             } else
7597             { 
7598               f.y1 = posBase;
7599               posBase = posBase + f.size;
7600             }
7601           });
7602         });
7603
7604
7605
7606       //------------------------------------------------------------
7607       // Setup Scales
7608
7609       // remap and flatten the data for use in calculating the scales' domains
7610       var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7611             data.map(function(d) {
7612               return d.values.map(function(d,i) {
7613                 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7614               })
7615             });
7616
7617       x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7618           .rangeBands([0, availableHeight], .1);
7619
7620       //y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
7621       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)))
7622
7623       if (showValues && !stacked)
7624         y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
7625       else
7626         y.range([0, availableWidth]);
7627
7628       x0 = x0 || x;
7629       y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
7630
7631       //------------------------------------------------------------
7632
7633
7634       //------------------------------------------------------------
7635       // Setup containers and skeleton of chart
7636
7637       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
7638       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
7639       var defsEnter = wrapEnter.append('defs');
7640       var gEnter = wrapEnter.append('g');
7641       var g = wrap.select('g');
7642
7643       gEnter.append('g').attr('class', 'nv-groups');
7644
7645       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7646
7647       //------------------------------------------------------------
7648
7649
7650
7651       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7652           .data(function(d) { return d }, function(d) { return d.key });
7653       groups.enter().append('g')
7654           .style('stroke-opacity', 1e-6)
7655           .style('fill-opacity', 1e-6);
7656       d3.transition(groups.exit())
7657           .style('stroke-opacity', 1e-6)
7658           .style('fill-opacity', 1e-6)
7659           .remove();
7660       groups
7661           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7662           .classed('hover', function(d) { return d.hover })
7663           .style('fill', function(d,i){ return color(d, i) })
7664           .style('stroke', function(d,i){ return color(d, i) });
7665       d3.transition(groups)
7666           .style('stroke-opacity', 1)
7667           .style('fill-opacity', .75);
7668
7669
7670       var bars = groups.selectAll('g.nv-bar')
7671           .data(function(d) { return d.values });
7672
7673       bars.exit().remove();
7674
7675
7676       var barsEnter = bars.enter().append('g')
7677           .attr('transform', function(d,i,j) {
7678               return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
7679           });
7680
7681       barsEnter.append('rect')
7682           .attr('width', 0)
7683           .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
7684
7685       bars
7686           .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7687             d3.select(this).classed('hover', true);
7688             dispatch.elementMouseover({
7689               value: getY(d,i),
7690               point: d,
7691               series: data[d.series],
7692               pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
7693               pointIndex: i,
7694               seriesIndex: d.series,
7695               e: d3.event
7696             });
7697           })
7698           .on('mouseout', function(d,i) {
7699             d3.select(this).classed('hover', false);
7700             dispatch.elementMouseout({
7701               value: getY(d,i),
7702               point: d,
7703               series: data[d.series],
7704               pointIndex: i,
7705               seriesIndex: d.series,
7706               e: d3.event
7707             });
7708           })
7709           .on('click', function(d,i) {
7710             dispatch.elementClick({
7711               value: getY(d,i),
7712               point: d,
7713               series: data[d.series],
7714               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
7715               pointIndex: i,
7716               seriesIndex: d.series,
7717               e: d3.event
7718             });
7719             d3.event.stopPropagation();
7720           })
7721           .on('dblclick', function(d,i) {
7722             dispatch.elementDblClick({
7723               value: getY(d,i),
7724               point: d,
7725               series: data[d.series],
7726               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
7727               pointIndex: i,
7728               seriesIndex: d.series,
7729               e: d3.event
7730             });
7731             d3.event.stopPropagation();
7732           });
7733
7734
7735       barsEnter.append('text');
7736
7737       if (showValues && !stacked) {
7738         bars.select('text')
7739             .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
7740             .attr('y', x.rangeBand() / (data.length * 2))
7741             .attr('dy', '.32em')
7742             .text(function(d,i) { return valueFormat(getY(d,i)) })
7743         d3.transition(bars)
7744             //.delay(function(d,i) { return i * delay / data[0].values.length })
7745           .select('text')
7746             .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
7747       } else {
7748         //bars.selectAll('text').remove();
7749         bars.selectAll('text').text('');
7750       }
7751
7752       bars
7753           .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7754
7755       if (barColor) {
7756         if (!disabled) disabled = data.map(function() { return true });
7757         bars
7758           //.style('fill', barColor)
7759           //.style('stroke', barColor)
7760           //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); })
7761           //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); })
7762           .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(); })
7763           .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(); });
7764       }
7765
7766       if (stacked)
7767         d3.transition(bars)
7768             //.delay(function(d,i) { return i * delay / data[0].values.length })
7769             .attr('transform', function(d,i) {
7770               //return 'translate(' + y(d.y0) + ',0)'
7771               //return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')'
7772               return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
7773             })
7774           .select('rect')
7775             .attr('width', function(d,i) {
7776               return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
7777             })
7778             .attr('height', x.rangeBand() );
7779       else
7780         d3.transition(bars)
7781           //.delay(function(d,i) { return i * delay / data[0].values.length })
7782             .attr('transform', function(d,i) {
7783               //TODO: stacked must be all positive or all negative, not both?
7784               return 'translate(' + 
7785               (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
7786               + ',' +
7787               (d.series * x.rangeBand() / data.length
7788               +
7789               x(getX(d,i)) )
7790               + ')'
7791             })
7792           .select('rect')
7793             .attr('height', x.rangeBand() / data.length )
7794             .attr('width', function(d,i) {
7795               return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
7796             });
7797
7798
7799       //store old scales for use in transitions on update
7800       x0 = x.copy();
7801       y0 = y.copy();
7802
7803     });
7804
7805     return chart;
7806   }
7807
7808
7809   //============================================================
7810   // Expose Public Variables
7811   //------------------------------------------------------------
7812
7813   chart.dispatch = dispatch;
7814
7815   chart.x = function(_) {
7816     if (!arguments.length) return getX;
7817     getX = _;
7818     return chart;
7819   };
7820
7821   chart.y = function(_) {
7822     if (!arguments.length) return getY;
7823     getY = _;
7824     return chart;
7825   };
7826
7827   chart.margin = function(_) {
7828     if (!arguments.length) return margin;
7829     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
7830     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
7831     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7832     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
7833     return chart;
7834   };
7835
7836   chart.width = function(_) {
7837     if (!arguments.length) return width;
7838     width = _;
7839     return chart;
7840   };
7841
7842   chart.height = function(_) {
7843     if (!arguments.length) return height;
7844     height = _;
7845     return chart;
7846   };
7847
7848   chart.xScale = function(_) {
7849     if (!arguments.length) return x;
7850     x = _;
7851     return chart;
7852   };
7853
7854   chart.yScale = function(_) {
7855     if (!arguments.length) return y;
7856     y = _;
7857     return chart;
7858   };
7859
7860   chart.xDomain = function(_) {
7861     if (!arguments.length) return xDomain;
7862     xDomain = _;
7863     return chart;
7864   };
7865
7866   chart.yDomain = function(_) {
7867     if (!arguments.length) return yDomain;
7868     yDomain = _;
7869     return chart;
7870   };
7871
7872   chart.forceY = function(_) {
7873     if (!arguments.length) return forceY;
7874     forceY = _;
7875     return chart;
7876   };
7877
7878   chart.stacked = function(_) {
7879     if (!arguments.length) return stacked;
7880     stacked = _;
7881     return chart;
7882   };
7883
7884   chart.color = function(_) {
7885     if (!arguments.length) return color;
7886     color = nv.utils.getColor(_);
7887     return chart;
7888   };
7889
7890   chart.barColor = function(_) {
7891     if (!arguments.length) return barColor;
7892     barColor = nv.utils.getColor(_);
7893     return chart;
7894   };
7895
7896   chart.disabled = function(_) {
7897     if (!arguments.length) return disabled;
7898     disabled = _;
7899     return chart;
7900   };
7901
7902   chart.id = function(_) {
7903     if (!arguments.length) return id;
7904     id = _;
7905     return chart;
7906   };
7907
7908   chart.delay = function(_) {
7909     if (!arguments.length) return delay;
7910     delay = _;
7911     return chart;
7912   };
7913
7914   chart.showValues = function(_) {
7915     if (!arguments.length) return showValues;
7916     showValues = _;
7917     return chart;
7918   };
7919
7920   chart.valueFormat= function(_) {
7921     if (!arguments.length) return valueFormat;
7922     valueFormat = _;
7923     return chart;
7924   };
7925
7926   chart.valuePadding = function(_) {
7927     if (!arguments.length) return valuePadding;
7928     valuePadding = _;
7929     return chart;
7930   };
7931
7932   //============================================================
7933
7934
7935   return chart;
7936 }
7937
7938 nv.models.multiBarHorizontalChart = function() {
7939
7940   //============================================================
7941   // Public Variables with Default Settings
7942   //------------------------------------------------------------
7943
7944   var multibar = nv.models.multiBarHorizontal()
7945     , xAxis = nv.models.axis()
7946     , yAxis = nv.models.axis()
7947     , legend = nv.models.legend().height(30)
7948     , controls = nv.models.legend().height(30)
7949     ;
7950
7951   var margin = {top: 30, right: 20, bottom: 50, left: 60}
7952     , width = null
7953     , height = null
7954     , color = nv.utils.defaultColor()
7955     , showControls = true
7956     , showLegend = true
7957         , logScale = false
7958     , stacked = false
7959     , tooltips = true
7960     , tooltip = function(key, x, y, e, graph, logScale) {
7961         //alert(y+ " " + logScale + " " + Math.pow(10,y) +  " " + Math.pow(10,y).toFixed(0));
7962         if(logScale) {
7963                     //var fmt = d3.format(',.2f');
7964                         return '<h3>' + key + ' - ' + x + '</h3>' +
7965                '<p>' +  yAxis.tickFormat()(Math.pow(10,y)) + '</p>'
7966             } else {
7967         return '<h3>' + key + ' - ' + x + '</h3>' +
7968                '<p>' +  y + '</p>'
7969       }
7970
7971       }
7972     , x //can be accessed via chart.xScale()
7973     , y //can be accessed via chart.yScale()
7974     , state = { stacked: stacked }
7975     , defaultState = null
7976     , noData = 'No Data Available.'
7977     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
7978     , controlWidth = function() { return showControls ? 180 : 0 }
7979     ;
7980
7981   multibar
7982     .stacked(stacked)
7983     ;
7984   xAxis
7985     .orient('left')
7986     .tickPadding(5)
7987     .highlightZero(false)
7988     .showMaxMin(false)
7989     .tickFormat(function(d) { return d })
7990     ;
7991   yAxis
7992     .orient('bottom')
7993     .tickFormat(d3.format(',.1f'))
7994     ;
7995
7996   //============================================================
7997
7998
7999   //============================================================
8000   // Private Variables
8001   //------------------------------------------------------------
8002
8003   var showTooltip = function(e, offsetElement) {
8004     //var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8005         //alert(offsetElement.offsetLeft + " " + e.pos[0]);
8006         var leftPos = 0;
8007         if(e.pos[0] >=200) leftPos = 200;
8008         else leftPos = e.pos[0];
8009         if(logScale) 
8010                 y = multibar.y()(e.point, e.pointIndex);
8011         else
8012                 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex))     
8013         var left = leftPos + ( offsetElement.offsetLeft || 0 ),
8014         top = e.pos[1] + ( offsetElement.offsetTop || 0),
8015         x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8016         content = tooltip(e.series.key, x, y, e, chart, logScale);
8017                 //alert("from tooltip " + multibar.y()(e.point, e.pointIndex));
8018
8019     nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
8020   };
8021
8022   //============================================================
8023
8024
8025   function chart(selection) {
8026     selection.each(function(data) {
8027       var container = d3.select(this),
8028           that = this;
8029
8030       var availableWidth = (width  || parseInt(container.style('width')) || 960)
8031                              - margin.left - margin.right,
8032           availableHeight = (height || parseInt(container.style('height')) || 400)
8033                              - margin.top - margin.bottom;
8034
8035       chart.update = function() { container.transition().call(chart) };
8036       chart.container = this;
8037
8038       //set state.disabled
8039       state.disabled = data.map(function(d) { return !!d.disabled });
8040
8041       if (!defaultState) {
8042         var key;
8043         defaultState = {};
8044         for (key in state) {
8045           if (state[key] instanceof Array)
8046             defaultState[key] = state[key].slice(0);
8047           else
8048             defaultState[key] = state[key];
8049         }
8050       }
8051
8052       //------------------------------------------------------------
8053       // Display No Data message if there's nothing to show.
8054
8055       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8056         var noDataText = container.selectAll('.nv-noData').data([noData]);
8057
8058         noDataText.enter().append('text')
8059           .attr('class', 'nvd3 nv-noData')
8060           .attr('dy', '-.7em')
8061           .style('text-anchor', 'middle');
8062
8063         noDataText
8064           .attr('x', margin.left + availableWidth / 2)
8065           .attr('y', margin.top + availableHeight / 2)
8066           .text(function(d) { return d });
8067
8068         return chart;
8069       } else {
8070         container.selectAll('.nv-noData').remove();
8071       }
8072
8073       //------------------------------------------------------------
8074
8075
8076       //------------------------------------------------------------
8077       // Setup Scales
8078
8079       x = multibar.xScale();
8080       y = multibar.yScale();
8081
8082       //------------------------------------------------------------
8083
8084
8085       //------------------------------------------------------------
8086       // Setup containers and skeleton of chart
8087
8088       var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8089       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8090       var g = wrap.select('g');
8091
8092       gEnter.append('g').attr('class', 'nv-x nv-axis');
8093       gEnter.append('g').attr('class', 'nv-y nv-axis');
8094       gEnter.append('g').attr('class', 'nv-barsWrap');
8095       gEnter.append('g').attr('class', 'nv-legendWrap');
8096       gEnter.append('g').attr('class', 'nv-controlsWrap');
8097
8098       //------------------------------------------------------------
8099
8100
8101       //------------------------------------------------------------
8102       // Legend
8103
8104       if (showLegend) {
8105         legend.width(availableWidth - controlWidth());
8106
8107         if (multibar.barColor())
8108           data.forEach(function(series,i) {
8109             series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8110           })
8111
8112         g.select('.nv-legendWrap')
8113             .datum(data)
8114             .call(legend);
8115
8116         if ( margin.top != legend.height()) {
8117           margin.top = legend.height();
8118           availableHeight = (height || parseInt(container.style('height')) || 400)
8119                              - margin.top - margin.bottom;
8120         }
8121
8122         g.select('.nv-legendWrap')
8123             .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8124       }
8125
8126       //------------------------------------------------------------
8127
8128
8129       //------------------------------------------------------------
8130       // Controls
8131
8132       if (showControls) {
8133         var controlsData = [
8134           { key: 'Grouped', disabled: multibar.stacked() },
8135           { key: 'Stacked', disabled: !multibar.stacked() }
8136         ];
8137
8138         controls.width(controlWidth()).color(['#444', '#444', '#444']);
8139         g.select('.nv-controlsWrap')
8140             .datum(controlsData)
8141             .attr('transform', 'translate(0,' + (-margin.top) +')')
8142             .call(controls);
8143       }
8144
8145       //------------------------------------------------------------
8146
8147
8148       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8149
8150
8151       //------------------------------------------------------------
8152       // Main Chart Component(s)
8153
8154       multibar
8155         .disabled(data.map(function(series) { return series.disabled }))
8156         .width(availableWidth)
8157         .height(availableHeight)
8158         .color(data.map(function(d,i) {
8159           return d.color || color(d, i);
8160         }).filter(function(d,i) { return !data[i].disabled }))
8161
8162
8163       var barsWrap = g.select('.nv-barsWrap')
8164           .datum(data.filter(function(d) { return !d.disabled }))
8165
8166       d3.transition(barsWrap).call(multibar);
8167
8168       //------------------------------------------------------------
8169
8170
8171       //------------------------------------------------------------
8172       // Setup Axes
8173
8174       xAxis
8175         .scale(x)
8176         .ticks( availableHeight / 24 )
8177         .tickSize(-availableWidth, 0);
8178
8179       d3.transition(g.select('.nv-x.nv-axis'))
8180           .call(xAxis);
8181
8182       var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
8183
8184       xTicks
8185           .selectAll('line, text')
8186           .style('opacity', 1)
8187
8188
8189       yAxis
8190         .scale(y)
8191         .ticks( availableWidth / 100 )
8192         .tickSize( -availableHeight, 0);
8193
8194       g.select('.nv-y.nv-axis')
8195           .attr('transform', 'translate(0,' + availableHeight + ')');
8196       d3.transition(g.select('.nv-y.nv-axis'))
8197           .call(yAxis);
8198
8199       //------------------------------------------------------------
8200
8201
8202
8203       //============================================================
8204       // Event Handling/Dispatching (in chart's scope)
8205       //------------------------------------------------------------
8206
8207       legend.dispatch.on('legendClick', function(d,i) {
8208         d.disabled = !d.disabled;
8209
8210         if (!data.filter(function(d) { return !d.disabled }).length) {
8211           data.map(function(d) {
8212             d.disabled = false;
8213             wrap.selectAll('.nv-series').classed('disabled', false);
8214             return d;
8215           });
8216         }
8217
8218         state.disabled = data.map(function(d) { return !!d.disabled });
8219         dispatch.stateChange(state);
8220
8221         chart.update();
8222       });
8223
8224       legend.dispatch.on('legendDblclick', function(d) {
8225           //Double clicking should always enable current series, and disabled all others.
8226           data.forEach(function(d) {
8227              d.disabled = true;
8228           });
8229           d.disabled = false;  
8230
8231           state.disabled = data.map(function(d) { return !!d.disabled });
8232           dispatch.stateChange(state);
8233           chart.update();
8234       });
8235
8236       controls.dispatch.on('legendClick', function(d,i) {
8237         if (!d.disabled) return;
8238         controlsData = controlsData.map(function(s) {
8239           s.disabled = true;
8240           return s;
8241         });
8242         d.disabled = false;
8243
8244         switch (d.key) {
8245           case 'Grouped':
8246             multibar.stacked(false);
8247             break;
8248           case 'Stacked':
8249             multibar.stacked(true);
8250             break;
8251         }
8252
8253         state.stacked = multibar.stacked();
8254         dispatch.stateChange(state);
8255
8256         chart.update();
8257       });
8258
8259       dispatch.on('tooltipShow', function(e) {
8260         if (tooltips) showTooltip(e, that.parentNode);
8261       });
8262
8263       // Update chart from a state object passed to event handler
8264       dispatch.on('changeState', function(e) {
8265
8266         if (typeof e.disabled !== 'undefined') {
8267           data.forEach(function(series,i) {
8268             series.disabled = e.disabled[i];
8269           });
8270
8271           state.disabled = e.disabled;
8272         }
8273
8274         if (typeof e.stacked !== 'undefined') {
8275           multibar.stacked(e.stacked);
8276           state.stacked = e.stacked;
8277         }
8278
8279         selection.call(chart);
8280       });
8281       //============================================================
8282
8283
8284     });
8285
8286     return chart;
8287   }
8288
8289
8290   //============================================================
8291   // Event Handling/Dispatching (out of chart's scope)
8292   //------------------------------------------------------------
8293
8294   multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8295     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8296     dispatch.tooltipShow(e);
8297   });
8298
8299   multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8300     dispatch.tooltipHide(e);
8301   });
8302   dispatch.on('tooltipHide', function() {
8303     if (tooltips) nv.tooltip.cleanup();
8304   });
8305
8306   //============================================================
8307
8308
8309   //============================================================
8310   // Expose Public Variables
8311   //------------------------------------------------------------
8312
8313   // expose chart's sub-components
8314   chart.dispatch = dispatch;
8315   chart.multibar = multibar;
8316   chart.legend = legend;
8317   chart.xAxis = xAxis;
8318   chart.yAxis = yAxis;
8319
8320   d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor');
8321
8322   chart.margin = function(_) {
8323     if (!arguments.length) return margin;
8324     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
8325     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
8326     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8327     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
8328     return chart;
8329   };
8330
8331   chart.width = function(_) {
8332     if (!arguments.length) return width;
8333     width = _;
8334     return chart;
8335   };
8336
8337   chart.height = function(_) {
8338     if (!arguments.length) return height;
8339     height = _;
8340     return chart;
8341   };
8342
8343   chart.color = function(_) {
8344     if (!arguments.length) return color;
8345     color = nv.utils.getColor(_);
8346     legend.color(color);
8347     return chart;
8348   };
8349
8350   chart.showControls = function(_) {
8351     if (!arguments.length) return showControls;
8352     showControls = _;
8353     return chart;
8354   };
8355
8356   chart.showLegend = function(_) {
8357     if (!arguments.length) return showLegend;
8358     showLegend = _;
8359     return chart;
8360   };
8361
8362   chart.logScale = function(_) {
8363     if (!arguments.length) return logScale;
8364     logScale = _;
8365     return chart;
8366   };
8367
8368   chart.tooltip = function(_) {
8369     if (!arguments.length) return tooltip;
8370     tooltip = _;
8371     return chart;
8372   };
8373
8374   chart.tooltips = function(_) {
8375     if (!arguments.length) return tooltips;
8376     tooltips = _;
8377     return chart;
8378   };
8379
8380   chart.tooltipContent = function(_) {
8381     if (!arguments.length) return tooltip;
8382     tooltip = _;
8383     return chart;
8384   };
8385
8386   chart.state = function(_) {
8387     if (!arguments.length) return state;
8388     state = _;
8389     return chart;
8390   };
8391
8392   chart.defaultState = function(_) {
8393     if (!arguments.length) return defaultState;
8394     defaultState = _;
8395     return chart;
8396   };
8397
8398   chart.noData = function(_) {
8399     if (!arguments.length) return noData;
8400     noData = _;
8401     return chart;
8402   };
8403
8404   //============================================================
8405
8406
8407   return chart;
8408 }
8409 nv.models.multiChart = function() {
8410
8411   //============================================================
8412   // Public Variables with Default Settings
8413   //------------------------------------------------------------
8414
8415   var margin = {top: 30, right: 20, bottom: 50, left: 60},
8416       color = d3.scale.category20().range(),
8417       width = null, 
8418       height = null,
8419       showLegend = true,
8420       tooltips = true,
8421       tooltip = function(key, x, y, e, graph) {
8422         return '<h3>' + key + '</h3>' +
8423                '<p>' +  y + ' at ' + x + '</p>'
8424       },
8425       x, y; //can be accessed via chart.lines.[x/y]Scale()
8426
8427   //============================================================
8428   // Private Variables
8429   //------------------------------------------------------------
8430
8431   var x = d3.scale.linear(),
8432       yScale1 = d3.scale.linear(),
8433       yScale2 = d3.scale.linear(),
8434
8435       lines1 = nv.models.line().yScale(yScale1),
8436       lines2 = nv.models.line().yScale(yScale2),
8437
8438       bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
8439       bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
8440
8441       stack1 = nv.models.stackedArea().yScale(yScale1),
8442       stack2 = nv.models.stackedArea().yScale(yScale2),
8443
8444       xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
8445       yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
8446       yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
8447
8448       legend = nv.models.legend().height(30),
8449       dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
8450
8451   var showTooltip = function(e, offsetElement) {
8452     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8453         top = e.pos[1] + ( offsetElement.offsetTop || 0),
8454         x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
8455         y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
8456         content = tooltip(e.series.key, x, y, e, chart);
8457
8458     nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
8459   };
8460
8461   function chart(selection) {
8462     selection.each(function(data) {
8463       var container = d3.select(this),
8464           that = this;
8465
8466       chart.update = function() { container.transition().call(chart); };
8467       chart.container = this;
8468
8469       var availableWidth = (width  || parseInt(container.style('width')) || 960)
8470                              - margin.left - margin.right,
8471           availableHeight = (height || parseInt(container.style('height')) || 400)
8472                              - margin.top - margin.bottom;
8473
8474       var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
8475       var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
8476       var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
8477       var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
8478       var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
8479       var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
8480
8481       var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
8482             .map(function(d) {
8483               return d.values.map(function(d,i) {
8484                 return { x: d.x, y: d.y }
8485               })
8486             })
8487
8488       var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
8489             .map(function(d) {
8490               return d.values.map(function(d,i) {
8491                 return { x: d.x, y: d.y }
8492               })
8493             })
8494
8495       x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
8496           .range([0, availableWidth]);
8497
8498       var wrap = container.selectAll('g.wrap.multiChart').data([data]);
8499       var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
8500
8501       gEnter.append('g').attr('class', 'x axis');
8502       gEnter.append('g').attr('class', 'y1 axis');
8503       gEnter.append('g').attr('class', 'y2 axis');
8504       gEnter.append('g').attr('class', 'lines1Wrap');
8505       gEnter.append('g').attr('class', 'lines2Wrap');
8506       gEnter.append('g').attr('class', 'bars1Wrap');
8507       gEnter.append('g').attr('class', 'bars2Wrap');
8508       gEnter.append('g').attr('class', 'stack1Wrap');
8509       gEnter.append('g').attr('class', 'stack2Wrap');
8510       gEnter.append('g').attr('class', 'legendWrap');
8511
8512       var g = wrap.select('g');
8513
8514       if (showLegend) {
8515         legend.width( availableWidth / 2 );
8516
8517         g.select('.legendWrap')
8518             .datum(data.map(function(series) { 
8519               series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
8520               series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
8521               return series;
8522             }))
8523           .call(legend);
8524
8525         if ( margin.top != legend.height()) {
8526           margin.top = legend.height();
8527           availableHeight = (height || parseInt(container.style('height')) || 400)
8528                              - margin.top - margin.bottom;
8529         }
8530
8531         g.select('.legendWrap')
8532             .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
8533       }
8534
8535
8536       lines1
8537         .width(availableWidth)
8538         .height(availableHeight)
8539         .interpolate("monotone")
8540         .color(data.map(function(d,i) {
8541           return d.color || color[i % color.length];
8542         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
8543
8544       lines2
8545         .width(availableWidth)
8546         .height(availableHeight)
8547         .interpolate("monotone")
8548         .color(data.map(function(d,i) {
8549           return d.color || color[i % color.length];
8550         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
8551
8552       bars1
8553         .width(availableWidth)
8554         .height(availableHeight)
8555         .color(data.map(function(d,i) {
8556           return d.color || color[i % color.length];
8557         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
8558
8559       bars2
8560         .width(availableWidth)
8561         .height(availableHeight)
8562         .color(data.map(function(d,i) {
8563           return d.color || color[i % color.length];
8564         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
8565
8566       stack1
8567         .width(availableWidth)
8568         .height(availableHeight)
8569         .color(data.map(function(d,i) {
8570           return d.color || color[i % color.length];
8571         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
8572
8573       stack2
8574         .width(availableWidth)
8575         .height(availableHeight)
8576         .color(data.map(function(d,i) {
8577           return d.color || color[i % color.length];
8578         }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
8579
8580       g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8581
8582
8583       var lines1Wrap = g.select('.lines1Wrap')
8584           .datum(dataLines1)
8585       var bars1Wrap = g.select('.bars1Wrap')
8586           .datum(dataBars1)
8587       var stack1Wrap = g.select('.stack1Wrap')
8588           .datum(dataStack1)
8589
8590       var lines2Wrap = g.select('.lines2Wrap')
8591           .datum(dataLines2)
8592       var bars2Wrap = g.select('.bars2Wrap')
8593           .datum(dataBars2)
8594       var stack2Wrap = g.select('.stack2Wrap')
8595           .datum(dataStack2)
8596
8597       var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
8598         return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
8599       }).concat([{x:0, y:0}]) : []
8600       var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
8601         return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
8602       }).concat([{x:0, y:0}]) : []
8603
8604       yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
8605               .range([0, availableHeight])
8606
8607       yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
8608               .range([0, availableHeight])
8609
8610       lines1.yDomain(yScale1.domain())
8611       bars1.yDomain(yScale1.domain())
8612       stack1.yDomain(yScale1.domain())
8613
8614       lines2.yDomain(yScale2.domain())
8615       bars2.yDomain(yScale2.domain())
8616       stack2.yDomain(yScale2.domain())
8617
8618       if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
8619       if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
8620
8621       if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
8622       if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
8623
8624       if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
8625       if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
8626       
8627
8628
8629       xAxis
8630         .ticks( availableWidth / 100 )
8631         .tickSize(-availableHeight, 0);
8632
8633       g.select('.x.axis')
8634           .attr('transform', 'translate(0,' + availableHeight + ')');
8635       d3.transition(g.select('.x.axis'))
8636           .call(xAxis);
8637
8638       yAxis1
8639         .ticks( availableHeight / 36 )
8640         .tickSize( -availableWidth, 0);
8641
8642
8643       d3.transition(g.select('.y1.axis'))
8644           .call(yAxis1);
8645
8646       yAxis2
8647         .ticks( availableHeight / 36 )
8648         .tickSize( -availableWidth, 0);
8649
8650       d3.transition(g.select('.y2.axis'))
8651           .call(yAxis2);
8652
8653       g.select('.y2.axis')
8654           .style('opacity', series2.length ? 1 : 0)
8655           .attr('transform', 'translate(' + x.range()[1] + ',0)');
8656
8657       legend.dispatch.on('legendClick', function(d,i) { 
8658         d.disabled = !d.disabled;
8659                 if (series2.length <= 0) {
8660         if (!data.filter(function(d) { return !d.disabled }).length) {
8661           data.map(function(d) {
8662             d.disabled = false;
8663             wrap.selectAll('.series').classed('disabled', false);
8664             return d;
8665           });
8666         }
8667         chart.update();
8668                 }
8669       });
8670
8671       dispatch.on('tooltipShow', function(e) {
8672         if (tooltips) showTooltip(e, that.parentNode);
8673       });
8674
8675     });
8676
8677     return chart;
8678   }
8679
8680
8681   //============================================================
8682   // Event Handling/Dispatching (out of chart's scope)
8683   //------------------------------------------------------------
8684
8685   lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8686     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8687     dispatch.tooltipShow(e);
8688   });
8689
8690   lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8691     dispatch.tooltipHide(e);
8692   });
8693
8694   lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8695     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8696     dispatch.tooltipShow(e);
8697   });
8698
8699   lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8700     dispatch.tooltipHide(e);
8701   });
8702
8703   bars1.dispatch.on('elementMouseover.tooltip', function(e) {
8704     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8705     dispatch.tooltipShow(e);
8706   });
8707
8708   bars1.dispatch.on('elementMouseout.tooltip', function(e) {
8709     dispatch.tooltipHide(e);
8710   });
8711
8712   bars2.dispatch.on('elementMouseover.tooltip', function(e) {
8713     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8714     dispatch.tooltipShow(e);
8715   });
8716
8717   bars2.dispatch.on('elementMouseout.tooltip', function(e) {
8718     dispatch.tooltipHide(e);
8719   });
8720
8721   stack1.dispatch.on('tooltipShow', function(e) {
8722     //disable tooltips when value ~= 0
8723     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8724     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
8725       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8726       return false;
8727     }
8728
8729     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8730     dispatch.tooltipShow(e);
8731   });
8732
8733   stack1.dispatch.on('tooltipHide', function(e) {
8734     dispatch.tooltipHide(e);
8735   });
8736
8737   stack2.dispatch.on('tooltipShow', function(e) {
8738     //disable tooltips when value ~= 0
8739     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8740     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
8741       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8742       return false;
8743     }
8744
8745     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8746     dispatch.tooltipShow(e);
8747   });
8748
8749   stack2.dispatch.on('tooltipHide', function(e) {
8750     dispatch.tooltipHide(e);
8751   });
8752
8753     lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8754     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8755     dispatch.tooltipShow(e);
8756   });
8757
8758   lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8759     dispatch.tooltipHide(e);
8760   });
8761
8762   lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8763     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
8764     dispatch.tooltipShow(e);
8765   });
8766
8767   lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8768     dispatch.tooltipHide(e);
8769   });
8770
8771   dispatch.on('tooltipHide', function() {
8772     if (tooltips) nv.tooltip.cleanup();
8773   });
8774
8775
8776
8777   //============================================================
8778   // Global getters and setters
8779   //------------------------------------------------------------
8780
8781   chart.dispatch = dispatch;
8782   chart.lines1 = lines1;
8783   chart.lines2 = lines2;
8784   chart.bars1 = bars1;
8785   chart.bars2 = bars2;
8786   chart.stack1 = stack1;
8787   chart.stack2 = stack2;
8788   chart.xAxis = xAxis;
8789   chart.yAxis1 = yAxis1;
8790   chart.yAxis2 = yAxis2;
8791
8792   chart.x = function(_) {
8793     if (!arguments.length) return getX;
8794     getX = _;
8795     lines1.x(_);
8796     bars1.x(_);
8797     return chart;
8798   };
8799
8800   chart.y = function(_) {
8801     if (!arguments.length) return getY;
8802     getY = _;
8803     lines1.y(_);
8804     bars1.y(_);
8805     return chart;
8806   };
8807
8808   chart.margin = function(_) {
8809     if (!arguments.length) return margin;
8810     margin = _;
8811     return chart;
8812   };
8813
8814   chart.width = function(_) {
8815     if (!arguments.length) return width;
8816     width = _;
8817     return chart;
8818   };
8819
8820   chart.height = function(_) {
8821     if (!arguments.length) return height;
8822     height = _;
8823     return chart;
8824   };
8825
8826   chart.color = function(_) {
8827     if (!arguments.length) return color;
8828     color = _;
8829     legend.color(_);
8830     return chart;
8831   };
8832
8833   chart.showLegend = function(_) {
8834     if (!arguments.length) return showLegend;
8835     showLegend = _;
8836     return chart;
8837   };
8838
8839   chart.tooltips = function(_) {
8840     if (!arguments.length) return tooltips;
8841     tooltips = _;
8842     return chart;
8843   };
8844
8845   chart.tooltipContent = function(_) {
8846     if (!arguments.length) return tooltip;
8847     tooltip = _;
8848     return chart;
8849   };
8850
8851   return chart;
8852 }
8853
8854
8855 nv.models.ohlcBar = function() {
8856
8857   //============================================================
8858   // Public Variables with Default Settings
8859   //------------------------------------------------------------
8860
8861   var margin = {top: 0, right: 0, bottom: 0, left: 0}
8862     , width = 960
8863     , height = 500
8864     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8865     , x = d3.scale.linear()
8866     , y = d3.scale.linear()
8867     , getX = function(d) { return d.x }
8868     , getY = function(d) { return d.y }
8869     , getOpen = function(d) { return d.open }
8870     , getClose = function(d) { return d.close }
8871     , getHigh = function(d) { return d.high }
8872     , getLow = function(d) { return d.low }
8873     , forceX = []
8874     , forceY = []
8875     , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
8876     , clipEdge = true
8877     , color = nv.utils.defaultColor()
8878     , xDomain
8879     , yDomain
8880     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8881     ;
8882
8883   //============================================================
8884
8885   //============================================================
8886   // Private Variables
8887   //------------------------------------------------------------
8888
8889   //TODO: store old scales for transitions
8890
8891   //============================================================
8892
8893
8894   function chart(selection) {
8895     selection.each(function(data) {
8896       var availableWidth = width - margin.left - margin.right,
8897           availableHeight = height - margin.top - margin.bottom,
8898           container = d3.select(this);
8899
8900
8901       //------------------------------------------------------------
8902       // Setup Scales
8903
8904       x   .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
8905
8906       if (padData)
8907         x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
8908       else
8909         x.range([0, availableWidth]);
8910
8911       y   .domain(yDomain || [
8912             d3.min(data[0].values.map(getLow).concat(forceY)),
8913             d3.max(data[0].values.map(getHigh).concat(forceY))
8914           ])
8915           .range([availableHeight, 0]);
8916
8917       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
8918       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
8919       if (x.domain()[0] === x.domain()[1])
8920         x.domain()[0] ?
8921             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8922           : x.domain([-1,1]);
8923
8924       if (y.domain()[0] === y.domain()[1])
8925         y.domain()[0] ?
8926             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8927           : y.domain([-1,1]);
8928
8929       //------------------------------------------------------------
8930
8931
8932       //------------------------------------------------------------
8933       // Setup containers and skeleton of chart
8934
8935       var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
8936       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
8937       var defsEnter = wrapEnter.append('defs');
8938       var gEnter = wrapEnter.append('g');
8939       var g = wrap.select('g');
8940
8941       gEnter.append('g').attr('class', 'nv-ticks');
8942
8943       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8944
8945       //------------------------------------------------------------
8946
8947
8948       container
8949           .on('click', function(d,i) {
8950             dispatch.chartClick({
8951                 data: d,
8952                 index: i,
8953                 pos: d3.event,
8954                 id: id
8955             });
8956           });
8957
8958
8959       defsEnter.append('clipPath')
8960           .attr('id', 'nv-chart-clip-path-' + id)
8961         .append('rect');
8962
8963       wrap.select('#nv-chart-clip-path-' + id + ' rect')
8964           .attr('width', availableWidth)
8965           .attr('height', availableHeight);
8966
8967       g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
8968
8969
8970
8971       var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
8972           .data(function(d) { return d });
8973
8974       ticks.exit().remove();
8975
8976
8977       var ticksEnter = ticks.enter().append('path')
8978           .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
8979           .attr('d', function(d,i) {
8980             var w = (availableWidth / data[0].values.length) * .9;
8981             return 'm0,0l0,'
8982                  + (y(getOpen(d,i))
8983                  - y(getHigh(d,i)))
8984                  + 'l'
8985                  + (-w/2)
8986                  + ',0l'
8987                  + (w/2)
8988                  + ',0l0,'
8989                  + (y(getLow(d,i)) - y(getOpen(d,i)))
8990                  + 'l0,'
8991                  + (y(getClose(d,i))
8992                  - y(getLow(d,i)))
8993                  + 'l'
8994                  + (w/2)
8995                  + ',0l'
8996                  + (-w/2)
8997                  + ',0z';
8998           })
8999           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9000           //.attr('fill', function(d,i) { return color[0]; })
9001           //.attr('stroke', function(d,i) { return color[0]; })
9002           //.attr('x', 0 )
9003           //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
9004           //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
9005           .on('mouseover', function(d,i) {
9006             d3.select(this).classed('hover', true);
9007             dispatch.elementMouseover({
9008                 point: d,
9009                 series: data[0],
9010                 pos: [x(getX(d,i)), y(getY(d,i))],  // TODO: Figure out why the value appears to be shifted
9011                 pointIndex: i,
9012                 seriesIndex: 0,
9013                 e: d3.event
9014             });
9015
9016           })
9017           .on('mouseout', function(d,i) {
9018                 d3.select(this).classed('hover', false);
9019                 dispatch.elementMouseout({
9020                     point: d,
9021                     series: data[0],
9022                     pointIndex: i,
9023                     seriesIndex: 0,
9024                     e: d3.event
9025                 });
9026           })
9027           .on('click', function(d,i) {
9028                 dispatch.elementClick({
9029                     //label: d[label],
9030                     value: getY(d,i),
9031                     data: d,
9032                     index: i,
9033                     pos: [x(getX(d,i)), y(getY(d,i))],
9034                     e: d3.event,
9035                     id: id
9036                 });
9037               d3.event.stopPropagation();
9038           })
9039           .on('dblclick', function(d,i) {
9040               dispatch.elementDblClick({
9041                   //label: d[label],
9042                   value: getY(d,i),
9043                   data: d,
9044                   index: i,
9045                   pos: [x(getX(d,i)), y(getY(d,i))],
9046                   e: d3.event,
9047                   id: id
9048               });
9049               d3.event.stopPropagation();
9050           });
9051
9052       ticks
9053           .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9054       d3.transition(ticks)
9055           .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9056           .attr('d', function(d,i) {
9057             var w = (availableWidth / data[0].values.length) * .9;
9058             return 'm0,0l0,'
9059                  + (y(getOpen(d,i))
9060                  - y(getHigh(d,i)))
9061                  + 'l'
9062                  + (-w/2)
9063                  + ',0l'
9064                  + (w/2)
9065                  + ',0l0,'
9066                  + (y(getLow(d,i))
9067                  - y(getOpen(d,i)))
9068                  + 'l0,'
9069                  + (y(getClose(d,i))
9070                  - y(getLow(d,i)))
9071                  + 'l'
9072                  + (w/2)
9073                  + ',0l'
9074                  + (-w/2)
9075                  + ',0z';
9076           })
9077           //.attr('width', (availableWidth / data[0].values.length) * .9 )
9078
9079
9080       //d3.transition(ticks)
9081           //.attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
9082           //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9083           //.order();  // not sure if this makes any sense for this model
9084
9085     });
9086
9087     return chart;
9088   }
9089
9090
9091   //============================================================
9092   // Expose Public Variables
9093   //------------------------------------------------------------
9094
9095   chart.dispatch = dispatch;
9096
9097   chart.x = function(_) {
9098     if (!arguments.length) return getX;
9099     getX = _;
9100     return chart;
9101   };
9102
9103   chart.y = function(_) {
9104     if (!arguments.length) return getY;
9105     getY = _;
9106     return chart;
9107   };
9108
9109   chart.open = function(_) {
9110     if (!arguments.length) return getOpen;
9111     getOpen = _;
9112     return chart;
9113   };
9114
9115   chart.close = function(_) {
9116     if (!arguments.length) return getClose;
9117     getClose = _;
9118     return chart;
9119   };
9120
9121   chart.high = function(_) {
9122     if (!arguments.length) return getHigh;
9123     getHigh = _;
9124     return chart;
9125   };
9126
9127   chart.low = function(_) {
9128     if (!arguments.length) return getLow;
9129     getLow = _;
9130     return chart;
9131   };
9132
9133   chart.margin = function(_) {
9134     if (!arguments.length) return margin;
9135     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9136     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9137     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9138     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9139     return chart;
9140   };
9141
9142   chart.width = function(_) {
9143     if (!arguments.length) return width;
9144     width = _;
9145     return chart;
9146   };
9147
9148   chart.height = function(_) {
9149     if (!arguments.length) return height;
9150     height = _;
9151     return chart;
9152   };
9153
9154   chart.xScale = function(_) {
9155     if (!arguments.length) return x;
9156     x = _;
9157     return chart;
9158   };
9159
9160   chart.yScale = function(_) {
9161     if (!arguments.length) return y;
9162     y = _;
9163     return chart;
9164   };
9165
9166   chart.xDomain = function(_) {
9167     if (!arguments.length) return xDomain;
9168     xDomain = _;
9169     return chart;
9170   };
9171
9172   chart.yDomain = function(_) {
9173     if (!arguments.length) return yDomain;
9174     yDomain = _;
9175     return chart;
9176   };
9177
9178   chart.forceX = function(_) {
9179     if (!arguments.length) return forceX;
9180     forceX = _;
9181     return chart;
9182   };
9183
9184   chart.forceY = function(_) {
9185     if (!arguments.length) return forceY;
9186     forceY = _;
9187     return chart;
9188   };
9189
9190   chart.padData = function(_) {
9191     if (!arguments.length) return padData;
9192     padData = _;
9193     return chart;
9194   };
9195
9196   chart.clipEdge = function(_) {
9197     if (!arguments.length) return clipEdge;
9198     clipEdge = _;
9199     return chart;
9200   };
9201
9202   chart.color = function(_) {
9203     if (!arguments.length) return color;
9204     color = nv.utils.getColor(_);
9205     return chart;
9206   };
9207
9208   chart.id = function(_) {
9209     if (!arguments.length) return id;
9210     id = _;
9211     return chart;
9212   };
9213
9214   //============================================================
9215
9216
9217   return chart;
9218 }
9219 nv.models.pie = function() {
9220
9221   //============================================================
9222   // Public Variables with Default Settings
9223   //------------------------------------------------------------
9224
9225   var margin = {top: 0, right: 0, bottom: 0, left: 0}
9226     , width = 500
9227     , height = 500
9228     , getValues = function(d) { return d.values }
9229     , getX = function(d) { return d.x }
9230     , getY = function(d) { return d.y }
9231     , getDescription = function(d) { return d.description }
9232     , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9233     , color = nv.utils.defaultColor()
9234     , valueFormat = d3.format(',.2f')
9235     , showLabels = true
9236     , pieLabelsOutside = true
9237     , donutLabelsOutside = false
9238     , labelThreshold = .02 //if slice percentage is under this, don't show label
9239     , donut = false
9240     , labelSunbeamLayout = false
9241     , startAngle = false
9242     , endAngle = false
9243     , donutRatio = 0.5
9244     , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9245     ;
9246
9247   //============================================================
9248
9249
9250   function chart(selection) {
9251     selection.each(function(data) {
9252       var availableWidth = width - margin.left - margin.right,
9253           availableHeight = height - margin.top - margin.bottom,
9254           radius = Math.min(availableWidth, availableHeight) / 2,
9255           arcRadius = radius-(radius / 5),
9256           container = d3.select(this);
9257
9258
9259       //------------------------------------------------------------
9260       // Setup containers and skeleton of chart
9261
9262       //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
9263       var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]);
9264       var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
9265       var gEnter = wrapEnter.append('g');
9266       var g = wrap.select('g');
9267
9268       gEnter.append('g').attr('class', 'nv-pie');
9269
9270       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9271       g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
9272
9273       //------------------------------------------------------------
9274
9275
9276       container
9277           .on('click', function(d,i) {
9278               dispatch.chartClick({
9279                   data: d,
9280                   index: i,
9281                   pos: d3.event,
9282                   id: id
9283               });
9284           });
9285
9286
9287       var arc = d3.svg.arc()
9288                   .outerRadius(arcRadius);
9289
9290       if (startAngle) arc.startAngle(startAngle)
9291       if (endAngle) arc.endAngle(endAngle);
9292       if (donut) arc.innerRadius(radius * donutRatio);
9293
9294       // Setup the Pie chart and choose the data element
9295       var pie = d3.layout.pie()
9296           .sort(null)
9297           .value(function(d) { return d.disabled ? 0 : getY(d) });
9298
9299       var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
9300           .data(pie);
9301
9302       slices.exit().remove();
9303
9304       var ae = slices.enter().append('g')
9305               .attr('class', 'nv-slice')
9306               .on('mouseover', function(d,i){
9307                 d3.select(this).classed('hover', true);
9308                 dispatch.elementMouseover({
9309                     label: getX(d.data),
9310                     value: getY(d.data),
9311                     point: d.data,
9312                     pointIndex: i,
9313                     pos: [d3.event.pageX, d3.event.pageY],
9314                     id: id
9315                 });
9316               })
9317               .on('mouseout', function(d,i){
9318                 d3.select(this).classed('hover', false);
9319                 dispatch.elementMouseout({
9320                     label: getX(d.data),
9321                     value: getY(d.data),
9322                     point: d.data,
9323                     index: i,
9324                     id: id
9325                 });
9326               })
9327               .on('click', function(d,i) {
9328                 dispatch.elementClick({
9329                     label: getX(d.data),
9330                     value: getY(d.data),
9331                     point: d.data,
9332                     index: i,
9333                     pos: d3.event,
9334                     id: id
9335                 });
9336                 d3.event.stopPropagation();
9337               })
9338               .on('dblclick', function(d,i) {
9339                 dispatch.elementDblClick({
9340                     label: getX(d.data),
9341                     value: getY(d.data),
9342                     point: d.data,
9343                     index: i,
9344                     pos: d3.event,
9345                     id: id
9346                 });
9347                 d3.event.stopPropagation();
9348               });
9349
9350         slices
9351             .attr('fill', function(d,i) { return color(d, i); })
9352             .attr('stroke', function(d,i) { return color(d, i); });
9353
9354         var paths = ae.append('path')
9355             .each(function(d) { this._current = d; });
9356             //.attr('d', arc);
9357
9358         d3.transition(slices.select('path'))
9359             .attr('d', arc)
9360             .attrTween('d', arcTween);
9361
9362         if (showLabels) {
9363           // This does the normal label
9364           var labelsArc = d3.svg.arc().innerRadius(0);
9365           
9366           if (pieLabelsOutside){ labelsArc = arc; }
9367
9368           if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
9369
9370           ae.append("g").classed("nv-label", true)
9371             .each(function(d, i) {
9372               var group = d3.select(this);
9373
9374               group
9375                 .attr('transform', function(d) {
9376                      if (labelSunbeamLayout) {
9377                        d.outerRadius = arcRadius + 10; // Set Outer Coordinate
9378                        d.innerRadius = arcRadius + 15; // Set Inner Coordinate
9379                        var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
9380                        if ((d.startAngle+d.endAngle)/2 < Math.PI) {
9381                          rotateAngle -= 90;
9382                        } else {
9383                          rotateAngle += 90;
9384                        }
9385                        return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
9386                      } else {
9387                        d.outerRadius = radius + 10; // Set Outer Coordinate
9388                        d.innerRadius = radius + 15; // Set Inner Coordinate
9389                        return 'translate(' + labelsArc.centroid(d) + ')'
9390                      }
9391                 });
9392
9393               group.append('rect')
9394                   .style('stroke', '#fff')
9395                   .style('fill', '#fff')
9396                   .attr("rx", 3)
9397                   .attr("ry", 3);
9398
9399               group.append('text')
9400                   .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
9401                   .style('fill', '#000')
9402
9403
9404           });
9405
9406           slices.select(".nv-label").transition()
9407             .attr('transform', function(d) {
9408                 if (labelSunbeamLayout) {
9409                   d.outerRadius = arcRadius + 10; // Set Outer Coordinate
9410                   d.innerRadius = arcRadius + 15; // Set Inner Coordinate
9411                   var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
9412                   if ((d.startAngle+d.endAngle)/2 < Math.PI) {
9413                     rotateAngle -= 90;
9414                   } else {
9415                     rotateAngle += 90;
9416                   }
9417                   return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
9418                 } else {
9419                   d.outerRadius = radius + 10; // Set Outer Coordinate
9420                   d.innerRadius = radius + 15; // Set Inner Coordinate
9421                   return 'translate(' + labelsArc.centroid(d) + ')'
9422                 }
9423             });
9424
9425           slices.each(function(d, i) {
9426             var slice = d3.select(this);
9427
9428             slice
9429               .select(".nv-label text")
9430                 .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
9431                 .text(function(d, i) {
9432                   var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
9433                   return (d.value && percent > labelThreshold) ? getX(d.data) : '';
9434                 });
9435
9436             var textBox = slice.select('text').node().getBBox();
9437             slice.select(".nv-label rect")
9438               .attr("width", textBox.width + 10)
9439               .attr("height", textBox.height + 10)
9440               .attr("transform", function() {
9441                 return "translate(" + [textBox.x - 5, textBox.y - 5] + ")";
9442               });
9443           });
9444         }
9445
9446
9447         // Computes the angle of an arc, converting from radians to degrees.
9448         function angle(d) {
9449           var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
9450           return a > 90 ? a - 180 : a;
9451         }
9452
9453         function arcTween(a) {
9454           a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
9455           a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
9456           if (!donut) a.innerRadius = 0;
9457           var i = d3.interpolate(this._current, a);
9458           this._current = i(0);
9459           return function(t) {
9460             return arc(i(t));
9461           };
9462         }
9463
9464         function tweenPie(b) {
9465           b.innerRadius = 0;
9466           var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
9467           return function(t) {
9468               return arc(i(t));
9469           };
9470         }
9471
9472     });
9473
9474     return chart;
9475   }
9476
9477
9478   //============================================================
9479   // Expose Public Variables
9480   //------------------------------------------------------------
9481
9482   chart.dispatch = dispatch;
9483
9484   chart.margin = function(_) {
9485     if (!arguments.length) return margin;
9486     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9487     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9488     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9489     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9490     return chart;
9491   };
9492
9493   chart.width = function(_) {
9494     if (!arguments.length) return width;
9495     width = _;
9496     return chart;
9497   };
9498
9499   chart.height = function(_) {
9500     if (!arguments.length) return height;
9501     height = _;
9502     return chart;
9503   };
9504
9505   chart.values = function(_) {
9506     if (!arguments.length) return getValues;
9507     getValues = _;
9508     return chart;
9509   };
9510
9511   chart.x = function(_) {
9512     if (!arguments.length) return getX;
9513     getX = _;
9514     return chart;
9515   };
9516
9517   chart.y = function(_) {
9518     if (!arguments.length) return getY;
9519     getY = d3.functor(_);
9520     return chart;
9521   };
9522   
9523   chart.description = function(_) {
9524     if (!arguments.length) return getDescription;
9525     getDescription = _;
9526     return chart;
9527   };
9528
9529   chart.showLabels = function(_) {
9530     if (!arguments.length) return showLabels;
9531     showLabels = _;
9532     return chart;
9533   };
9534   
9535   chart.labelSunbeamLayout = function(_) {
9536     if (!arguments.length) return labelSunbeamLayout;
9537     labelSunbeamLayout = _;
9538     return chart;
9539   };
9540
9541   chart.donutLabelsOutside = function(_) {
9542     if (!arguments.length) return donutLabelsOutside;
9543     donutLabelsOutside = _;
9544     return chart;
9545   };
9546   
9547   chart.pieLabelsOutside = function(_) {
9548     if (!arguments.length) return pieLabelsOutside;
9549     pieLabelsOutside = _;
9550     return chart;
9551   };
9552
9553   chart.donut = function(_) {
9554     if (!arguments.length) return donut;
9555     donut = _;
9556     return chart;
9557   };
9558   
9559   chart.donutRatio = function(_) {
9560     if (!arguments.length) return donutRatio;
9561     donutRatio = _;
9562     return chart;
9563   };
9564
9565   chart.startAngle = function(_) {
9566     if (!arguments.length) return startAngle;
9567     startAngle = _;
9568     return chart;
9569   };
9570
9571   chart.endAngle = function(_) {
9572     if (!arguments.length) return endAngle;
9573     endAngle = _;
9574     return chart;
9575   };
9576
9577   chart.id = function(_) {
9578     if (!arguments.length) return id;
9579     id = _;
9580     return chart;
9581   };
9582
9583   chart.color = function(_) {
9584     if (!arguments.length) return color;
9585     color = nv.utils.getColor(_);
9586     return chart;
9587   };
9588
9589   chart.valueFormat = function(_) {
9590     if (!arguments.length) return valueFormat;
9591     valueFormat = _;
9592     return chart;
9593   };
9594
9595   chart.labelThreshold = function(_) {
9596     if (!arguments.length) return labelThreshold;
9597     labelThreshold = _;
9598     return chart;
9599   };
9600   //============================================================
9601
9602
9603   return chart;
9604 }
9605 nv.models.pieChart = function() {
9606
9607   //============================================================
9608   // Public Variables with Default Settings
9609   //------------------------------------------------------------
9610
9611   var pie = nv.models.pie()
9612     , legend = nv.models.legend()
9613     ;
9614
9615   var margin = {top: 30, right: 20, bottom: 20, left: 20}
9616     , width = null
9617     , height = null
9618     , showLegend = true
9619     , color = nv.utils.defaultColor()
9620     , tooltips = true
9621     , tooltip = function(key, y, e, graph) {
9622         return '<h3>' + key + '</h3>' +
9623                '<p>' +  y + '</p>'
9624       }
9625     , state = {}
9626     , defaultState = null
9627     , noData = "No Data Available."
9628     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
9629     ;
9630
9631   //============================================================
9632
9633
9634   //============================================================
9635   // Private Variables
9636   //------------------------------------------------------------
9637
9638   var showTooltip = function(e, offsetElement) {
9639     var tooltipLabel = pie.description()(e.point) || pie.x()(e.point)
9640     var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
9641         top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
9642         y = pie.valueFormat()(pie.y()(e.point)),
9643         content = tooltip(tooltipLabel, y, e, chart);
9644
9645     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
9646   };
9647
9648   //============================================================
9649
9650
9651   function chart(selection) {
9652     selection.each(function(data) {
9653       var container = d3.select(this),
9654           that = this;
9655
9656       var availableWidth = (width || parseInt(container.style('width')) || 960)
9657                              - margin.left - margin.right,
9658           availableHeight = (height || parseInt(container.style('height')) || 400)
9659                              - margin.top - margin.bottom;
9660
9661       chart.update = function() { container.transition().call(chart); };
9662       chart.container = this;
9663
9664       //set state.disabled
9665       state.disabled = data[0].map(function(d) { return !!d.disabled });
9666
9667       if (!defaultState) {
9668         var key;
9669         defaultState = {};
9670         for (key in state) {
9671           if (state[key] instanceof Array)
9672             defaultState[key] = state[key].slice(0);
9673           else
9674             defaultState[key] = state[key];
9675         }
9676       }
9677
9678       //------------------------------------------------------------
9679       // Display No Data message if there's nothing to show.
9680
9681       if (!data[0] || !data[0].length) {
9682         var noDataText = container.selectAll('.nv-noData').data([noData]);
9683
9684         noDataText.enter().append('text')
9685           .attr('class', 'nvd3 nv-noData')
9686           .attr('dy', '-.7em')
9687           .style('text-anchor', 'middle');
9688
9689         noDataText
9690           .attr('x', margin.left + availableWidth / 2)
9691           .attr('y', margin.top + availableHeight / 2)
9692           .text(function(d) { return d });
9693
9694         return chart;
9695       } else {
9696         container.selectAll('.nv-noData').remove();
9697       }
9698
9699       //------------------------------------------------------------
9700
9701
9702       //------------------------------------------------------------
9703       // Setup containers and skeleton of chart
9704
9705       var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
9706       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
9707       var g = wrap.select('g');
9708
9709       gEnter.append('g').attr('class', 'nv-pieWrap');
9710       gEnter.append('g').attr('class', 'nv-legendWrap');
9711
9712       //------------------------------------------------------------
9713
9714
9715       //------------------------------------------------------------
9716       // Legend
9717
9718       if (showLegend) {
9719         legend
9720           .width( availableWidth )
9721           .key(pie.x());
9722
9723         wrap.select('.nv-legendWrap')
9724             .datum(pie.values()(data[0]))
9725             .call(legend);
9726
9727         if ( margin.top != legend.height()) {
9728           margin.top = legend.height();
9729           availableHeight = (height || parseInt(container.style('height')) || 400)
9730                              - margin.top - margin.bottom;
9731         }
9732
9733         wrap.select('.nv-legendWrap')
9734             .attr('transform', 'translate(0,' + (-margin.top) +')');
9735       }
9736
9737       //------------------------------------------------------------
9738
9739
9740       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9741
9742
9743       //------------------------------------------------------------
9744       // Main Chart Component(s)
9745
9746       pie
9747         .width(availableWidth)
9748         .height(availableHeight);
9749
9750
9751       var pieWrap = g.select('.nv-pieWrap')
9752           .datum(data);
9753
9754       d3.transition(pieWrap).call(pie);
9755
9756       //------------------------------------------------------------
9757
9758
9759       //============================================================
9760       // Event Handling/Dispatching (in chart's scope)
9761       //------------------------------------------------------------
9762
9763       legend.dispatch.on('legendClick', function(d,i, that) {
9764         d.disabled = !d.disabled;
9765
9766         if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) {
9767           pie.values()(data[0]).map(function(d) {
9768             d.disabled = false;
9769             wrap.selectAll('.nv-series').classed('disabled', false);
9770             return d;
9771           });
9772         }
9773
9774         state.disabled = data[0].map(function(d) { return !!d.disabled });
9775         dispatch.stateChange(state);
9776
9777         chart.update();
9778       });
9779
9780       pie.dispatch.on('elementMouseout.tooltip', function(e) {
9781         dispatch.tooltipHide(e);
9782       });
9783
9784       // Update chart from a state object passed to event handler
9785       dispatch.on('changeState', function(e) {
9786
9787         if (typeof e.disabled !== 'undefined') {
9788           data[0].forEach(function(series,i) {
9789             series.disabled = e.disabled[i];
9790           });
9791
9792           state.disabled = e.disabled;
9793         }
9794
9795         chart.update();
9796       });
9797
9798       //============================================================
9799
9800
9801     });
9802
9803     return chart;
9804   }
9805
9806   //============================================================
9807   // Event Handling/Dispatching (out of chart's scope)
9808   //------------------------------------------------------------
9809
9810   pie.dispatch.on('elementMouseover.tooltip', function(e) {
9811     e.pos = [e.pos[0] +  margin.left, e.pos[1] + margin.top];
9812     dispatch.tooltipShow(e);
9813   });
9814
9815   dispatch.on('tooltipShow', function(e) {
9816     if (tooltips) showTooltip(e);
9817   });
9818
9819   dispatch.on('tooltipHide', function() {
9820     if (tooltips) nv.tooltip.cleanup();
9821   });
9822
9823   //============================================================
9824
9825
9826   //============================================================
9827   // Expose Public Variables
9828   //------------------------------------------------------------
9829
9830   // expose chart's sub-components
9831   chart.legend = legend;
9832   chart.dispatch = dispatch;
9833   chart.pie = pie;
9834
9835   d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'donut', 'donutRatio', 'labelThreshold');
9836
9837   chart.margin = function(_) {
9838     if (!arguments.length) return margin;
9839     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
9840     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
9841     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9842     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
9843     return chart;
9844   };
9845
9846   chart.width = function(_) {
9847     if (!arguments.length) return width;
9848     width = _;
9849     return chart;
9850   };
9851
9852   chart.height = function(_) {
9853     if (!arguments.length) return height;
9854     height = _;
9855     return chart;
9856   };
9857
9858   chart.color = function(_) {
9859     if (!arguments.length) return color;
9860     color = nv.utils.getColor(_);
9861     legend.color(color);
9862     pie.color(color);
9863     return chart;
9864   };
9865
9866   chart.showLegend = function(_) {
9867     if (!arguments.length) return showLegend;
9868     showLegend = _;
9869     return chart;
9870   };
9871
9872   chart.tooltips = function(_) {
9873     if (!arguments.length) return tooltips;
9874     tooltips = _;
9875     return chart;
9876   };
9877
9878   chart.tooltipContent = function(_) {
9879     if (!arguments.length) return tooltip;
9880     tooltip = _;
9881     return chart;
9882   };
9883
9884   chart.state = function(_) {
9885     if (!arguments.length) return state;
9886     state = _;
9887     return chart;
9888   };
9889
9890   chart.defaultState = function(_) {
9891     if (!arguments.length) return defaultState;
9892     defaultState = _;
9893     return chart;
9894   };
9895
9896   chart.noData = function(_) {
9897     if (!arguments.length) return noData;
9898     noData = _;
9899     return chart;
9900   };
9901
9902   //============================================================
9903
9904
9905   return chart;
9906 }
9907
9908 nv.models.scatter = function() {
9909
9910   //============================================================
9911   // Public Variables with Default Settings
9912   //------------------------------------------------------------
9913
9914   var margin       = {top: 0, right: 0, bottom: 0, left: 0}
9915     , width        = 960
9916     , height       = 500
9917     , color        = nv.utils.defaultColor() // chooses color
9918     , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
9919     , x            = d3.scale.linear()
9920     , y            = d3.scale.linear()
9921     , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
9922     , getX         = function(d) { return d.x } // accessor to get the x value
9923     , getY         = function(d) { return d.y } // accessor to get the y value
9924     , getSize      = function(d) { return d.size || 1} // accessor to get the point size
9925     , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
9926     , onlyCircles  = true // Set to false to use shapes
9927     , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
9928     , forceY       = [] // List of numbers to Force into the Y scale
9929     , forceSize    = [] // List of numbers to Force into the Size scale
9930     , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
9931     , pointKey     = null
9932     , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
9933     , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9934     , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
9935     , clipEdge     = false // if true, masks points within x and y scale
9936     , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
9937     , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
9938     , xDomain      = null // Override x domain (skips the calculation from data)
9939     , yDomain      = null // Override y domain
9940     , sizeDomain   = null // Override point size domain
9941     , sizeRange    = null
9942     , singlePoint  = false
9943     , dispatch     = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
9944     , useVoronoi   = true
9945     ;
9946
9947   //============================================================
9948
9949
9950   //============================================================
9951   // Private Variables
9952   //------------------------------------------------------------
9953
9954   var x0, y0, z0 // used to store previous scales
9955     , timeoutID
9956     , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
9957     ;
9958
9959   //============================================================
9960
9961
9962   function chart(selection) {
9963     selection.each(function(data) {
9964       var availableWidth = width - margin.left - margin.right,
9965           availableHeight = height - margin.top - margin.bottom,
9966           container = d3.select(this);
9967
9968       //add series index to each data point for reference
9969       data = data.map(function(series, i) {
9970         series.values = series.values.map(function(point) {
9971           point.series = i;
9972           return point;
9973         });
9974         return series;
9975       });
9976
9977       //------------------------------------------------------------
9978       // Setup Scales
9979
9980       // remap and flatten the data for use in calculating the scales' domains
9981       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
9982             d3.merge(
9983               data.map(function(d) {
9984                 return d.values.map(function(d,i) {
9985                   return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
9986                 })
9987               })
9988             );
9989
9990       x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
9991
9992       if (padData && data[0])
9993         x.range([(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
9994         //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
9995       else
9996         x.range([0, availableWidth]);
9997
9998       y   .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
9999           .range([availableHeight, 0]);
10000
10001       z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10002           .range(sizeRange || [16, 256]);
10003
10004       // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10005       if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
10006       if (x.domain()[0] === x.domain()[1])
10007         x.domain()[0] ?
10008             x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10009           : x.domain([-1,1]);
10010
10011       if (y.domain()[0] === y.domain()[1])
10012         y.domain()[0] ?
10013             y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
10014           : y.domain([-1,1]);
10015
10016       if ( isNaN(x.domain()[0])) {
10017           x.domain([-1,1]);
10018       }
10019
10020       if ( isNaN(y.domain()[0])) {
10021           y.domain([-1,1]);
10022       }
10023
10024
10025       x0 = x0 || x;
10026       y0 = y0 || y;
10027       z0 = z0 || z;
10028
10029       //------------------------------------------------------------
10030
10031
10032       //------------------------------------------------------------
10033       // Setup containers and skeleton of chart
10034
10035       var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10036       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
10037       var defsEnter = wrapEnter.append('defs');
10038       var gEnter = wrapEnter.append('g');
10039       var g = wrap.select('g');
10040
10041       gEnter.append('g').attr('class', 'nv-groups');
10042       gEnter.append('g').attr('class', 'nv-point-paths');
10043
10044       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10045
10046       //------------------------------------------------------------
10047
10048
10049       defsEnter.append('clipPath')
10050           .attr('id', 'nv-edge-clip-' + id)
10051         .append('rect');
10052
10053       wrap.select('#nv-edge-clip-' + id + ' rect')
10054           .attr('width', availableWidth)
10055           .attr('height', availableHeight);
10056
10057       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10058
10059
10060       function updateInteractiveLayer() {
10061
10062         if (!interactive) return false;
10063
10064         var eventElements;
10065
10066         var vertices = d3.merge(data.map(function(group, groupIndex) {
10067             return group.values
10068               .map(function(point, pointIndex) {
10069                 // *Adding noise to make duplicates very unlikely
10070                 // *Injecting series and point index for reference
10071                 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10072                 */
10073                 var pX = getX(point,pointIndex) + Math.random() * 1e-7;
10074                 var pY = getY(point,pointIndex) + Math.random() * 1e-7;
10075
10076                 return [x(pX), 
10077                         y(pY), 
10078                         groupIndex, 
10079                         pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
10080               })
10081               .filter(function(pointArray, pointIndex) {
10082                 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10083               })
10084           })
10085         );
10086
10087
10088
10089         //inject series and point index for reference into voronoi
10090         if (useVoronoi === true) {
10091
10092           if (clipVoronoi) {
10093             var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
10094                 .data([id])
10095               .enter();
10096
10097             pointClipsEnter.append('clipPath')
10098                   .attr('class', 'nv-point-clips')
10099                   .attr('id', 'nv-points-clip-' + id);
10100
10101             var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
10102                 .data(vertices);
10103             pointClips.enter().append('circle')
10104                 .attr('r', clipRadius);
10105             pointClips.exit().remove();
10106             pointClips
10107                 .attr('cx', function(d) { return d[0] })
10108                 .attr('cy', function(d) { return d[1] });
10109
10110             wrap.select('.nv-point-paths')
10111                 .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
10112           }
10113
10114
10115           if(vertices.length) {
10116             // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
10117             vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
10118             vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
10119             vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
10120             vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
10121           }
10122
10123           var bounds = d3.geom.polygon([
10124               [-10,-10],
10125               [-10,height + 10],
10126               [width + 10,height + 10],
10127               [width + 10,-10]
10128           ]);
10129
10130           var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
10131               return {
10132                 'data': bounds.clip(d),
10133                 'series': vertices[i][2],
10134                 'point': vertices[i][3]
10135               }
10136             });
10137
10138
10139           var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
10140               .data(voronoi);
10141           pointPaths.enter().append('path')
10142               .attr('class', function(d,i) { return 'nv-path-'+i; });
10143           pointPaths.exit().remove();
10144           pointPaths
10145               .attr('d', function(d) {
10146                 if (d.data.length === 0) 
10147                     return 'M 0 0'
10148                 else 
10149                     return 'M' + d.data.join('L') + 'Z'; 
10150               });
10151
10152           pointPaths
10153               .on('click', function(d) {
10154                 if (needsUpdate) return 0;
10155                 var series = data[d.series],
10156                     point  = series.values[d.point];
10157
10158                 dispatch.elementClick({
10159                   point: point,
10160                   series: series,
10161                   pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
10162                   seriesIndex: d.series,
10163                   pointIndex: d.point
10164                 });
10165               })
10166               .on('mouseover', function(d) {
10167                 if (needsUpdate) return 0;
10168                 var series = data[d.series],
10169                     point  = series.values[d.point];
10170
10171                 dispatch.elementMouseover({
10172                   point: point,
10173                   series: series,
10174                   pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
10175                   seriesIndex: d.series,
10176                   pointIndex: d.point
10177                 });
10178               })
10179               .on('mouseout', function(d, i) {
10180                 if (needsUpdate) return 0;
10181                 var series = data[d.series],
10182                     point  = series.values[d.point];
10183
10184                 dispatch.elementMouseout({
10185                   point: point,
10186                   series: series,
10187                   seriesIndex: d.series,
10188                   pointIndex: d.point
10189                 });
10190               });
10191
10192
10193         } else {
10194           /*
10195           // bring data in form needed for click handlers
10196           var dataWithPoints = vertices.map(function(d, i) {
10197               return {
10198                 'data': d,
10199                 'series': vertices[i][2],
10200                 'point': vertices[i][3]
10201               }
10202             });
10203            */
10204
10205           // add event handlers to points instead voronoi paths
10206           wrap.select('.nv-groups').selectAll('.nv-group')
10207             .selectAll('.nv-point')
10208               //.data(dataWithPoints)
10209               //.style('pointer-events', 'auto') // recativate events, disabled by css
10210               .on('click', function(d,i) { 
10211                 //nv.log('test', d, i);
10212                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
10213                 var series = data[d.series],
10214                     point  = series.values[i];
10215
10216                 dispatch.elementClick({
10217                   point: point,
10218                   series: series,
10219                   pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
10220                   seriesIndex: d.series,
10221                   pointIndex: i
10222                 });
10223               })
10224               .on('mouseover', function(d,i) {
10225                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
10226                 var series = data[d.series],
10227                     point  = series.values[i];
10228
10229                 dispatch.elementMouseover({
10230                   point: point,
10231                   series: series,
10232                   pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
10233                   seriesIndex: d.series,
10234                   pointIndex: i
10235                 });
10236               })
10237               .on('mouseout', function(d,i) {
10238                 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
10239                 var series = data[d.series],
10240                     point  = series.values[i];
10241
10242                 dispatch.elementMouseout({
10243                   point: point,
10244                   series: series,
10245                   seriesIndex: d.series,
10246                   pointIndex: i
10247                 });
10248               });
10249           }
10250
10251           needsUpdate = false;
10252       }
10253
10254       needsUpdate = true;
10255
10256       var groups = wrap.select('.nv-groups').selectAll('.nv-group')
10257           .data(function(d) { return d }, function(d) { return d.key });
10258       groups.enter().append('g')
10259           .style('stroke-opacity', 1e-6)
10260           .style('fill-opacity', 1e-6);
10261       d3.transition(groups.exit())
10262           .style('stroke-opacity', 1e-6)
10263           .style('fill-opacity', 1e-6)
10264           .remove();
10265       groups
10266           .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
10267           .classed('hover', function(d) { return d.hover });
10268       d3.transition(groups)
10269           .style('fill', function(d,i) { return color(d, i) })
10270           .style('stroke', function(d,i) { return color(d, i) })
10271           .style('stroke-opacity', 1)
10272           .style('fill-opacity', .5);
10273
10274
10275       if (onlyCircles) {
10276
10277         var points = groups.selectAll('circle.nv-point')
10278             .data(function(d) { return d.values }, pointKey);
10279         points.enter().append('circle')
10280             .attr('cx', function(d,i) { return x0(getX(d,i)) })
10281             .attr('cy', function(d,i) { return y0(getY(d,i)) })
10282             .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
10283         points.exit().remove();
10284         groups.exit().selectAll('path.nv-point').transition()
10285             .attr('cx', function(d,i) { return x(getX(d,i)) })
10286             .attr('cy', function(d,i) { return y(getY(d,i)) })
10287             .remove();
10288         points.each(function(d,i) {
10289           d3.select(this)
10290             .classed('nv-point', true)
10291             .classed('nv-point-' + i, true);
10292         });
10293         points.transition()
10294             .attr('cx', function(d,i) { return x(getX(d,i)) })
10295             .attr('cy', function(d,i) { return y(getY(d,i)) })
10296             .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
10297
10298       } else {
10299
10300         var points = groups.selectAll('path.nv-point')
10301             .data(function(d) { return d.values });
10302         points.enter().append('path')
10303             .attr('transform', function(d,i) {
10304               return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
10305             })
10306             .attr('d',
10307               d3.svg.symbol()
10308                 .type(getShape)
10309                 .size(function(d,i) { return z(getSize(d,i)) })
10310             );
10311         points.exit().remove();
10312         d3.transition(groups.exit().selectAll('path.nv-point'))
10313             .attr('transform', function(d,i) {
10314               return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
10315             })
10316             .remove();
10317         points.each(function(d,i) {
10318           d3.select(this)
10319             .classed('nv-point', true)
10320             .classed('nv-point-' + i, true);
10321         });
10322         points.transition()
10323             .attr('transform', function(d,i) {
10324               //nv.log(d,i,getX(d,i), x(getX(d,i)));
10325               return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
10326             })
10327             .attr('d',
10328               d3.svg.symbol()
10329                 .type(getShape)
10330                 .size(function(d,i) { return z(getSize(d,i)) })
10331             );
10332       }
10333
10334
10335       // Delay updating the invisible interactive layer for smoother animation
10336       clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
10337       timeoutID = setTimeout(updateInteractiveLayer, 300);
10338       //updateInteractiveLayer();
10339
10340       //store old scales for use in transitions on update
10341       x0 = x.copy();
10342       y0 = y.copy();
10343       z0 = z.copy();
10344
10345     });
10346
10347     return chart;
10348   }
10349
10350
10351   //============================================================
10352   // Event Handling/Dispatching (out of chart's scope)
10353   //------------------------------------------------------------
10354
10355   dispatch.on('elementMouseover.point', function(d) {
10356     if (interactive)
10357       d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
10358           .classed('hover', true);
10359   });
10360
10361   dispatch.on('elementMouseout.point', function(d) {
10362     if (interactive)
10363       d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
10364           .classed('hover', false);
10365   });
10366
10367   //============================================================
10368
10369
10370   //============================================================
10371   // Expose Public Variables
10372   //------------------------------------------------------------
10373
10374   chart.dispatch = dispatch;
10375
10376   chart.x = function(_) {
10377     if (!arguments.length) return getX;
10378     getX = d3.functor(_);
10379     return chart;
10380   };
10381
10382   chart.y = function(_) {
10383     if (!arguments.length) return getY;
10384     getY = d3.functor(_);
10385     return chart;
10386   };
10387
10388   chart.size = function(_) {
10389     if (!arguments.length) return getSize;
10390     getSize = d3.functor(_);
10391     return chart;
10392   };
10393
10394   chart.margin = function(_) {
10395     if (!arguments.length) return margin;
10396     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
10397     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
10398     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10399     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
10400     return chart;
10401   };
10402
10403   chart.width = function(_) {
10404     if (!arguments.length) return width;
10405     width = _;
10406     return chart;
10407   };
10408
10409   chart.height = function(_) {
10410     if (!arguments.length) return height;
10411     height = _;
10412     return chart;
10413   };
10414
10415   chart.xScale = function(_) {
10416     if (!arguments.length) return x;
10417     x = _;
10418     return chart;
10419   };
10420
10421   chart.yScale = function(_) {
10422     if (!arguments.length) return y;
10423     y = _;
10424     return chart;
10425   };
10426
10427   chart.zScale = function(_) {
10428     if (!arguments.length) return z;
10429     z = _;
10430     return chart;
10431   };
10432
10433   chart.xDomain = function(_) {
10434     if (!arguments.length) return xDomain;
10435     xDomain = _;
10436     return chart;
10437   };
10438
10439   chart.yDomain = function(_) {
10440     if (!arguments.length) return yDomain;
10441     yDomain = _;
10442     return chart;
10443   };
10444
10445   chart.sizeDomain = function(_) {
10446     if (!arguments.length) return sizeDomain;
10447     sizeDomain = _;
10448     return chart;
10449   };
10450
10451   chart.sizeRange = function(_) {
10452     if (!arguments.length) return sizeRange;
10453     sizeRange = _;
10454     return chart;
10455   };
10456
10457   chart.forceX = function(_) {
10458     if (!arguments.length) return forceX;
10459     forceX = _;
10460     return chart;
10461   };
10462
10463   chart.forceY = function(_) {
10464     if (!arguments.length) return forceY;
10465     forceY = _;
10466     return chart;
10467   };
10468
10469   chart.forceSize = function(_) {
10470     if (!arguments.length) return forceSize;
10471     forceSize = _;
10472     return chart;
10473   };
10474
10475   chart.interactive = function(_) {
10476     if (!arguments.length) return interactive;
10477     interactive = _;
10478     return chart;
10479   };
10480
10481   chart.pointKey = function(_) {
10482     if (!arguments.length) return pointKey;
10483     pointKey = _;
10484     return chart;
10485   };
10486
10487   chart.pointActive = function(_) {
10488     if (!arguments.length) return pointActive;
10489     pointActive = _;
10490     return chart;
10491   };
10492
10493   chart.padData = function(_) {
10494     if (!arguments.length) return padData;
10495     padData = _;
10496     return chart;
10497   };
10498
10499   chart.padDataOuter = function(_) {
10500     if (!arguments.length) return padDataOuter;
10501     padDataOuter = _;
10502     return chart;
10503   };
10504
10505   chart.clipEdge = function(_) {
10506     if (!arguments.length) return clipEdge;
10507     clipEdge = _;
10508     return chart;
10509   };
10510
10511   chart.clipVoronoi= function(_) {
10512     if (!arguments.length) return clipVoronoi;
10513     clipVoronoi = _;
10514     return chart;
10515   };
10516
10517   chart.useVoronoi= function(_) {
10518     if (!arguments.length) return useVoronoi;
10519     useVoronoi = _;
10520     if (useVoronoi === false) {
10521         clipVoronoi = false;
10522     }
10523     return chart;
10524   };
10525
10526   chart.clipRadius = function(_) {
10527     if (!arguments.length) return clipRadius;
10528     clipRadius = _;
10529     return chart;
10530   };
10531
10532   chart.color = function(_) {
10533     if (!arguments.length) return color;
10534     color = nv.utils.getColor(_);
10535     return chart;
10536   };
10537
10538   chart.shape = function(_) {
10539     if (!arguments.length) return getShape;
10540     getShape = _;
10541     return chart;
10542   };
10543
10544   chart.onlyCircles = function(_) {
10545     if (!arguments.length) return onlyCircles;
10546     onlyCircles = _;
10547     return chart;
10548   };
10549
10550   chart.id = function(_) {
10551     if (!arguments.length) return id;
10552     id = _;
10553     return chart;
10554   };
10555
10556   chart.singlePoint = function(_) {
10557     if (!arguments.length) return singlePoint;
10558     singlePoint = _;
10559     return chart;
10560   };
10561
10562   //============================================================
10563
10564
10565   return chart;
10566 }
10567
10568 nv.models.scatterChart = function() {
10569
10570   //============================================================
10571   // Public Variables with Default Settings
10572   //------------------------------------------------------------
10573
10574   var scatter      = nv.models.scatter()
10575     , xAxis        = nv.models.axis()
10576     , yAxis        = nv.models.axis()
10577     , legend       = nv.models.legend()
10578     , controls     = nv.models.legend()
10579     , distX        = nv.models.distribution()
10580     , distY        = nv.models.distribution()
10581     ;
10582
10583   var margin       = {top: 30, right: 20, bottom: 50, left: 75}
10584     , width        = null
10585     , height       = null
10586     , color        = nv.utils.defaultColor()
10587     , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
10588     , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
10589     , xPadding     = 0
10590     , yPadding     = 0
10591     , showDistX    = false
10592     , showDistY    = false
10593     , showLegend   = true
10594     , showControls = !!d3.fisheye
10595     , fisheye      = 0
10596     , pauseFisheye = false
10597     , tooltips     = true
10598     , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
10599     , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
10600     //, tooltip      = function(key, x, y) { return '<h3>' + key + '</h3>' }
10601     , tooltip      = null
10602     , state = {}
10603     , defaultState = null
10604     , dispatch     = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10605     , noData       = "No Data Available."
10606     ;
10607
10608   scatter
10609     .xScale(x)
10610     .yScale(y)
10611     ;
10612   xAxis
10613     .orient('bottom')
10614     .tickPadding(10)
10615     ;
10616   yAxis
10617     .orient('left')
10618     .tickPadding(10)
10619     ;
10620   distX
10621     .axis('x')
10622     ;
10623   distY
10624     .axis('y')
10625     ;
10626
10627   //============================================================
10628
10629
10630   //============================================================
10631   // Private Variables
10632   //------------------------------------------------------------
10633
10634   var x0, y0;
10635
10636   var showTooltip = function(e, offsetElement) {
10637     //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
10638
10639     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10640         top = e.pos[1] + ( offsetElement.offsetTop || 0),
10641         leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10642         topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
10643         leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
10644         topY = e.pos[1] + ( offsetElement.offsetTop || 0),
10645         xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
10646         yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
10647
10648       if( tooltipX != null )
10649           nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
10650       if( tooltipY != null )
10651           nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
10652       if( tooltip != null )
10653           nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
10654   };
10655
10656   var controlsData = [
10657     { key: 'Magnify', disabled: true }
10658   ];
10659
10660   //============================================================
10661
10662
10663   function chart(selection) {
10664     selection.each(function(data) {
10665       var container = d3.select(this),
10666           that = this;
10667
10668       var availableWidth = (width  || parseInt(container.style('width')) || 960)
10669                              - margin.left - margin.right,
10670           availableHeight = (height || parseInt(container.style('height')) || 400)
10671                              - margin.top - margin.bottom;
10672
10673       chart.update = function() { container.transition().call(chart); };
10674       // chart.container = this;
10675
10676       //set state.disabled
10677       state.disabled = data.map(function(d) { return !!d.disabled });
10678
10679       if (!defaultState) {
10680         var key;
10681         defaultState = {};
10682         for (key in state) {
10683           if (state[key] instanceof Array)
10684             defaultState[key] = state[key].slice(0);
10685           else
10686             defaultState[key] = state[key];
10687         }
10688       }
10689
10690       //------------------------------------------------------------
10691       // Display noData message if there's nothing to show.
10692
10693       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
10694         var noDataText = container.selectAll('.nv-noData').data([noData]);
10695
10696         noDataText.enter().append('text')
10697           .attr('class', 'nvd3 nv-noData')
10698           .attr('dy', '-.7em')
10699           .style('text-anchor', 'middle');
10700
10701         noDataText
10702           .attr('x', margin.left + availableWidth / 2)
10703           .attr('y', margin.top + availableHeight / 2)
10704           .text(function(d) { return d });
10705
10706         return chart;
10707       } else {
10708         container.selectAll('.nv-noData').remove();
10709       }
10710
10711       //------------------------------------------------------------
10712
10713
10714       //------------------------------------------------------------
10715       // Setup Scales
10716
10717       x0 = x0 || x;
10718       y0 = y0 || y;
10719
10720       //------------------------------------------------------------
10721
10722
10723       //------------------------------------------------------------
10724       // Setup containers and skeleton of chart
10725
10726       var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
10727       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
10728       var gEnter = wrapEnter.append('g');
10729       var g = wrap.select('g');
10730
10731       // background for pointer events
10732       gEnter.append('rect').attr('class', 'nvd3 nv-background');
10733
10734       gEnter.append('g').attr('class', 'nv-x nv-axis');
10735       gEnter.append('g').attr('class', 'nv-y nv-axis');
10736       gEnter.append('g').attr('class', 'nv-scatterWrap');
10737       gEnter.append('g').attr('class', 'nv-distWrap');
10738       gEnter.append('g').attr('class', 'nv-legendWrap');
10739       gEnter.append('g').attr('class', 'nv-controlsWrap');
10740
10741       //------------------------------------------------------------
10742
10743
10744       //------------------------------------------------------------
10745       // Legend
10746
10747       if (showLegend) {
10748         legend.width( availableWidth / 2 );
10749
10750         wrap.select('.nv-legendWrap')
10751             .datum(data)
10752             .call(legend);
10753
10754         if ( margin.top != legend.height()) {
10755           margin.top = legend.height();
10756           availableHeight = (height || parseInt(container.style('height')) || 400)
10757                              - margin.top - margin.bottom;
10758         }
10759
10760         wrap.select('.nv-legendWrap')
10761             .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
10762       }
10763
10764       //------------------------------------------------------------
10765
10766
10767       //------------------------------------------------------------
10768       // Controls
10769
10770       if (showControls) {
10771         controls.width(180).color(['#444']);
10772         g.select('.nv-controlsWrap')
10773             .datum(controlsData)
10774             .attr('transform', 'translate(0,' + (-margin.top) +')')
10775             .call(controls);
10776       }
10777
10778       //------------------------------------------------------------
10779
10780
10781       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10782
10783
10784       //------------------------------------------------------------
10785       // Main Chart Component(s)
10786
10787       scatter
10788           .width(availableWidth)
10789           .height(availableHeight)
10790           .color(data.map(function(d,i) {
10791             return d.color || color(d, i);
10792           }).filter(function(d,i) { return !data[i].disabled }))
10793           .xDomain(null)
10794           .yDomain(null)
10795
10796       wrap.select('.nv-scatterWrap')
10797           .datum(data.filter(function(d) { return !d.disabled }))
10798           .call(scatter);
10799
10800
10801       //Adjust for x and y padding
10802       if (xPadding) {
10803         var xRange = x.domain()[1] - x.domain()[0];
10804         scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
10805       }
10806
10807       if (yPadding) {
10808         var yRange = y.domain()[1] - y.domain()[0];
10809         scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
10810       }
10811
10812       wrap.select('.nv-scatterWrap')
10813           .datum(data.filter(function(d) { return !d.disabled }))
10814           .call(scatter);
10815
10816       //------------------------------------------------------------
10817
10818
10819       //------------------------------------------------------------
10820       // Setup Axes
10821
10822       xAxis
10823           .scale(x)
10824           .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
10825           .tickSize( -availableHeight , 0);
10826
10827       g.select('.nv-x.nv-axis')
10828           .attr('transform', 'translate(0,' + y.range()[0] + ')')
10829           .call(xAxis);
10830
10831
10832       yAxis
10833           .scale(y)
10834           .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
10835           .tickSize( -availableWidth, 0);
10836
10837       g.select('.nv-y.nv-axis')
10838           .call(yAxis);
10839
10840
10841       if (showDistX) {
10842         distX
10843             .getData(scatter.x())
10844             .scale(x)
10845             .width(availableWidth)
10846             .color(data.map(function(d,i) {
10847               return d.color || color(d, i);
10848             }).filter(function(d,i) { return !data[i].disabled }));
10849         gEnter.select('.nv-distWrap').append('g')
10850             .attr('class', 'nv-distributionX');
10851         g.select('.nv-distributionX')
10852             .attr('transform', 'translate(0,' + y.range()[0] + ')')
10853             .datum(data.filter(function(d) { return !d.disabled }))
10854             .call(distX);
10855       }
10856
10857       if (showDistY) {
10858         distY
10859             .getData(scatter.y())
10860             .scale(y)
10861             .width(availableHeight)
10862             .color(data.map(function(d,i) {
10863               return d.color || color(d, i);
10864             }).filter(function(d,i) { return !data[i].disabled }));
10865         gEnter.select('.nv-distWrap').append('g')
10866             .attr('class', 'nv-distributionY');
10867         g.select('.nv-distributionY')
10868             .attr('transform', 'translate(-' + distY.size() + ',0)')
10869             .datum(data.filter(function(d) { return !d.disabled }))
10870             .call(distY);
10871       }
10872
10873       //------------------------------------------------------------
10874
10875
10876
10877
10878       if (d3.fisheye) {
10879         g.select('.nv-background')
10880             .attr('width', availableWidth)
10881             .attr('height', availableHeight);
10882
10883         g.select('.nv-background').on('mousemove', updateFisheye);
10884         g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
10885         scatter.dispatch.on('elementClick.freezeFisheye', function() {
10886           pauseFisheye = !pauseFisheye;
10887         });
10888       }
10889
10890
10891       function updateFisheye() {
10892         if (pauseFisheye) {
10893           g.select('.nv-point-paths').style('pointer-events', 'all');
10894           return false;
10895         }
10896
10897         g.select('.nv-point-paths').style('pointer-events', 'none' );
10898
10899         var mouse = d3.mouse(this);
10900         x.distortion(fisheye).focus(mouse[0]);
10901         y.distortion(fisheye).focus(mouse[1]);
10902
10903         g.select('.nv-scatterWrap')
10904             .call(scatter);
10905
10906         g.select('.nv-x.nv-axis').call(xAxis);
10907         g.select('.nv-y.nv-axis').call(yAxis);
10908         g.select('.nv-distributionX')
10909             .datum(data.filter(function(d) { return !d.disabled }))
10910             .call(distX);
10911         g.select('.nv-distributionY')
10912             .datum(data.filter(function(d) { return !d.disabled }))
10913             .call(distY);
10914       }
10915
10916
10917
10918       //============================================================
10919       // Event Handling/Dispatching (in chart's scope)
10920       //------------------------------------------------------------
10921
10922       controls.dispatch.on('legendClick', function(d,i) {
10923         d.disabled = !d.disabled;
10924
10925         fisheye = d.disabled ? 0 : 2.5;
10926         g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
10927         g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
10928
10929         if (d.disabled) {
10930           x.distortion(fisheye).focus(0);
10931           y.distortion(fisheye).focus(0);
10932
10933           g.select('.nv-scatterWrap').call(scatter);
10934           g.select('.nv-x.nv-axis').call(xAxis);
10935           g.select('.nv-y.nv-axis').call(yAxis);
10936         } else {
10937           pauseFisheye = false;
10938         }
10939
10940         chart.update();
10941       });
10942
10943       legend.dispatch.on('legendClick', function(d,i, that) {
10944         d.disabled = !d.disabled;
10945
10946         if (!data.filter(function(d) { return !d.disabled }).length) {
10947           data.map(function(d) {
10948             d.disabled = false;
10949             wrap.selectAll('.nv-series').classed('disabled', false);
10950             return d;
10951           });
10952         }
10953
10954         state.disabled = data.map(function(d) { return !!d.disabled });
10955         dispatch.stateChange(state);
10956
10957         chart.update();
10958       });
10959
10960       legend.dispatch.on('legendDblclick', function(d) {
10961           //Double clicking should always enable current series, and disabled all others.
10962           data.forEach(function(d) {
10963              d.disabled = true;
10964           });
10965           d.disabled = false;  
10966
10967           state.disabled = data.map(function(d) { return !!d.disabled });
10968           dispatch.stateChange(state);
10969           chart.update();
10970       });
10971
10972
10973       /*
10974       legend.dispatch.on('legendMouseover', function(d, i) {
10975         d.hover = true;
10976         chart(selection);
10977       });
10978
10979       legend.dispatch.on('legendMouseout', function(d, i) {
10980         d.hover = false;
10981         chart(selection);
10982       });
10983       */
10984
10985       scatter.dispatch.on('elementMouseover.tooltip', function(e) {
10986         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
10987             .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
10988         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
10989             .attr('x2', e.pos[0] + distX.size());
10990
10991         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10992         dispatch.tooltipShow(e);
10993       });
10994
10995       dispatch.on('tooltipShow', function(e) {
10996         if (tooltips) showTooltip(e, that.parentNode);
10997       });
10998
10999       // Update chart from a state object passed to event handler
11000       dispatch.on('changeState', function(e) {
11001
11002         if (typeof e.disabled !== 'undefined') {
11003           data.forEach(function(series,i) {
11004             series.disabled = e.disabled[i];
11005           });
11006
11007           state.disabled = e.disabled;
11008         }
11009
11010         chart.update();
11011       });
11012
11013       //============================================================
11014
11015
11016       //store old scales for use in transitions on update
11017       x0 = x.copy();
11018       y0 = y.copy();
11019
11020
11021     });
11022
11023     return chart;
11024   }
11025
11026
11027   //============================================================
11028   // Event Handling/Dispatching (out of chart's scope)
11029   //------------------------------------------------------------
11030
11031   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11032     dispatch.tooltipHide(e);
11033
11034     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11035         .attr('y1', 0);
11036     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11037         .attr('x2', distY.size());
11038   });
11039   dispatch.on('tooltipHide', function() {
11040     if (tooltips) nv.tooltip.cleanup();
11041   });
11042
11043   //============================================================
11044
11045
11046   //============================================================
11047   // Expose Public Variables
11048   //------------------------------------------------------------
11049
11050   // expose chart's sub-components
11051   chart.dispatch = dispatch;
11052   chart.scatter = scatter;
11053   chart.legend = legend;
11054   chart.controls = controls;
11055   chart.xAxis = xAxis;
11056   chart.yAxis = yAxis;
11057   chart.distX = distX;
11058   chart.distY = distY;
11059
11060   d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
11061
11062   chart.margin = function(_) {
11063     if (!arguments.length) return margin;
11064     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11065     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11066     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11067     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11068     return chart;
11069   };
11070
11071   chart.width = function(_) {
11072     if (!arguments.length) return width;
11073     width = _;
11074     return chart;
11075   };
11076
11077   chart.height = function(_) {
11078     if (!arguments.length) return height;
11079     height = _;
11080     return chart;
11081   };
11082
11083   chart.color = function(_) {
11084     if (!arguments.length) return color;
11085     color = nv.utils.getColor(_);
11086     legend.color(color);
11087     distX.color(color);
11088     distY.color(color);
11089     return chart;
11090   };
11091
11092   chart.showDistX = function(_) {
11093     if (!arguments.length) return showDistX;
11094     showDistX = _;
11095     return chart;
11096   };
11097
11098   chart.showDistY = function(_) {
11099     if (!arguments.length) return showDistY;
11100     showDistY = _;
11101     return chart;
11102   };
11103
11104   chart.showControls = function(_) {
11105     if (!arguments.length) return showControls;
11106     showControls = _;
11107     return chart;
11108   };
11109
11110   chart.showLegend = function(_) {
11111     if (!arguments.length) return showLegend;
11112     showLegend = _;
11113     return chart;
11114   };
11115
11116   chart.fisheye = function(_) {
11117     if (!arguments.length) return fisheye;
11118     fisheye = _;
11119     return chart;
11120   };
11121
11122   chart.xPadding = function(_) {
11123     if (!arguments.length) return xPadding;
11124     xPadding = _;
11125     return chart;
11126   };
11127
11128   chart.yPadding = function(_) {
11129     if (!arguments.length) return yPadding;
11130     yPadding = _;
11131     return chart;
11132   };
11133
11134   chart.tooltips = function(_) {
11135     if (!arguments.length) return tooltips;
11136     tooltips = _;
11137     return chart;
11138   };
11139
11140   chart.tooltipContent = function(_) {
11141     if (!arguments.length) return tooltip;
11142     tooltip = _;
11143     return chart;
11144   };
11145
11146   chart.tooltipXContent = function(_) {
11147     if (!arguments.length) return tooltipX;
11148     tooltipX = _;
11149     return chart;
11150   };
11151
11152   chart.tooltipYContent = function(_) {
11153     if (!arguments.length) return tooltipY;
11154     tooltipY = _;
11155     return chart;
11156   };
11157
11158   chart.state = function(_) {
11159     if (!arguments.length) return state;
11160     state = _;
11161     return chart;
11162   };
11163
11164   chart.defaultState = function(_) {
11165     if (!arguments.length) return defaultState;
11166     defaultState = _;
11167     return chart;
11168   };
11169   
11170   chart.noData = function(_) {
11171     if (!arguments.length) return noData;
11172     noData = _;
11173     return chart;
11174   };
11175
11176   //============================================================
11177
11178
11179   return chart;
11180 }
11181
11182 nv.models.scatterPlusLineChart = function() {
11183
11184   //============================================================
11185   // Public Variables with Default Settings
11186   //------------------------------------------------------------
11187
11188   var scatter      = nv.models.scatter()
11189     , xAxis        = nv.models.axis()
11190     , yAxis        = nv.models.axis()
11191     , legend       = nv.models.legend()
11192     , controls     = nv.models.legend()
11193     , distX        = nv.models.distribution()
11194     , distY        = nv.models.distribution()
11195     ;
11196
11197   var margin       = {top: 30, right: 20, bottom: 50, left: 75}
11198     , width        = null
11199     , height       = null
11200     , color        = nv.utils.defaultColor()
11201     , x            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
11202     , y            = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
11203     , showDistX    = false
11204     , showDistY    = false
11205     , showLegend   = true
11206     , showControls = !!d3.fisheye
11207     , fisheye      = 0
11208     , pauseFisheye = false
11209     , tooltips     = true
11210     , tooltipX     = function(key, x, y) { return '<strong>' + x + '</strong>' }
11211     , tooltipY     = function(key, x, y) { return '<strong>' + y + '</strong>' }
11212     , tooltip      = function(key, x, y, date) { return '<h3>' + key + '</h3>' 
11213                                                       + '<p>' + date + '</p>' }
11214     //, tooltip      = null
11215     , state = {}
11216     , defaultState = null
11217     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11218     , noData       = "No Data Available."
11219     ;
11220
11221   scatter
11222     .xScale(x)
11223     .yScale(y)
11224     ;
11225   xAxis
11226     .orient('bottom')
11227     .tickPadding(10)
11228     ;
11229   yAxis
11230     .orient('left')
11231     .tickPadding(10)
11232     ;
11233   distX
11234     .axis('x')
11235     ;
11236   distY
11237     .axis('y')
11238     ;
11239
11240   //============================================================
11241
11242
11243   //============================================================
11244   // Private Variables
11245   //------------------------------------------------------------
11246
11247   var x0, y0;
11248
11249   var showTooltip = function(e, offsetElement) {
11250     //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
11251
11252     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11253         top = e.pos[1] + ( offsetElement.offsetTop || 0),
11254         leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11255         topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
11256         leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
11257         topY = e.pos[1] + ( offsetElement.offsetTop || 0),
11258         xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
11259         yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
11260
11261       if( tooltipX != null )
11262           nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
11263       if( tooltipY != null )
11264           nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
11265       if( tooltip != null )
11266           nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
11267   };
11268
11269   var controlsData = [
11270     { key: 'Magnify', disabled: true }
11271   ];
11272
11273   //============================================================
11274
11275
11276   function chart(selection) {
11277     selection.each(function(data) {
11278       var container = d3.select(this),
11279           that = this;
11280
11281       var availableWidth = (width  || parseInt(container.style('width')) || 960)
11282                              - margin.left - margin.right,
11283           availableHeight = (height || parseInt(container.style('height')) || 400)
11284                              - margin.top - margin.bottom;
11285
11286       chart.update = function() { container.transition().call(chart); };
11287       chart.container = this;
11288
11289       //set state.disabled
11290       state.disabled = data.map(function(d) { return !!d.disabled });
11291
11292       if (!defaultState) {
11293         var key;
11294         defaultState = {};
11295         for (key in state) {
11296           if (state[key] instanceof Array)
11297             defaultState[key] = state[key].slice(0);
11298           else
11299             defaultState[key] = state[key];
11300         }
11301       }
11302
11303       //------------------------------------------------------------
11304       // Display noData message if there's nothing to show.
11305
11306       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11307         var noDataText = container.selectAll('.nv-noData').data([noData]);
11308
11309         noDataText.enter().append('text')
11310           .attr('class', 'nvd3 nv-noData')
11311           .attr('dy', '-.7em')
11312           .style('text-anchor', 'middle');
11313
11314         noDataText
11315           .attr('x', margin.left + availableWidth / 2)
11316           .attr('y', margin.top + availableHeight / 2)
11317           .text(function(d) { return d });
11318
11319         return chart;
11320       } else {
11321         container.selectAll('.nv-noData').remove();
11322       }
11323
11324       //------------------------------------------------------------
11325
11326
11327       //------------------------------------------------------------
11328       // Setup Scales
11329
11330       x = scatter.xScale();
11331       y = scatter.yScale();
11332
11333       x0 = x0 || x;
11334       y0 = y0 || y;
11335
11336       //------------------------------------------------------------
11337
11338
11339       //------------------------------------------------------------
11340       // Setup containers and skeleton of chart
11341
11342       var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11343       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11344       var gEnter = wrapEnter.append('g');
11345       var g = wrap.select('g')
11346
11347       // background for pointer events
11348       gEnter.append('rect').attr('class', 'nvd3 nv-background')
11349
11350       gEnter.append('g').attr('class', 'nv-x nv-axis');
11351       gEnter.append('g').attr('class', 'nv-y nv-axis');
11352       gEnter.append('g').attr('class', 'nv-scatterWrap');
11353       gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
11354       gEnter.append('g').attr('class', 'nv-distWrap');
11355       gEnter.append('g').attr('class', 'nv-legendWrap');
11356       gEnter.append('g').attr('class', 'nv-controlsWrap');
11357
11358       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11359
11360       //------------------------------------------------------------
11361
11362
11363       //------------------------------------------------------------
11364       // Legend
11365
11366       if (showLegend) {
11367         legend.width( availableWidth / 2 );
11368
11369         wrap.select('.nv-legendWrap')
11370             .datum(data)
11371             .call(legend);
11372
11373         if ( margin.top != legend.height()) {
11374           margin.top = legend.height();
11375           availableHeight = (height || parseInt(container.style('height')) || 400)
11376                              - margin.top - margin.bottom;
11377         }
11378
11379         wrap.select('.nv-legendWrap')
11380             .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
11381       }
11382
11383       //------------------------------------------------------------
11384
11385
11386       //------------------------------------------------------------
11387       // Controls
11388
11389       if (showControls) {
11390         controls.width(180).color(['#444']);
11391         g.select('.nv-controlsWrap')
11392             .datum(controlsData)
11393             .attr('transform', 'translate(0,' + (-margin.top) +')')
11394             .call(controls);
11395       }
11396
11397       //------------------------------------------------------------
11398
11399
11400       //------------------------------------------------------------
11401       // Main Chart Component(s)
11402
11403       scatter
11404           .width(availableWidth)
11405           .height(availableHeight)
11406           .color(data.map(function(d,i) {
11407             return d.color || color(d, i);
11408           }).filter(function(d,i) { return !data[i].disabled }))
11409
11410       wrap.select('.nv-scatterWrap')
11411           .datum(data.filter(function(d) { return !d.disabled }))
11412           .call(scatter);
11413
11414
11415       wrap.select('.nv-regressionLinesWrap')
11416           .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
11417
11418       var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
11419                       .data(function(d) { return d });
11420
11421       var reglines = regWrap.enter()
11422                        .append('g').attr('class', 'nv-regLines')
11423                        .append('line').attr('class', 'nv-regLine')
11424                        .style('stroke-opacity', 0);
11425
11426       //d3.transition(regWrap.selectAll('.nv-regLines line'))
11427       regWrap.selectAll('.nv-regLines line')
11428           .attr('x1', x.range()[0])
11429           .attr('x2', x.range()[1])
11430           .attr('y1', function(d,i) { return y(x.domain()[0] * d.slope + d.intercept) })
11431           .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
11432           .style('stroke', function(d,i,j) { return color(d,j) })
11433           .style('stroke-opacity', function(d,i) {
11434             return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 
11435           });
11436
11437
11438       //------------------------------------------------------------
11439
11440
11441       //------------------------------------------------------------
11442       // Setup Axes
11443
11444       xAxis
11445           .scale(x)
11446           .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
11447           .tickSize( -availableHeight , 0);
11448
11449       g.select('.nv-x.nv-axis')
11450           .attr('transform', 'translate(0,' + y.range()[0] + ')')
11451           .call(xAxis);
11452
11453
11454       yAxis
11455           .scale(y)
11456           .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
11457           .tickSize( -availableWidth, 0);
11458
11459       g.select('.nv-y.nv-axis')
11460           .call(yAxis);
11461
11462
11463       if (showDistX) {
11464         distX
11465             .getData(scatter.x())
11466             .scale(x)
11467             .width(availableWidth)
11468             .color(data.map(function(d,i) {
11469               return d.color || color(d, i);
11470             }).filter(function(d,i) { return !data[i].disabled }));
11471         gEnter.select('.nv-distWrap').append('g')
11472             .attr('class', 'nv-distributionX');
11473         g.select('.nv-distributionX')
11474             .attr('transform', 'translate(0,' + y.range()[0] + ')')
11475             .datum(data.filter(function(d) { return !d.disabled }))
11476             .call(distX);
11477       }
11478
11479       if (showDistY) {
11480         distY
11481             .getData(scatter.y())
11482             .scale(y)
11483             .width(availableHeight)
11484             .color(data.map(function(d,i) {
11485               return d.color || color(d, i);
11486             }).filter(function(d,i) { return !data[i].disabled }));
11487         gEnter.select('.nv-distWrap').append('g')
11488             .attr('class', 'nv-distributionY');
11489         g.select('.nv-distributionY')
11490             .attr('transform', 'translate(-' + distY.size() + ',0)')
11491             .datum(data.filter(function(d) { return !d.disabled }))
11492             .call(distY);
11493       }
11494
11495       //------------------------------------------------------------
11496
11497
11498
11499
11500       if (d3.fisheye) {
11501         g.select('.nv-background')
11502             .attr('width', availableWidth)
11503             .attr('height', availableHeight);
11504
11505         g.select('.nv-background').on('mousemove', updateFisheye);
11506         g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
11507         scatter.dispatch.on('elementClick.freezeFisheye', function() {
11508           pauseFisheye = !pauseFisheye;
11509         });
11510       }
11511
11512
11513       function updateFisheye() {
11514         if (pauseFisheye) {
11515           g.select('.nv-point-paths').style('pointer-events', 'all');
11516           return false;
11517         }
11518
11519         g.select('.nv-point-paths').style('pointer-events', 'none' );
11520
11521         var mouse = d3.mouse(this);
11522         x.distortion(fisheye).focus(mouse[0]);
11523         y.distortion(fisheye).focus(mouse[1]);
11524
11525         g.select('.nv-scatterWrap')
11526             .datum(data.filter(function(d) { return !d.disabled }))
11527             .call(scatter);
11528         g.select('.nv-x.nv-axis').call(xAxis);
11529         g.select('.nv-y.nv-axis').call(yAxis);
11530         g.select('.nv-distributionX')
11531             .datum(data.filter(function(d) { return !d.disabled }))
11532             .call(distX);
11533         g.select('.nv-distributionY')
11534             .datum(data.filter(function(d) { return !d.disabled }))
11535             .call(distY);
11536       }
11537
11538
11539
11540       //============================================================
11541       // Event Handling/Dispatching (in chart's scope)
11542       //------------------------------------------------------------
11543
11544       controls.dispatch.on('legendClick', function(d,i) {
11545         d.disabled = !d.disabled;
11546
11547         fisheye = d.disabled ? 0 : 2.5;
11548         g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
11549         g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
11550
11551         if (d.disabled) {
11552           x.distortion(fisheye).focus(0);
11553           y.distortion(fisheye).focus(0);
11554
11555           g.select('.nv-scatterWrap').call(scatter);
11556           g.select('.nv-x.nv-axis').call(xAxis);
11557           g.select('.nv-y.nv-axis').call(yAxis);
11558         } else {
11559           pauseFisheye = false;
11560         }
11561
11562         chart.update();
11563       });
11564
11565       legend.dispatch.on('legendClick', function(d,i, that) {
11566         d.disabled = !d.disabled;
11567
11568         if (!data.filter(function(d) { return !d.disabled }).length) {
11569           data.map(function(d) {
11570             d.disabled = false;
11571             wrap.selectAll('.nv-series').classed('disabled', false);
11572             return d;
11573           });
11574         }
11575
11576         state.disabled = data.map(function(d) { return !!d.disabled });
11577         dispatch.stateChange(state);
11578
11579         chart.update();
11580       });
11581
11582       legend.dispatch.on('legendDblclick', function(d) {
11583           //Double clicking should always enable current series, and disabled all others.
11584           data.forEach(function(d) {
11585              d.disabled = true;
11586           });
11587           d.disabled = false;  
11588
11589           state.disabled = data.map(function(d) { return !!d.disabled });
11590           dispatch.stateChange(state);
11591           chart.update();
11592       });
11593
11594
11595       /*
11596       legend.dispatch.on('legendMouseover', function(d, i) {
11597         d.hover = true;
11598         chart(selection);
11599       });
11600
11601       legend.dispatch.on('legendMouseout', function(d, i) {
11602         d.hover = false;
11603         chart(selection);
11604       });
11605       */
11606
11607       scatter.dispatch.on('elementMouseover.tooltip', function(e) {
11608         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11609             .attr('y1', e.pos[1] - availableHeight);
11610         d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11611             .attr('x2', e.pos[0] + distX.size());
11612
11613         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11614         dispatch.tooltipShow(e);
11615       });
11616
11617       dispatch.on('tooltipShow', function(e) {
11618         if (tooltips) showTooltip(e, that.parentNode);
11619       });
11620
11621       // Update chart from a state object passed to event handler
11622       dispatch.on('changeState', function(e) {
11623
11624         if (typeof e.disabled !== 'undefined') {
11625           data.forEach(function(series,i) {
11626             series.disabled = e.disabled[i];
11627           });
11628
11629           state.disabled = e.disabled;
11630         }
11631
11632         chart.update();
11633       });
11634
11635       //============================================================
11636
11637
11638       //store old scales for use in transitions on update
11639       x0 = x.copy();
11640       y0 = y.copy();
11641
11642
11643     });
11644
11645     return chart;
11646   }
11647
11648
11649   //============================================================
11650   // Event Handling/Dispatching (out of chart's scope)
11651   //------------------------------------------------------------
11652
11653   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11654     dispatch.tooltipHide(e);
11655
11656     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11657         .attr('y1', 0);
11658     d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11659         .attr('x2', distY.size());
11660   });
11661   dispatch.on('tooltipHide', function() {
11662     if (tooltips) nv.tooltip.cleanup();
11663   });
11664
11665   //============================================================
11666
11667
11668   //============================================================
11669   // Expose Public Variables
11670   //------------------------------------------------------------
11671
11672   // expose chart's sub-components
11673   chart.dispatch = dispatch;
11674   chart.scatter = scatter;
11675   chart.legend = legend;
11676   chart.controls = controls;
11677   chart.xAxis = xAxis;
11678   chart.yAxis = yAxis;
11679   chart.distX = distX;
11680   chart.distY = distY;
11681
11682   d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
11683
11684   chart.margin = function(_) {
11685     if (!arguments.length) return margin;
11686     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11687     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11688     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11689     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11690     return chart;
11691   };
11692
11693   chart.width = function(_) {
11694     if (!arguments.length) return width;
11695     width = _;
11696     return chart;
11697   };
11698
11699   chart.height = function(_) {
11700     if (!arguments.length) return height;
11701     height = _;
11702     return chart;
11703   };
11704
11705   chart.color = function(_) {
11706     if (!arguments.length) return color;
11707     color = nv.utils.getColor(_);
11708     legend.color(color);
11709     distX.color(color);
11710     distY.color(color);
11711     return chart;
11712   };
11713
11714   chart.showDistX = function(_) {
11715     if (!arguments.length) return showDistX;
11716     showDistX = _;
11717     return chart;
11718   };
11719
11720   chart.showDistY = function(_) {
11721     if (!arguments.length) return showDistY;
11722     showDistY = _;
11723     return chart;
11724   };
11725
11726   chart.showControls = function(_) {
11727     if (!arguments.length) return showControls;
11728     showControls = _;
11729     return chart;
11730   };
11731
11732   chart.showLegend = function(_) {
11733     if (!arguments.length) return showLegend;
11734     showLegend = _;
11735     return chart;
11736   };
11737
11738   chart.fisheye = function(_) {
11739     if (!arguments.length) return fisheye;
11740     fisheye = _;
11741     return chart;
11742   };
11743
11744   chart.tooltips = function(_) {
11745     if (!arguments.length) return tooltips;
11746     tooltips = _;
11747     return chart;
11748   };
11749
11750   chart.tooltipContent = function(_) {
11751     if (!arguments.length) return tooltip;
11752     tooltip = _;
11753     return chart;
11754   };
11755
11756   chart.tooltipXContent = function(_) {
11757     if (!arguments.length) return tooltipX;
11758     tooltipX = _;
11759     return chart;
11760   };
11761
11762   chart.tooltipYContent = function(_) {
11763     if (!arguments.length) return tooltipY;
11764     tooltipY = _;
11765     return chart;
11766   };
11767
11768   chart.state = function(_) {
11769     if (!arguments.length) return state;
11770     state = _;
11771     return chart;
11772   };
11773
11774   chart.defaultState = function(_) {
11775     if (!arguments.length) return defaultState;
11776     defaultState = _;
11777     return chart;
11778   };
11779
11780   chart.noData = function(_) {
11781     if (!arguments.length) return noData;
11782     noData = _;
11783     return chart;
11784   };
11785
11786   //============================================================
11787
11788
11789   return chart;
11790 }
11791
11792 nv.models.sparkline = function() {
11793
11794   //============================================================
11795   // Public Variables with Default Settings
11796   //------------------------------------------------------------
11797
11798   var margin = {top: 2, right: 0, bottom: 2, left: 0}
11799     , width = 400
11800     , height = 32
11801     , animate = true
11802     , x = d3.scale.linear()
11803     , y = d3.scale.linear()
11804     , getX = function(d) { return d.x }
11805     , getY = function(d) { return d.y }
11806     , color = nv.utils.getColor(['#000'])
11807     , xDomain
11808     , yDomain
11809     ;
11810
11811   //============================================================
11812
11813
11814   function chart(selection) {
11815     selection.each(function(data) {
11816       var availableWidth = width - margin.left - margin.right,
11817           availableHeight = height - margin.top - margin.bottom,
11818           container = d3.select(this);
11819
11820
11821       //------------------------------------------------------------
11822       // Setup Scales
11823
11824       x   .domain(xDomain || d3.extent(data, getX ))
11825           .range([0, availableWidth]);
11826
11827       y   .domain(yDomain || d3.extent(data, getY ))
11828           .range([availableHeight, 0]);
11829
11830       //------------------------------------------------------------
11831
11832
11833       //------------------------------------------------------------
11834       // Setup containers and skeleton of chart
11835
11836       var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
11837       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
11838       var gEnter = wrapEnter.append('g');
11839       var g = wrap.select('g');
11840
11841       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
11842
11843       //------------------------------------------------------------
11844
11845
11846       var paths = wrap.selectAll('path')
11847           .data(function(d) { return [d] });
11848       paths.enter().append('path');
11849       paths.exit().remove();
11850       paths
11851           .style('stroke', function(d,i) { return d.color || color(d, i) })
11852           .attr('d', d3.svg.line()
11853             .x(function(d,i) { return x(getX(d,i)) })
11854             .y(function(d,i) { return y(getY(d,i)) })
11855           );
11856
11857
11858       // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
11859       var points = wrap.selectAll('circle.nv-point')
11860           .data(function(data) {
11861               var yValues = data.map(function(d, i) { return getY(d,i); });
11862               function pointIndex(index) {
11863                   if (index != -1) {
11864                       var result = data[index];
11865                       result.pointIndex = index;
11866                       return result;
11867                   } else {
11868                       return null;
11869                   }
11870               }
11871               var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
11872                   minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
11873                   currentPoint = pointIndex(yValues.length - 1);
11874               return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
11875           });
11876       points.enter().append('circle');
11877       points.exit().remove();
11878       points
11879           .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
11880           .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
11881           .attr('r', 2)
11882           .attr('class', function(d,i) {
11883             return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
11884                    getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
11885           });
11886     });
11887
11888     return chart;
11889   }
11890
11891
11892   //============================================================
11893   // Expose Public Variables
11894   //------------------------------------------------------------
11895
11896   chart.margin = function(_) {
11897     if (!arguments.length) return margin;
11898     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
11899     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
11900     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11901     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
11902     return chart;
11903   };
11904
11905   chart.width = function(_) {
11906     if (!arguments.length) return width;
11907     width = _;
11908     return chart;
11909   };
11910
11911   chart.height = function(_) {
11912     if (!arguments.length) return height;
11913     height = _;
11914     return chart;
11915   };
11916
11917   chart.x = function(_) {
11918     if (!arguments.length) return getX;
11919     getX = d3.functor(_);
11920     return chart;
11921   };
11922
11923   chart.y = function(_) {
11924     if (!arguments.length) return getY;
11925     getY = d3.functor(_);
11926     return chart;
11927   };
11928
11929   chart.xScale = function(_) {
11930     if (!arguments.length) return x;
11931     x = _;
11932     return chart;
11933   };
11934
11935   chart.yScale = function(_) {
11936     if (!arguments.length) return y;
11937     y = _;
11938     return chart;
11939   };
11940
11941   chart.xDomain = function(_) {
11942     if (!arguments.length) return xDomain;
11943     xDomain = _;
11944     return chart;
11945   };
11946
11947   chart.yDomain = function(_) {
11948     if (!arguments.length) return yDomain;
11949     yDomain = _;
11950     return chart;
11951   };
11952
11953   chart.animate = function(_) {
11954     if (!arguments.length) return animate;
11955     animate = _;
11956     return chart;
11957   };
11958
11959   chart.color = function(_) {
11960     if (!arguments.length) return color;
11961     color = nv.utils.getColor(_);
11962     return chart;
11963   };
11964
11965   //============================================================
11966
11967
11968   return chart;
11969 }
11970
11971 nv.models.sparklinePlus = function() {
11972
11973   //============================================================
11974   // Public Variables with Default Settings
11975   //------------------------------------------------------------
11976
11977   var sparkline = nv.models.sparkline();
11978
11979   var margin = {top: 15, right: 100, bottom: 10, left: 50}
11980     , width = null
11981     , height = null
11982     , x
11983     , y
11984     , index = []
11985     , paused = false
11986     , xTickFormat = d3.format(',r')
11987     , yTickFormat = d3.format(',.2f')
11988     , showValue = true
11989     , alignValue = true
11990     , rightAlignValue = false
11991     , noData = "No Data Available."
11992     ;
11993
11994   //============================================================
11995
11996
11997   function chart(selection) {
11998     selection.each(function(data) {
11999       var container = d3.select(this);
12000
12001       var availableWidth = (width  || parseInt(container.style('width')) || 960)
12002                              - margin.left - margin.right,
12003           availableHeight = (height || parseInt(container.style('height')) || 400)
12004                              - margin.top - margin.bottom;
12005
12006       
12007
12008       chart.update = function() { chart(selection) };
12009       chart.container = this;
12010
12011
12012       //------------------------------------------------------------
12013       // Display No Data message if there's nothing to show.
12014
12015       if (!data || !data.length) {
12016         var noDataText = container.selectAll('.nv-noData').data([noData]);
12017
12018         noDataText.enter().append('text')
12019           .attr('class', 'nvd3 nv-noData')
12020           .attr('dy', '-.7em')
12021           .style('text-anchor', 'middle');
12022
12023         noDataText
12024           .attr('x', margin.left + availableWidth / 2)
12025           .attr('y', margin.top + availableHeight / 2)
12026           .text(function(d) { return d });
12027
12028         return chart;
12029       } else {
12030         container.selectAll('.nv-noData').remove();
12031       }
12032
12033       var currentValue = sparkline.y()(data[data.length-1], data.length-1);
12034
12035       //------------------------------------------------------------
12036
12037
12038
12039       //------------------------------------------------------------
12040       // Setup Scales
12041
12042       x = sparkline.xScale();
12043       y = sparkline.yScale();
12044
12045       //------------------------------------------------------------
12046
12047
12048       //------------------------------------------------------------
12049       // Setup containers and skeleton of chart
12050
12051       var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
12052       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
12053       var gEnter = wrapEnter.append('g');
12054       var g = wrap.select('g');
12055
12056       gEnter.append('g').attr('class', 'nv-sparklineWrap');
12057       gEnter.append('g').attr('class', 'nv-valueWrap');
12058       gEnter.append('g').attr('class', 'nv-hoverArea');
12059
12060       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12061
12062       //------------------------------------------------------------
12063
12064
12065       //------------------------------------------------------------
12066       // Main Chart Component(s)
12067
12068       var sparklineWrap = g.select('.nv-sparklineWrap');
12069
12070       sparkline
12071         .width(availableWidth)
12072         .height(availableHeight);
12073
12074       sparklineWrap
12075           .call(sparkline);
12076
12077       //------------------------------------------------------------
12078
12079
12080       var valueWrap = g.select('.nv-valueWrap');
12081       
12082       var value = valueWrap.selectAll('.nv-currentValue')
12083           .data([currentValue]);
12084
12085       value.enter().append('text').attr('class', 'nv-currentValue')
12086           .attr('dx', rightAlignValue ? -8 : 8)
12087           .attr('dy', '.9em')
12088           .style('text-anchor', rightAlignValue ? 'end' : 'start');
12089
12090       value
12091           .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
12092           .attr('y', alignValue ? function(d) { return y(d) } : 0)
12093           .style('fill', sparkline.color()(data[data.length-1], data.length-1))
12094           .text(yTickFormat(currentValue));
12095
12096
12097
12098       gEnter.select('.nv-hoverArea').append('rect')
12099           .on('mousemove', sparklineHover)
12100           .on('click', function() { paused = !paused })
12101           .on('mouseout', function() { index = []; updateValueLine(); });
12102           //.on('mouseout', function() { index = null; updateValueLine(); });
12103
12104       g.select('.nv-hoverArea rect')
12105           .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
12106           .attr('width', availableWidth + margin.left + margin.right)
12107           .attr('height', availableHeight + margin.top);
12108
12109
12110
12111       function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
12112         if (paused) return;
12113
12114         var hoverValue = g.selectAll('.nv-hoverValue').data(index)
12115
12116         var hoverEnter = hoverValue.enter()
12117           .append('g').attr('class', 'nv-hoverValue')
12118             .style('stroke-opacity', 0)
12119             .style('fill-opacity', 0);
12120
12121         hoverValue.exit()
12122           .transition().duration(250)
12123             .style('stroke-opacity', 0)
12124             .style('fill-opacity', 0)
12125             .remove();
12126
12127         hoverValue
12128             .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
12129           .transition().duration(250)
12130             .style('stroke-opacity', 1)
12131             .style('fill-opacity', 1);
12132
12133         if (!index.length) return;
12134
12135         hoverEnter.append('line')
12136             .attr('x1', 0)
12137             .attr('y1', -margin.top)
12138             .attr('x2', 0)
12139             .attr('y2', availableHeight);
12140
12141
12142         hoverEnter.append('text').attr('class', 'nv-xValue')
12143             .attr('x', -6)
12144             .attr('y', -margin.top)
12145             .attr('text-anchor', 'end')
12146             .attr('dy', '.9em')
12147
12148
12149         g.select('.nv-hoverValue .nv-xValue')
12150             .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
12151
12152         hoverEnter.append('text').attr('class', 'nv-yValue')
12153             .attr('x', 6)
12154             .attr('y', -margin.top)
12155             .attr('text-anchor', 'start')
12156             .attr('dy', '.9em')
12157
12158         g.select('.nv-hoverValue .nv-yValue')
12159             .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
12160
12161       }
12162
12163
12164       function sparklineHover() {
12165         if (paused) return;
12166
12167         var pos = d3.mouse(this)[0] - margin.left;
12168
12169         function getClosestIndex(data, x) {
12170           var distance = Math.abs(sparkline.x()(data[0], 0) - x);
12171           var closestIndex = 0;
12172           for (var i = 0; i < data.length; i++){
12173             if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
12174               distance = Math.abs(sparkline.x()(data[i], i) - x);
12175               closestIndex = i;
12176             }
12177           }
12178           return closestIndex;
12179         }
12180
12181         index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12182
12183         updateValueLine();
12184       }
12185
12186     });
12187
12188     return chart;
12189   }
12190
12191
12192   //============================================================
12193   // Expose Public Variables
12194   //------------------------------------------------------------
12195
12196   // expose chart's sub-components
12197   chart.sparkline = sparkline;
12198
12199   d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
12200
12201   chart.margin = function(_) {
12202     if (!arguments.length) return margin;
12203     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
12204     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
12205     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12206     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
12207     return chart;
12208   };
12209
12210   chart.width = function(_) {
12211     if (!arguments.length) return width;
12212     width = _;
12213     return chart;
12214   };
12215
12216   chart.height = function(_) {
12217     if (!arguments.length) return height;
12218     height = _;
12219     return chart;
12220   };
12221
12222   chart.xTickFormat = function(_) {
12223     if (!arguments.length) return xTickFormat;
12224     xTickFormat = _;
12225     return chart;
12226   };
12227
12228   chart.yTickFormat = function(_) {
12229     if (!arguments.length) return yTickFormat;
12230     yTickFormat = _;
12231     return chart;
12232   };
12233
12234   chart.showValue = function(_) {
12235     if (!arguments.length) return showValue;
12236     showValue = _;
12237     return chart;
12238   };
12239
12240   chart.alignValue = function(_) {
12241     if (!arguments.length) return alignValue;
12242     alignValue = _;
12243     return chart;
12244   };
12245
12246   chart.rightAlignValue = function(_) {
12247     if (!arguments.length) return rightAlignValue;
12248     rightAlignValue = _;
12249     return chart;
12250   };
12251
12252   chart.noData = function(_) {
12253     if (!arguments.length) return noData;
12254     noData = _;
12255     return chart;
12256   };
12257
12258   //============================================================
12259
12260
12261   return chart;
12262 }
12263
12264 nv.models.stackedArea = function() {
12265
12266   //============================================================
12267   // Public Variables with Default Settings
12268   //------------------------------------------------------------
12269
12270   var margin = {top: 0, right: 0, bottom: 0, left: 0}
12271     , width = 960
12272     , height = 500
12273     , color = nv.utils.defaultColor() // a function that computes the color
12274     , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
12275     , getX = function(d) { return d.x } // accessor to get the x value from a data point
12276     , getY = function(d) { return d.y } // accessor to get the y value from a data point
12277     , style = 'stack'
12278     , offset = 'zero'
12279     , order = 'default'
12280     , interpolate = 'linear'  // controls the line interpolation
12281     , clipEdge = false // if true, masks lines within x and y scale
12282     , x //can be accessed via chart.xScale()
12283     , y //can be accessed via chart.yScale()
12284         , yAxisTooltipFormat = d3.format(',.3f')
12285     , scatter = nv.models.scatter()
12286     , dispatch =  d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
12287     ;
12288
12289   scatter
12290     .size(2.2) // default size
12291     .sizeDomain([2.2,2.2]) // all the same size by default
12292     ;
12293
12294   /************************************
12295    * offset:
12296    *   'wiggle' (stream)
12297    *   'zero' (stacked)
12298    *   'expand' (normalize to 100%)
12299    *   'silhouette' (simple centered)
12300    *
12301    * order:
12302    *   'inside-out' (stream)
12303    *   'default' (input order)
12304    ************************************/
12305
12306   //============================================================
12307
12308
12309   function chart(selection) {
12310     selection.each(function(data) {
12311       var availableWidth = width - margin.left - margin.right,
12312           availableHeight = height - margin.top - margin.bottom,
12313           container = d3.select(this);
12314
12315       //------------------------------------------------------------
12316       // Setup Scales
12317
12318       x = scatter.xScale();
12319       y = scatter.yScale();
12320
12321       //------------------------------------------------------------
12322
12323
12324       // Injecting point index into each point because d3.layout.stack().out does not give index
12325       // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled
12326       data = data.map(function(aseries, i) {
12327                aseries.values = aseries.values.map(function(d, j) {
12328                  d.index = j;
12329                  d.stackedY = aseries.disabled ? 0 : getY(d,j);
12330                  return d;
12331                })
12332                return aseries;
12333              });
12334
12335
12336       data = d3.layout.stack()
12337                .order(order)
12338                .offset(offset)
12339                .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
12340                .x(getX)
12341                .y(function(d) { return d.stackedY })
12342                .out(function(d, y0, y) {
12343                   d.display = {
12344                     y: y,
12345                    y0: y0
12346                   };
12347                 })
12348               (data);
12349
12350
12351       //------------------------------------------------------------
12352       // Setup containers and skeleton of chart
12353
12354       var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
12355       var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
12356       var defsEnter = wrapEnter.append('defs');
12357       var gEnter = wrapEnter.append('g');
12358       var g = wrap.select('g');
12359
12360       gEnter.append('g').attr('class', 'nv-areaWrap');
12361       gEnter.append('g').attr('class', 'nv-scatterWrap');
12362
12363       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12364
12365       //------------------------------------------------------------
12366
12367
12368       scatter
12369         .width(availableWidth)
12370         .height(availableHeight)
12371         .x(getX)
12372         .y(function(d) { return d.display.y + d.display.y0 })
12373         .forceY([0])
12374         .color(data.map(function(d,i) {
12375           return d.color || color(d, i);
12376         }).filter(function(d,i) { return !data[i].disabled }));
12377
12378
12379       var scatterWrap = g.select('.nv-scatterWrap')
12380           .datum(data.filter(function(d) { return !d.disabled }))
12381
12382       //d3.transition(scatterWrap).call(scatter);
12383       scatterWrap.call(scatter);
12384
12385
12386
12387
12388
12389       defsEnter.append('clipPath')
12390           .attr('id', 'nv-edge-clip-' + id)
12391         .append('rect');
12392
12393       wrap.select('#nv-edge-clip-' + id + ' rect')
12394           .attr('width', availableWidth)
12395           .attr('height', availableHeight);
12396
12397       g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
12398
12399
12400
12401
12402       var area = d3.svg.area()
12403           .x(function(d,i)  { return x(getX(d,i)) })
12404           .y0(function(d) { return y(d.display.y0) })
12405           .y1(function(d) { return y(d.display.y + d.display.y0) })
12406           .interpolate(interpolate);
12407
12408       var zeroArea = d3.svg.area()
12409           .x(function(d,i)  { return x(getX(d,i)) })
12410           .y0(function(d) { return y(d.display.y0) })
12411           .y1(function(d) { return y(d.display.y0) });
12412
12413
12414       var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
12415           .data(function(d) { return d });
12416           //.data(function(d) { return d }, function(d) { return d.key });
12417       path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
12418           .on('mouseover', function(d,i) {
12419             d3.select(this).classed('hover', true);
12420             dispatch.areaMouseover({
12421               point: d,
12422               series: d.key,
12423               pos: [d3.event.pageX, d3.event.pageY],
12424               seriesIndex: i
12425             });
12426           })
12427           .on('mouseout', function(d,i) {
12428             d3.select(this).classed('hover', false);
12429             dispatch.areaMouseout({
12430               point: d,
12431               series: d.key,
12432               pos: [d3.event.pageX, d3.event.pageY],
12433               seriesIndex: i
12434             });
12435           })
12436           .on('click', function(d,i) {
12437             d3.select(this).classed('hover', false);
12438             dispatch.areaClick({
12439               point: d,
12440               series: d.key,
12441               pos: [d3.event.pageX, d3.event.pageY],
12442               seriesIndex: i
12443             });
12444           })
12445       //d3.transition(path.exit())
12446       path.exit()
12447           .attr('d', function(d,i) { return zeroArea(d.values,i) })
12448           .remove();
12449       path
12450           .style('fill', function(d,i){ return d.color || color(d, i) })
12451           .style('stroke', function(d,i){ return d.color || color(d, i) });
12452       //d3.transition(path)
12453       path
12454           .attr('d', function(d,i) { return area(d.values,i) })
12455
12456
12457       //============================================================
12458       // Event Handling/Dispatching (in chart's scope)
12459       //------------------------------------------------------------
12460
12461       scatter.dispatch.on('elementMouseover.area', function(e) {
12462         g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
12463       });
12464       scatter.dispatch.on('elementMouseout.area', function(e) {
12465         g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
12466       });
12467
12468       //============================================================
12469
12470     });
12471
12472
12473     return chart;
12474   }
12475
12476
12477   //============================================================
12478   // Event Handling/Dispatching (out of chart's scope)
12479   //------------------------------------------------------------
12480
12481   scatter.dispatch.on('elementClick.area', function(e) {
12482     dispatch.areaClick(e);
12483   })
12484   scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12485         e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
12486         dispatch.tooltipShow(e);
12487   });
12488   scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12489         dispatch.tooltipHide(e);
12490   });
12491
12492   //============================================================
12493
12494
12495   //============================================================
12496   // Global getters and setters
12497   //------------------------------------------------------------
12498
12499   chart.dispatch = dispatch;
12500   chart.scatter = scatter;
12501
12502   d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius');
12503
12504   chart.x = function(_) {
12505     if (!arguments.length) return getX;
12506     getX = d3.functor(_);
12507     return chart;
12508   };
12509
12510   chart.y = function(_) {
12511     if (!arguments.length) return getY;
12512     getY = d3.functor(_);
12513     return chart;
12514   }
12515
12516   chart.margin = function(_) {
12517     if (!arguments.length) return margin;
12518     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
12519     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
12520     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12521     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
12522     return chart;
12523   };
12524
12525   chart.width = function(_) {
12526     if (!arguments.length) return width;
12527     width = _;
12528     return chart;
12529   };
12530
12531   chart.height = function(_) {
12532     if (!arguments.length) return height;
12533     height = _;
12534     return chart;
12535   };
12536
12537   chart.clipEdge = function(_) {
12538     if (!arguments.length) return clipEdge;
12539     clipEdge = _;
12540     return chart;
12541   };
12542
12543   chart.color = function(_) {
12544     if (!arguments.length) return color;
12545     color = nv.utils.getColor(_);
12546     return chart;
12547   };
12548
12549   chart.offset = function(_) {
12550     if (!arguments.length) return offset;
12551     offset = _;
12552     return chart;
12553   };
12554
12555   chart.order = function(_) {
12556     if (!arguments.length) return order;
12557     order = _;
12558     return chart;
12559   };
12560
12561   //shortcut for offset + order
12562   chart.style = function(_) {
12563     if (!arguments.length) return style;
12564     style = _;
12565
12566     switch (style) {
12567       case 'stack':
12568         chart.offset('zero');
12569         chart.order('default');
12570         break;
12571       case 'stream':
12572         chart.offset('wiggle');
12573         chart.order('inside-out');
12574         break;
12575       case 'stream-center':
12576           chart.offset('silhouette');
12577           chart.order('inside-out');
12578           break;
12579       case 'expand':
12580         chart.offset('expand');
12581         chart.order('default');
12582         break;
12583     }
12584
12585     return chart;
12586   };
12587
12588   chart.interpolate = function(_) {
12589             if (!arguments.length) return interpolate;
12590             interpolate = _;
12591             return interpolate;
12592   
12593   };
12594   
12595   //============================================================
12596
12597
12598   return chart;
12599 }
12600
12601 nv.models.stackedAreaChart = function() {
12602
12603   //============================================================
12604   // Public Variables with Default Settings
12605   //------------------------------------------------------------
12606
12607   var stacked = nv.models.stackedArea()
12608     , xAxis = nv.models.axis()
12609     , yAxis = nv.models.axis()
12610     , legend = nv.models.legend()
12611     , controls = nv.models.legend()
12612     ;
12613
12614   var margin = {top: 30, right: 25, bottom: 50, left: 60}
12615     , width = null
12616     , height = null
12617     , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
12618     , showControls = true
12619     , showLegend = true
12620     , tooltips = true
12621     , tooltip = function(key, x, y, e, graph) {
12622         return '<h3>' + key + '</h3>' +
12623                '<p>' +  y + ' on ' + x + '</p>'
12624       }
12625     , x //can be accessed via chart.xScale()
12626     , y //can be accessed via chart.yScale()
12627     , yAxisTickFormat = d3.format(',.2f')
12628         , yAxisTooltipFormat = d3.format(',.3f')
12629     , state = { style: stacked.style() }
12630     , defaultState = null
12631     , noData = 'No Data Available.'
12632     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
12633     , controlWidth = 250
12634     ;
12635
12636   xAxis
12637     .orient('bottom')
12638     .tickPadding(7)
12639     ;
12640   yAxis
12641     .orient('left')
12642     ;
12643   stacked.scatter
12644     .pointActive(function(d) {
12645       //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100));
12646       return !!Math.round(stacked.y()(d) * 100);
12647     })
12648     ;
12649
12650   //============================================================
12651
12652
12653   //============================================================
12654   // Private Variables
12655   //------------------------------------------------------------
12656
12657   var showTooltip = function(e, offsetElement) {
12658     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12659         top = e.pos[1] + ( offsetElement.offsetTop || 0),
12660         x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
12661         //y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
12662                  y = yAxisTooltipFormat(stacked.y()(e.point, e.pointIndex)),
12663         content = tooltip(e.series.key, x, y, e, chart);
12664
12665     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
12666   };
12667
12668   //============================================================
12669
12670
12671   function chart(selection) {
12672     selection.each(function(data) {
12673       var container = d3.select(this),
12674           that = this;
12675
12676       var availableWidth = (width  || parseInt(container.style('width')) || 960)
12677                              - margin.left - margin.right,
12678           availableHeight = (height || parseInt(container.style('height')) || 400)
12679                              - margin.top - margin.bottom;
12680
12681       chart.update = function() { container.transition().call(chart); };
12682       chart.container = this;
12683
12684       //set state.disabled
12685       state.disabled = data.map(function(d) { return !!d.disabled });
12686
12687       if (!defaultState) {
12688         var key;
12689         defaultState = {};
12690         for (key in state) {
12691           if (state[key] instanceof Array)
12692             defaultState[key] = state[key].slice(0);
12693           else
12694             defaultState[key] = state[key];
12695         }
12696       }
12697
12698       //------------------------------------------------------------
12699       // Display No Data message if there's nothing to show.
12700
12701       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12702         var noDataText = container.selectAll('.nv-noData').data([noData]);
12703
12704         noDataText.enter().append('text')
12705           .attr('class', 'nvd3 nv-noData')
12706           .attr('dy', '-.7em')
12707           .style('text-anchor', 'middle');
12708
12709         noDataText
12710           .attr('x', margin.left + availableWidth / 2)
12711           .attr('y', margin.top + availableHeight / 2)
12712           .text(function(d) { return d });
12713
12714         return chart;
12715       } else {
12716         container.selectAll('.nv-noData').remove();
12717       }
12718
12719       //------------------------------------------------------------
12720
12721
12722       //------------------------------------------------------------
12723       // Setup Scales
12724
12725       x = stacked.xScale();
12726       y = stacked.yScale();
12727
12728       //------------------------------------------------------------
12729
12730
12731       //------------------------------------------------------------
12732       // Setup containers and skeleton of chart
12733
12734       var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
12735       var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
12736       var g = wrap.select('g');
12737
12738       gEnter.append('g').attr('class', 'nv-x nv-axis');
12739       gEnter.append('g').attr('class', 'nv-y nv-axis');
12740       gEnter.append('g').attr('class', 'nv-stackedWrap');
12741       gEnter.append('g').attr('class', 'nv-legendWrap');
12742       gEnter.append('g').attr('class', 'nv-controlsWrap');
12743
12744       //------------------------------------------------------------
12745
12746
12747       //------------------------------------------------------------
12748       // Legend
12749
12750       if (showLegend) {
12751         legend
12752           .width( availableWidth - controlWidth );
12753
12754         g.select('.nv-legendWrap')
12755             .datum(data)
12756             .call(legend);
12757
12758         if ( margin.top != legend.height()) {
12759           margin.top = legend.height();
12760           availableHeight = (height || parseInt(container.style('height')) || 400)
12761                              - margin.top - margin.bottom;
12762         }
12763
12764         g.select('.nv-legendWrap')
12765             .attr('transform', 'translate(' + controlWidth + ',' + (-margin.top) +')');
12766       }
12767
12768       //------------------------------------------------------------
12769
12770
12771       //------------------------------------------------------------
12772       // Controls
12773
12774       if (showControls) {
12775         var controlsData = [
12776           { key: 'Stacked', disabled: stacked.offset() != 'zero' },
12777           { key: 'Stream', disabled: stacked.offset() != 'wiggle' },
12778           { key: 'Expanded', disabled: stacked.offset() != 'expand' }
12779         ];
12780
12781         controls
12782           .width( controlWidth )
12783           .color(['#444', '#444', '#444']);
12784
12785         g.select('.nv-controlsWrap')
12786             .datum(controlsData)
12787             .call(controls);
12788
12789
12790         if ( margin.top != Math.max(controls.height(), legend.height()) ) {
12791           margin.top = Math.max(controls.height(), legend.height());
12792           availableHeight = (height || parseInt(container.style('height')) || 400)
12793                              - margin.top - margin.bottom;
12794         }
12795
12796
12797         g.select('.nv-controlsWrap')
12798             .attr('transform', 'translate(0,' + (-margin.top) +')');
12799       }
12800
12801       //------------------------------------------------------------
12802
12803
12804       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12805
12806
12807       //------------------------------------------------------------
12808       // Main Chart Component(s)
12809
12810       stacked
12811         .width(availableWidth)
12812         .height(availableHeight)
12813
12814       var stackedWrap = g.select('.nv-stackedWrap')
12815           .datum(data);
12816       //d3.transition(stackedWrap).call(stacked);
12817       stackedWrap.call(stacked);
12818
12819       //------------------------------------------------------------
12820
12821
12822       //------------------------------------------------------------
12823       // Setup Axes
12824
12825       xAxis
12826         .scale(x)
12827         .ticks( availableWidth / 100 )
12828         .tickSize( -availableHeight, 0);
12829
12830       g.select('.nv-x.nv-axis')
12831           .attr('transform', 'translate(0,' + availableHeight + ')');
12832       //d3.transition(g.select('.nv-x.nv-axis'))
12833       g.select('.nv-x.nv-axis')
12834         .transition().duration(0)
12835           .call(xAxis);
12836
12837       yAxis
12838         .scale(y)
12839         .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
12840         .tickSize(-availableWidth, 0)
12841         .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
12842
12843       //d3.transition(g.select('.nv-y.nv-axis'))
12844       g.select('.nv-y.nv-axis')
12845         .transition().duration(0)
12846           .call(yAxis);
12847
12848       //------------------------------------------------------------
12849
12850
12851       //============================================================
12852       // Event Handling/Dispatching (in chart's scope)
12853       //------------------------------------------------------------
12854
12855       stacked.dispatch.on('areaClick.toggle', function(e) {
12856         if (data.filter(function(d) { return !d.disabled }).length === 1)
12857           data = data.map(function(d) {
12858             d.disabled = false;
12859             return d
12860           });
12861         else
12862           data = data.map(function(d,i) {
12863             d.disabled = (i != e.seriesIndex);
12864             return d
12865           });
12866
12867         state.disabled = data.map(function(d) { return !!d.disabled });
12868         dispatch.stateChange(state);
12869
12870         //selection.transition().call(chart);
12871         chart.update();
12872       });
12873
12874       legend.dispatch.on('legendClick', function(d,i) {
12875         d.disabled = !d.disabled;
12876
12877         if (!data.filter(function(d) { return !d.disabled }).length) {
12878           data.map(function(d) {
12879             d.disabled = false;
12880             return d;
12881           });
12882         }
12883
12884         state.disabled = data.map(function(d) { return !!d.disabled });
12885         dispatch.stateChange(state);
12886
12887         //selection.transition().call(chart);
12888         chart.update();
12889       });
12890
12891       legend.dispatch.on('legendDblclick', function(d) {
12892           //Double clicking should always enable current series, and disabled all others.
12893           data.forEach(function(d) {
12894              d.disabled = true;
12895           });
12896           d.disabled = false;  
12897
12898           state.disabled = data.map(function(d) { return !!d.disabled });
12899           dispatch.stateChange(state);
12900           chart.update();
12901       });
12902
12903       controls.dispatch.on('legendClick', function(d,i) {
12904         if (!d.disabled) return;
12905
12906         controlsData = controlsData.map(function(s) {
12907           s.disabled = true;
12908           return s;
12909         });
12910         d.disabled = false;
12911
12912         switch (d.key) {
12913           case 'Stacked':
12914             stacked.style('stack');
12915             break;
12916           case 'Stream':
12917             stacked.style('stream');
12918             break;
12919           case 'Expanded':
12920             stacked.style('expand');
12921             break;
12922         }
12923
12924         state.style = stacked.style();
12925         dispatch.stateChange(state);
12926
12927         //selection.transition().call(chart);
12928         chart.update();
12929       });
12930
12931       dispatch.on('tooltipShow', function(e) {
12932         if (tooltips) showTooltip(e, that.parentNode);
12933       });
12934
12935       // Update chart from a state object passed to event handler
12936       dispatch.on('changeState', function(e) {
12937
12938         if (typeof e.disabled !== 'undefined') {
12939           data.forEach(function(series,i) {
12940             series.disabled = e.disabled[i];
12941           });
12942
12943           state.disabled = e.disabled;
12944         }
12945
12946         if (typeof e.style !== 'undefined') {
12947           stacked.style(e.style);
12948         }
12949
12950         chart.update();
12951       });
12952
12953     });
12954
12955
12956     return chart;
12957   }
12958
12959
12960   //============================================================
12961   // Event Handling/Dispatching (out of chart's scope)
12962   //------------------------------------------------------------
12963
12964   stacked.dispatch.on('tooltipShow', function(e) {
12965     //disable tooltips when value ~= 0
12966     //// TODO: consider removing points from voronoi that have 0 value instead of this hack
12967     /*
12968     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
12969       setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
12970       return false;
12971     }
12972    */
12973
12974     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
12975     dispatch.tooltipShow(e);
12976   });
12977
12978   stacked.dispatch.on('tooltipHide', function(e) {
12979     dispatch.tooltipHide(e);
12980   });
12981
12982   dispatch.on('tooltipHide', function() {
12983     if (tooltips) nv.tooltip.cleanup();
12984   });
12985
12986   //============================================================
12987
12988
12989   //============================================================
12990   // Expose Public Variables
12991   //------------------------------------------------------------
12992
12993   // expose chart's sub-components
12994   chart.dispatch = dispatch;
12995   chart.stacked = stacked;
12996   chart.legend = legend;
12997   chart.controls = controls;
12998   chart.xAxis = xAxis;
12999   chart.yAxis = yAxis;
13000
13001   d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
13002
13003   chart.margin = function(_) {
13004     if (!arguments.length) return margin;
13005     margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
13006     margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
13007     margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13008     margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
13009     return chart;
13010   };
13011
13012   chart.width = function(_) {
13013     if (!arguments.length) return getWidth;
13014     width = _;
13015     return chart;
13016   };
13017
13018   chart.height = function(_) {
13019     if (!arguments.length) return getHeight;
13020     height = _;
13021     return chart;
13022   };
13023
13024   chart.color = function(_) {
13025     if (!arguments.length) return color;
13026     color = nv.utils.getColor(_);
13027     legend.color(color);
13028     stacked.color(color);
13029     return chart;
13030   };
13031
13032   chart.showControls = function(_) {
13033     if (!arguments.length) return showControls;
13034     showControls = _;
13035     return chart;
13036   };
13037
13038   chart.showLegend = function(_) {
13039     if (!arguments.length) return showLegend;
13040     showLegend = _;
13041     return chart;
13042   };
13043
13044   chart.tooltip = function(_) {
13045     if (!arguments.length) return tooltip;
13046     tooltip = _;
13047     return chart;
13048   };
13049
13050   chart.tooltips = function(_) {
13051     if (!arguments.length) return tooltips;
13052     tooltips = _;
13053     return chart;
13054   };
13055
13056   chart.tooltipContent = function(_) {
13057     if (!arguments.length) return tooltip;
13058     tooltip = _;
13059     return chart;
13060   };
13061   
13062   chart.yAxisTooltipFormat = function(_) {
13063     if (!arguments.length) return yAxisTooltipFormat;
13064     yAxisTooltipFormat = _;
13065     return chart;
13066   };
13067   chart.state = function(_) {
13068     if (!arguments.length) return state;
13069     state = _;
13070     return chart;
13071   };
13072
13073   chart.defaultState = function(_) {
13074     if (!arguments.length) return defaultState;
13075     defaultState = _;
13076     return chart;
13077   };
13078
13079   chart.noData = function(_) {
13080     if (!arguments.length) return noData;
13081     noData = _;
13082     return chart;
13083   };
13084
13085   yAxis.setTickFormat = yAxis.tickFormat;
13086
13087   yAxis.tickFormat = function(_) {
13088     if (!arguments.length) return yAxisTickFormat;
13089     yAxisTickFormat = _;
13090     return yAxis;
13091   };
13092
13093   //============================================================
13094
13095   return chart;
13096 }
13097 })();