3 var nv = window.nv || {};
6 nv.dev = true //set false when in production
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
17 nv.dispatch = d3.dispatch('render_start', 'render_end');
19 // *************************************************************************
20 // Development render timers - disabled if dev = false
23 nv.dispatch.on('render_start', function(e) {
24 nv.logs.startTime = +new Date();
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
34 // ********************************************
35 // Public Core NV functions
37 // Logs all arguments, and returns the last so you can test things in place
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);
45 return arguments[arguments.length - 1];
49 nv.render = function render(step) {
50 step = step || 1; // number of graphs to generate in each timeout loop
52 nv.render.active = true;
53 nv.dispatch.render_start();
55 setTimeout(function() {
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);
64 nv.render.queue.splice(0, i);
66 if (nv.render.queue.length) setTimeout(arguments.callee, 0);
67 else { nv.render.active = false; nv.dispatch.render_end(); }
71 nv.render.active = false;
74 nv.addGraph = function(obj) {
75 if (typeof arguments[0] === typeof(Function))
76 obj = {generate: arguments[0], callback: arguments[1]};
78 nv.render.queue.push(obj);
80 if (!nv.render.active) nv.render();
83 nv.identity = function(d) { return d; };
85 nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
87 function daysInMonth(month,year) {
88 return (new Date(year, month+1, 0)).getDate();
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);
97 var date = new Date(+time);
98 if ((number(date) % dt === 0)) times.push(date);
102 while (time < t1) { times.push(new Date(+time)); step(time); }
108 d3.time.monthEnd = function(date) {
109 return new Date(date.getFullYear(), date.getMonth(), 0);
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()));
116 return date.getMonth();
122 * A no-frills tooltip implementation.
128 var nvtooltip = window.nv.tooltip = {};
130 nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
132 var container = document.createElement('div');
133 container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
135 gravity = gravity || 's';
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];
144 container.innerHTML = content;
145 container.style.left = 0;
146 container.style.top = 0;
147 container.style.opacity = 0;
149 body.appendChild(container);
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,
159 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
160 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
162 var tooltipTop = function ( Elem ) {
165 if( !isNaN( Elem.offsetTop ) ) {
166 offsetTop += (Elem.offsetTop);
168 } while( Elem = Elem.offsetParent );
172 var tooltipLeft = function ( Elem ) {
173 var offsetLeft = left;
175 if( !isNaN( Elem.offsetLeft ) ) {
176 offsetLeft += (Elem.offsetLeft);
178 } while( Elem = Elem.offsetParent );
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;
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;
200 left = pos[0] - (width / 2) - 5;
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;
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;
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
229 nvtooltip.cleanup = function() {
231 // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
232 var tooltips = document.getElementsByClassName('nvtooltip');
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';
242 setTimeout(function() {
244 while (purging.length) {
245 var removeMe = purging.pop();
246 removeMe.parentNode.removeChild(removeMe);
254 nv.utils.windowSize = function() {
256 var size = {width: 640, height: 480};
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;
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;
272 // Most recent browsers use
273 if (window.innerWidth && window.innerHeight) {
274 size.width = window.innerWidth;
275 size.height = window.innerHeight;
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;
287 window.onresize = function(e) {
288 if (typeof oldresize == 'function') oldresize(e);
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
296 nv.utils.getColor = function(color) {
297 if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
299 if( Object.prototype.toString.call( color ) === '[object Array]' )
300 return function(d, i) { return d.color || color[i % color.length]; };
303 //can't really help it if someone passes rubbish as color
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] };
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
319 var defIndex = defaultColors.length; //current default color (going in reverse)
321 return function(series, index) {
322 var key = getKey(series);
324 if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
326 if (typeof dictionary[key] !== "undefined")
327 return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
329 return defaultColors[--defIndex]; // no match in dictionary, use default color
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);
342 d3.event.preventDefault();
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);
353 d3.select(window).on("popstate", function() {
354 if (d3.event.state) load(d3.event.state);
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
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;
367 return textLength * fontSize * 0.5;
371 nv.models.axis = function() {
373 //============================================================
374 // Public Variables with Default Settings
375 //------------------------------------------------------------
377 var axis = d3.svg.axis()
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
388 , rotateYLabel = true
389 , staggerLabels = false
398 .tickFormat(function(d) { return d })
401 //============================================================
404 //============================================================
406 //------------------------------------------------------------
410 //============================================================
413 function chart(selection) {
414 selection.each(function(data) {
415 var container = d3.select(this);
418 //------------------------------------------------------------
419 // Setup containers and skeleton of chart
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')
426 //------------------------------------------------------------
431 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
432 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
435 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
441 scale0 = scale0 || axis.scale();
443 var fmt = axis.tickFormat();
445 fmt = scale0.tickFormat();
448 var axisLabel = g.selectAll('text.nv-axislabel')
449 .data([axisLabelText || null]);
450 axisLabel.exit().remove();
451 switch (axis.orient()) {
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]));
456 .attr('text-anchor', 'middle')
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();
465 .attr('transform', function(d,i) {
466 return 'translate(' + scale(d) + ',0)'
470 .attr('y', -axis.tickPadding())
471 .attr('text-anchor', 'middle')
472 .text(function(d,i) {
474 return ('' + v).match('NaN') ? '' : v;
476 d3.transition(axisMaxMin)
477 .attr('transform', function(d,i) {
478 return 'translate(' + scale.range()[i] + ',0)'
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;
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;
497 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
498 .attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
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]));
503 .attr('text-anchor', 'middle')
504 .attr('y', xLabelMargin)
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();
514 .attr('transform', function(d,i) {
515 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
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) {
524 return ('' + v).match('NaN') ? '' : v;
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)'
535 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
539 axisLabel.enter().append('text').attr('class', 'nv-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());
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();
552 .attr('transform', function(d,i) {
553 return 'translate(0,' + scale(d) + ')'
558 .attr('x', axis.tickPadding())
559 .attr('text-anchor', 'start')
560 .text(function(d,i) {
562 return ('' + v).match('NaN') ? '' : v;
564 d3.transition(axisMaxMin)
565 .attr('transform', function(d,i) {
566 return 'translate(0,' + scale.range()[i] + ')'
569 .style('opacity', 1);
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;
581 axisLabel.enter().append('text').attr('class', 'nv-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());
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();
594 .attr('transform', function(d,i) {
595 return 'translate(0,' + scale0(d) + ')'
600 .attr('x', -axis.tickPadding())
601 .attr('text-anchor', 'end')
602 .text(function(d,i) {
604 return ('' + v).match('NaN') ? '' : v;
606 d3.transition(axisMaxMin)
607 .attr('transform', function(d,i) {
608 return 'translate(0,' + scale.range()[i] + ')'
611 .style('opacity', 1);
616 .text(function(d) { return d });
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);
628 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
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 });
639 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
640 var maxMinRange = [];
641 wrap.selectAll('g.nv-axisMaxMin')
642 .each(function(d,i) {
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)
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)
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();
661 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
667 //highlight zero line ... Maybe should not be an option and should just be in CSS?
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);
673 //store old scales for use in transitions on update
674 scale0 = scale.copy();
682 //============================================================
683 // Expose Public Variables
684 //------------------------------------------------------------
686 // expose chart's sub-components
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
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;
701 chart.width = function(_) {
702 if (!arguments.length) return width;
707 chart.ticks = function(_) {
708 if (!arguments.length) return ticks;
713 chart.height = function(_) {
714 if (!arguments.length) return height;
719 chart.axisLabel = function(_) {
720 if (!arguments.length) return axisLabelText;
725 chart.showMaxMin = function(_) {
726 if (!arguments.length) return showMaxMin;
731 chart.highlightZero = function(_) {
732 if (!arguments.length) return highlightZero;
737 chart.scale = function(_) {
738 if (!arguments.length) return scale;
741 isOrdinal = typeof scale.rangeBands === 'function';
742 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
746 chart.rotateYLabel = function(_) {
747 if(!arguments.length) return rotateYLabel;
752 chart.rotateLabels = function(_) {
753 if(!arguments.length) return rotateLabels;
758 chart.staggerLabels = function(_) {
759 if (!arguments.length) return staggerLabels;
765 //============================================================
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/
775 nv.models.bullet = function() {
777 //============================================================
778 // Public Variables with Default Settings
779 //------------------------------------------------------------
781 var margin = {top: 0, right: 0, bottom: 0, left: 0}
782 , orient = 'left' // TODO top & bottom
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.)
791 , color = nv.utils.getColor(['#1f77b4'])
792 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
795 //============================================================
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);
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);
809 //------------------------------------------------------------
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]);
817 // Retrieve the old x-scale, if this is an update.
818 var x0 = this.__chart__ || d3.scale.linear()
819 .domain([0, Infinity])
822 // Stash the new scale.
826 var rangeMin = d3.min(rangez), //rangez[2]
827 rangeMax = d3.max(rangez), //rangez[0]
828 rangeAvg = rangez[1];
830 //------------------------------------------------------------
833 //------------------------------------------------------------
834 // Setup containers and skeleton of chart
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');
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');
847 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
849 //------------------------------------------------------------
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) };
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)
865 .attr('x', rangeMin < 0 ?
872 g.select('rect.nv-rangeAvg')
873 .attr('height', availableHeight)
874 .attr('width', w1(rangeAvg))
875 .attr('x', xp1(rangeAvg))
878 .attr('width', rangeMax <= 0 ?
879 x1(rangeMax) - x1(rangeAvg)
880 : x1(rangeAvg) - x1(rangeMin))
881 .attr('x', rangeMax <= 0 ?
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)
894 .attr('width', rangeMax <= 0 ?
895 x1(rangeAvg) - x1(rangeMin)
896 : x1(rangeMax) - x1(rangeAvg))
897 .attr('x', rangeMax <= 0 ?
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({
914 pos: [x1(measurez[0]), availableHeight/2]
917 .on('mouseout', function() {
918 dispatch.elementMouseout({
924 var h3 = availableHeight / 6;
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({
933 pos: [x1(markerz[0]), availableHeight/2]
936 .on('mouseout', function() {
937 dispatch.elementMouseout({
943 g.selectAll('path.nv-markerTriangle').remove();
947 wrap.selectAll('.nv-range')
948 .on('mouseover', function(d,i) {
949 var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
951 dispatch.elementMouseover({
954 pos: [x1(d), availableHeight/2]
957 .on('mouseout', function(d,i) {
958 var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
960 dispatch.elementMouseout({
966 /* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
967 // Update the range rects.
968 var range = g.selectAll('rect.nv-range')
971 range.enter().append('rect')
972 .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
974 .attr('height', availableHeight)
975 .attr('x', reverse ? x0 : 0)
976 .on('mouseover', function(d,i) {
977 dispatch.elementMouseover({
979 label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
980 pos: [x1(d), availableHeight/2]
983 .on('mouseout', function(d,i) {
984 dispatch.elementMouseout({
986 label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
991 .attr('x', reverse ? x1 : 0)
993 .attr('height', availableHeight);
996 // Update the measure rects.
997 var measure = g.selectAll('rect.nv-measure')
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 ) })
1004 .attr('height', availableHeight / 3)
1005 .attr('x', reverse ? x0 : 0)
1006 .attr('y', availableHeight / 3)
1007 .on('mouseover', function(d) {
1008 dispatch.elementMouseover({
1010 label: 'Current', //TODO: make these labels a variable
1011 pos: [x1(d), availableHeight/2]
1014 .on('mouseout', function(d) {
1015 dispatch.elementMouseout({
1017 label: 'Current' //TODO: make these labels a variable
1021 d3.transition(measure)
1023 .attr('height', availableHeight / 3)
1024 .attr('x', reverse ? x1 : 0)
1025 .attr('y', availableHeight / 3);
1029 // Update the marker lines.
1030 var marker = g.selectAll('path.nv-markerTriangle')
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({
1042 pos: [x1(d), availableHeight/2]
1045 .on('mouseout', function(d,i) {
1046 dispatch.elementMouseout({
1052 d3.transition(marker)
1053 .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
1055 marker.exit().remove();
1060 // d3.timer.flush(); // Not needed?
1066 //============================================================
1067 // Expose Public Variables
1068 //------------------------------------------------------------
1070 chart.dispatch = dispatch;
1072 // left, right, top, bottom
1073 chart.orient = function(_) {
1074 if (!arguments.length) return orient;
1076 reverse = orient == 'right' || orient == 'bottom';
1080 // ranges (bad, satisfactory, good)
1081 chart.ranges = function(_) {
1082 if (!arguments.length) return ranges;
1087 // markers (previous, goal)
1088 chart.markers = function(_) {
1089 if (!arguments.length) return markers;
1094 // measures (actual, forecast)
1095 chart.measures = function(_) {
1096 if (!arguments.length) return measures;
1101 chart.forceX = function(_) {
1102 if (!arguments.length) return forceX;
1107 chart.width = function(_) {
1108 if (!arguments.length) return width;
1113 chart.height = function(_) {
1114 if (!arguments.length) return height;
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;
1128 chart.tickFormat = function(_) {
1129 if (!arguments.length) return tickFormat;
1134 chart.color = function(_) {
1135 if (!arguments.length) return color;
1136 color = nv.utils.getColor(_);
1140 //============================================================
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() {
1153 //============================================================
1154 // Public Variables with Default Settings
1155 //------------------------------------------------------------
1157 var bullet = nv.models.bullet()
1160 var orient = 'left' // TODO top & bottom
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 }
1170 , tooltip = function(key, x, y, e, graph) {
1171 return '<h3>' + x + '</h3>' +
1174 , noData = 'No Data Available.'
1175 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
1178 //============================================================
1181 //============================================================
1182 // Private Variables
1183 //------------------------------------------------------------
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);
1190 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
1193 //============================================================
1196 function chart(selection) {
1197 selection.each(function(d, i) {
1198 var container = d3.select(this);
1200 var availableWidth = (width || parseInt(container.style('width')) || 960)
1201 - margin.left - margin.right,
1202 availableHeight = height - margin.top - margin.bottom,
1206 chart.update = function() { chart(selection) };
1207 chart.container = this;
1209 //------------------------------------------------------------
1210 // Display No Data message if there's nothing to show.
1212 if (!d || !ranges.call(this, d, i)) {
1213 var noDataText = container.selectAll('.nv-noData').data([noData]);
1215 noDataText.enter().append('text')
1216 .attr('class', 'nvd3 nv-noData')
1217 .attr('dy', '-.7em')
1218 .style('text-anchor', 'middle');
1221 .attr('x', margin.left + availableWidth / 2)
1222 .attr('y', 18 + margin.top + availableHeight / 2)
1223 .text(function(d) { return d });
1227 container.selectAll('.nv-noData').remove();
1230 //------------------------------------------------------------
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);
1239 //------------------------------------------------------------
1240 // Setup containers and skeleton of chart
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');
1247 gEnter.append('g').attr('class', 'nv-bulletWrap');
1248 gEnter.append('g').attr('class', 'nv-titles');
1250 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1252 //------------------------------------------------------------
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]);
1260 // Retrieve the old x-scale, if this is an update.
1261 var x0 = this.__chart__ || d3.scale.linear()
1262 .domain([0, Infinity])
1265 // Stash the new scale.
1266 this.__chart__ = x1;
1269 // Derive width-scales from the x-scales.
1270 var w0 = bulletWidth(x0),
1271 w1 = bulletWidth(x1);
1273 function bulletWidth(x) {
1275 return function(d) {
1276 return Math.abs(x(d) - x(0));
1280 function bulletTranslate(x) {
1281 return function(d) {
1282 return 'translate(' + x(d) + ',0)';
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)) };
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; });
1298 title.append('text')
1299 .attr('class', 'nv-subtitle')
1301 .text(function(d) { return d.subtitle; });
1306 .width(availableWidth)
1307 .height(availableHeight)
1309 var bulletWrap = g.select('.nv-bulletWrap');
1311 d3.transition(bulletWrap).call(bullet);
1315 // Compute the tick format.
1316 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
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);
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);
1330 tickEnter.append('line')
1331 .attr('y1', availableHeight)
1332 .attr('y2', availableHeight * 7 / 6);
1334 tickEnter.append('text')
1335 .attr('text-anchor', 'middle')
1337 .attr('y', availableHeight * 7 / 6)
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);
1346 tickUpdate.select('line')
1347 .attr('y1', availableHeight)
1348 .attr('y2', availableHeight * 7 / 6);
1350 tickUpdate.select('text')
1351 .attr('y', availableHeight * 7 / 6);
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)
1360 //============================================================
1361 // Event Handling/Dispatching (in chart's scope)
1362 //------------------------------------------------------------
1364 dispatch.on('tooltipShow', function(e) {
1366 if (tooltips) showTooltip(e, that.parentNode);
1369 //============================================================
1379 //============================================================
1380 // Event Handling/Dispatching (out of chart's scope)
1381 //------------------------------------------------------------
1383 bullet.dispatch.on('elementMouseover.tooltip', function(e) {
1384 dispatch.tooltipShow(e);
1387 bullet.dispatch.on('elementMouseout.tooltip', function(e) {
1388 dispatch.tooltipHide(e);
1391 dispatch.on('tooltipHide', function() {
1392 if (tooltips) nv.tooltip.cleanup();
1395 //============================================================
1398 //============================================================
1399 // Expose Public Variables
1400 //------------------------------------------------------------
1402 chart.dispatch = dispatch;
1403 chart.bullet = bullet;
1405 d3.rebind(chart, bullet, 'color');
1407 // left, right, top, bottom
1408 chart.orient = function(x) {
1409 if (!arguments.length) return orient;
1411 reverse = orient == 'right' || orient == 'bottom';
1415 // ranges (bad, satisfactory, good)
1416 chart.ranges = function(x) {
1417 if (!arguments.length) return ranges;
1422 // markers (previous, goal)
1423 chart.markers = function(x) {
1424 if (!arguments.length) return markers;
1429 // measures (actual, forecast)
1430 chart.measures = function(x) {
1431 if (!arguments.length) return measures;
1436 chart.width = function(x) {
1437 if (!arguments.length) return width;
1442 chart.height = function(x) {
1443 if (!arguments.length) return height;
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;
1457 chart.tickFormat = function(x) {
1458 if (!arguments.length) return tickFormat;
1463 chart.tooltips = function(_) {
1464 if (!arguments.length) return tooltips;
1469 chart.tooltipContent = function(_) {
1470 if (!arguments.length) return tooltip;
1475 chart.noData = function(_) {
1476 if (!arguments.length) return noData;
1481 //============================================================
1489 nv.models.cumulativeLineChart = function() {
1491 //============================================================
1492 // Public Variables with Default Settings
1493 //------------------------------------------------------------
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()
1502 var margin = {top: 30, right: 30, bottom: 50, left: 60}
1503 , color = nv.utils.defaultColor()
1508 , showControls = true
1510 , tooltip = function(key, x, y, e, graph) {
1511 return '<h3>' + key + '</h3>' +
1512 '<p>' + y + ' at ' + x + '</p>'
1514 , x //can be accessed via chart.xScale()
1515 , y //can be accessed via chart.yScale()
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')
1532 //============================================================
1535 //============================================================
1536 // Private Variables
1537 //------------------------------------------------------------
1539 var dx = d3.scale.linear()
1540 , index = {i: 0, x: 0}
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);
1550 nv.tooltip.show([left, top], content, null, null, offsetElement);
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);
1560 function dragStart(d,i) {
1561 d3.select(chart.container)
1562 .style('cursor', 'ew-resize');
1565 function dragMove(d,i) {
1567 d.i = Math.round(dx.invert(d.x));
1569 d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
1573 function dragEnd(d,i) {
1574 d3.select(chart.container)
1575 .style('cursor', 'auto');
1580 //============================================================
1583 function chart(selection) {
1584 selection.each(function(data) {
1585 var container = d3.select(this).classed('nv-chart-' + id, true),
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;
1594 chart.update = function() { container.transition().call(chart) };
1595 chart.container = this;
1597 //set state.disabled
1598 state.disabled = data.map(function(d) { return !!d.disabled });
1600 if (!defaultState) {
1603 for (key in state) {
1604 if (state[key] instanceof Array)
1605 defaultState[key] = state[key].slice(0);
1607 defaultState[key] = state[key];
1611 var indexDrag = d3.behavior.drag()
1612 .on('dragstart', dragStart)
1613 .on('drag', dragMove)
1614 .on('dragend', dragEnd);
1617 function dragStart(d,i) {
1618 d3.select(chart.container)
1619 .style('cursor', 'ew-resize');
1622 function dragMove(d,i) {
1623 index.x = d3.event.x;
1624 index.i = Math.round(dx.invert(index.x));
1628 function dragEnd(d,i) {
1629 d3.select(chart.container)
1630 .style('cursor', 'auto');
1632 // update state and send stateChange with new index
1633 state.index = index.i;
1634 dispatch.stateChange(state);
1640 //------------------------------------------------------------
1641 // Display No Data message if there's nothing to show.
1643 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
1644 var noDataText = container.selectAll('.nv-noData').data([noData]);
1646 noDataText.enter().append('text')
1647 .attr('class', 'nvd3 nv-noData')
1648 .attr('dy', '-.7em')
1649 .style('text-anchor', 'middle');
1652 .attr('x', margin.left + availableWidth / 2)
1653 .attr('y', margin.top + availableHeight / 2)
1654 .text(function(d) { return d });
1658 container.selectAll('.nv-noData').remove();
1661 //------------------------------------------------------------
1664 //------------------------------------------------------------
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());
1677 //account for series being disabled when losing 95% or more
1678 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
1681 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
1682 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
1686 var completeDomain = [
1687 d3.min(seriesDomains, function(d) { return d[0] }),
1688 d3.max(seriesDomains, function(d) { return d[1] })
1691 lines.yDomain(completeDomain);
1693 lines.yDomain(null);
1697 dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
1698 .range([0, availableWidth])
1701 //------------------------------------------------------------
1704 var data = indexify(index.i, data);
1707 //------------------------------------------------------------
1708 // Setup containers and skeleton of chart
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');
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');
1722 //------------------------------------------------------------
1725 //------------------------------------------------------------
1729 legend.width(availableWidth);
1731 g.select('.nv-legendWrap')
1735 if ( margin.top != legend.height()) {
1736 margin.top = legend.height();
1737 availableHeight = (height || parseInt(container.style('height')) || 400)
1738 - margin.top - margin.bottom;
1741 g.select('.nv-legendWrap')
1742 .attr('transform', 'translate(0,' + (-margin.top) +')')
1745 //------------------------------------------------------------
1748 //------------------------------------------------------------
1752 var controlsData = [
1753 { key: 'Re-scale y-axis', disabled: !rescaleY }
1756 controls.width(140).color(['#444', '#444', '#444']);
1757 g.select('.nv-controlsWrap')
1758 .datum(controlsData)
1759 .attr('transform', 'translate(0,' + (-margin.top) +')')
1763 //------------------------------------------------------------
1766 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1769 // Show error if series goes below 100%
1770 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
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.');
1781 //------------------------------------------------------------
1782 // Main Chart Component(s)
1784 gEnter.select('.nv-background')
1787 g.select('.nv-background rect')
1788 .attr('width', availableWidth)
1789 .attr('height', availableHeight);
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; }));
1802 var linesWrap = g.select('.nv-linesWrap')
1803 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
1805 //d3.transition(linesWrap).call(lines);
1806 linesWrap.call(lines);
1808 /*Handle average lines [AN-612] ----------------------------*/
1810 //Store a series index number in the data array.
1811 data.forEach(function(d,i) {
1815 var avgLineData = data.filter(function(d) {
1816 return !d.disabled && !!average(d);
1819 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
1820 .data(avgLineData, function(d) { return d.key; });
1824 .style('stroke-width',2)
1825 .style('stroke-dasharray','10,10')
1826 .style('stroke',function (d,i) {
1827 return lines.color()(d,d.seriesIndex);
1830 .attr('x2',availableWidth)
1831 .attr('y1', function(d) { return y(average(d)); })
1832 .attr('y2', function(d) { return y(average(d)); });
1836 .attr('x2',availableWidth)
1837 .attr('y1', function(d) { return y(average(d)); })
1838 .attr('y2', function(d) { return y(average(d)); });
1840 avgLines.exit().remove();
1842 //Create index line -----------------------------------------
1844 var indexLine = linesWrap.selectAll('.nv-indexLine')
1846 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
1849 .attr('fill', 'red')
1850 .attr('fill-opacity', .5)
1854 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
1855 .attr('height', availableHeight)
1857 //------------------------------------------------------------
1860 //------------------------------------------------------------
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);
1869 g.select('.nv-x.nv-axis')
1870 .attr('transform', 'translate(0,' + y.range()[0] + ')');
1871 d3.transition(g.select('.nv-x.nv-axis'))
1877 .ticks( availableHeight / 36 )
1878 .tickSize( -availableWidth, 0);
1880 d3.transition(g.select('.nv-y.nv-axis'))
1883 //------------------------------------------------------------
1886 //============================================================
1887 // Event Handling/Dispatching (in chart's scope)
1888 //------------------------------------------------------------
1891 function updateZero() {
1895 container.call(chart);
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));
1903 // update state and send stateChange with new index
1904 state.index = index.i;
1905 dispatch.stateChange(state);
1910 lines.dispatch.on('elementClick', function(e) {
1911 index.i = e.pointIndex;
1912 index.x = dx(index.i);
1914 // update state and send stateChange with new index
1915 state.index = index.i;
1916 dispatch.stateChange(state);
1921 controls.dispatch.on('legendClick', function(d,i) {
1922 d.disabled = !d.disabled;
1923 rescaleY = !d.disabled;
1925 state.rescaleY = rescaleY;
1926 dispatch.stateChange(state);
1928 //selection.transition().call(chart);
1933 legend.dispatch.on('legendClick', function(d,i) {
1934 d.disabled = !d.disabled;
1936 if (!data.filter(function(d) { return !d.disabled }).length) {
1937 data.map(function(d) {
1939 wrap.selectAll('.nv-series').classed('disabled', false);
1944 state.disabled = data.map(function(d) { return !!d.disabled });
1945 dispatch.stateChange(state);
1947 //selection.transition().call(chart);
1951 legend.dispatch.on('legendDblclick', function(d) {
1952 //Double clicking should always enable current series, and disabled all others.
1953 data.forEach(function(d) {
1958 state.disabled = data.map(function(d) { return !!d.disabled });
1959 dispatch.stateChange(state);
1966 legend.dispatch.on('legendMouseover', function(d, i) {
1968 selection.transition().call(chart)
1971 legend.dispatch.on('legendMouseout', function(d, i) {
1973 selection.transition().call(chart)
1977 dispatch.on('tooltipShow', function(e) {
1978 if (tooltips) showTooltip(e, that.parentNode);
1982 // Update chart from a state object passed to event handler
1983 dispatch.on('changeState', function(e) {
1985 if (typeof e.disabled !== 'undefined') {
1986 data.forEach(function(series,i) {
1987 series.disabled = e.disabled[i];
1990 state.disabled = e.disabled;
1994 if (typeof e.index !== 'undefined') {
1996 index.x = dx(index.i);
1998 state.index = e.index;
2005 if (typeof e.rescaleY !== 'undefined') {
2006 rescaleY = e.rescaleY;
2012 //============================================================
2020 //============================================================
2021 // Event Handling/Dispatching (out of chart's scope)
2022 //------------------------------------------------------------
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);
2029 lines.dispatch.on('elementMouseout.tooltip', function(e) {
2030 dispatch.tooltipHide(e);
2033 dispatch.on('tooltipHide', function() {
2034 if (tooltips) nv.tooltip.cleanup();
2037 //============================================================
2040 //============================================================
2041 // Expose Public Variables
2042 //------------------------------------------------------------
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;
2051 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
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;
2062 chart.width = function(_) {
2063 if (!arguments.length) return width;
2068 chart.height = function(_) {
2069 if (!arguments.length) return height;
2074 chart.color = function(_) {
2075 if (!arguments.length) return color;
2076 color = nv.utils.getColor(_);
2077 legend.color(color);
2081 chart.rescaleY = function(_) {
2082 if (!arguments.length) return rescaleY;
2087 chart.showControls = function(_) {
2088 if (!arguments.length) return showControls;
2093 chart.showLegend = function(_) {
2094 if (!arguments.length) return showLegend;
2099 chart.tooltips = function(_) {
2100 if (!arguments.length) return tooltips;
2105 chart.tooltipContent = function(_) {
2106 if (!arguments.length) return tooltip;
2111 chart.state = function(_) {
2112 if (!arguments.length) return state;
2117 chart.defaultState = function(_) {
2118 if (!arguments.length) return defaultState;
2123 chart.noData = function(_) {
2124 if (!arguments.length) return noData;
2129 chart.average = function(_) {
2130 if(!arguments.length) return average;
2135 //============================================================
2138 //============================================================
2140 //------------------------------------------------------------
2142 /* Normalize the data according to an index point. */
2143 function indexify(idx, data) {
2144 return data.map(function(line, i) {
2148 var v = lines.y()(line.values[idx], idx);
2150 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
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;
2157 line.tempDisabled = false;
2159 line.values = line.values.map(function(point, pointIndex) {
2160 point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
2168 //============================================================
2173 //TODO: consider deprecating by adding necessary features to multiBar model
2174 nv.models.discreteBar = function() {
2176 //============================================================
2177 // Public Variables with Default Settings
2178 //------------------------------------------------------------
2180 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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')
2194 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2195 , rectClass = 'discreteBar'
2198 //============================================================
2201 //============================================================
2202 // Private Variables
2203 //------------------------------------------------------------
2207 //============================================================
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);
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) {
2227 //------------------------------------------------------------
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 }
2238 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2239 .rangeBands([0, availableWidth], .1);
2241 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
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]);
2248 //store old scales if they exist
2250 y0 = y0 || y.copy().range([y(0),y(0)]);
2252 //------------------------------------------------------------
2255 //------------------------------------------------------------
2256 // Setup containers and skeleton of chart
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');
2263 gEnter.append('g').attr('class', 'nv-groups');
2265 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2267 //------------------------------------------------------------
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)
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);
2289 var bars = groups.selectAll('g.nv-bar')
2290 .data(function(d) { return d.values });
2292 bars.exit().remove();
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) + ')'
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({
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
2307 seriesIndex: d.series,
2311 .on('mouseout', function(d,i) {
2312 d3.select(this).classed('hover', false);
2313 dispatch.elementMouseout({
2316 series: data[d.series],
2318 seriesIndex: d.series,
2322 .on('click', function(d,i) {
2323 dispatch.elementClick({
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
2329 seriesIndex: d.series,
2332 d3.event.stopPropagation();
2334 .on('dblclick', function(d,i) {
2335 dispatch.elementDblClick({
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
2341 seriesIndex: d.series,
2344 d3.event.stopPropagation();
2347 barsEnter.append('rect')
2349 .attr('width', x.rangeBand() * .9 / data.length )
2352 barsEnter.append('text')
2353 .attr('text-anchor', 'middle')
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)) });
2359 bars.selectAll('text').remove();
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) })
2367 .attr('class', rectClass)
2368 .attr('width', x.rangeBand() * .9 / data.length);
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 ?
2375 y(0) - y(getY(d,i)) < 1 ?
2376 y(0) - 1 : //make 1 px positive bars show up above y=0
2379 return 'translate(' + left + ', ' + top + ')'
2382 .attr('height', function(d,i) {
2383 return Math.max(Math.abs(y(getY(d,i)) - y(0)) || 1)
2387 //store old scales for use in transitions on update
2397 //============================================================
2398 // Expose Public Variables
2399 //------------------------------------------------------------
2401 chart.dispatch = dispatch;
2403 chart.x = function(_) {
2404 if (!arguments.length) return getX;
2409 chart.y = function(_) {
2410 if (!arguments.length) return getY;
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;
2424 chart.width = function(_) {
2425 if (!arguments.length) return width;
2430 chart.height = function(_) {
2431 if (!arguments.length) return height;
2436 chart.xScale = function(_) {
2437 if (!arguments.length) return x;
2442 chart.yScale = function(_) {
2443 if (!arguments.length) return y;
2448 chart.xDomain = function(_) {
2449 if (!arguments.length) return xDomain;
2454 chart.yDomain = function(_) {
2455 if (!arguments.length) return yDomain;
2460 chart.forceY = function(_) {
2461 if (!arguments.length) return forceY;
2466 chart.color = function(_) {
2467 if (!arguments.length) return color;
2468 color = nv.utils.getColor(_);
2472 chart.id = function(_) {
2473 if (!arguments.length) return id;
2478 chart.showValues = function(_) {
2479 if (!arguments.length) return showValues;
2484 chart.valueFormat= function(_) {
2485 if (!arguments.length) return valueFormat;
2490 chart.rectClass= function(_) {
2491 if (!arguments.length) return rectClass;
2495 //============================================================
2501 nv.models.discreteBarChart = function() {
2503 //============================================================
2504 // Public Variables with Default Settings
2505 //------------------------------------------------------------
2507 var discretebar = nv.models.discreteBar()
2508 , xAxis = nv.models.axis()
2509 , yAxis = nv.models.axis()
2512 var margin = {top: 15, right: 10, bottom: 50, left: 60}
2515 , color = nv.utils.getColor()
2516 , staggerLabels = false
2518 , tooltip = function(key, x, y, e, graph) {
2519 return '<h3>' + x + '</h3>' +
2524 , noData = "No Data Available."
2525 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
2530 .highlightZero(false)
2532 .tickFormat(function(d) { return d })
2536 .tickFormat(d3.format(',.1f'))
2539 //============================================================
2542 //============================================================
2543 // Private Variables
2544 //------------------------------------------------------------
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);
2553 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
2556 //============================================================
2559 function chart(selection) {
2560 selection.each(function(data) {
2561 var container = d3.select(this),
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;
2570 chart.update = function() { dispatch.beforeUpdate(); container.transition().call(chart); };
2571 chart.container = this;
2574 //------------------------------------------------------------
2575 // Display No Data message if there's nothing to show.
2577 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2578 var noDataText = container.selectAll('.nv-noData').data([noData]);
2580 noDataText.enter().append('text')
2581 .attr('class', 'nvd3 nv-noData')
2582 .attr('dy', '-.7em')
2583 .style('text-anchor', 'middle');
2586 .attr('x', margin.left + availableWidth / 2)
2587 .attr('y', margin.top + availableHeight / 2)
2588 .text(function(d) { return d });
2592 container.selectAll('.nv-noData').remove();
2595 //------------------------------------------------------------
2598 //------------------------------------------------------------
2601 x = discretebar.xScale();
2602 y = discretebar.yScale();
2604 //------------------------------------------------------------
2607 //------------------------------------------------------------
2608 // Setup containers and skeleton of chart
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');
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');
2619 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2621 //------------------------------------------------------------
2624 //------------------------------------------------------------
2625 // Main Chart Component(s)
2628 .width(availableWidth)
2629 .height(availableHeight);
2632 var barsWrap = g.select('.nv-barsWrap')
2633 .datum(data.filter(function(d) { return !d.disabled }))
2635 d3.transition(barsWrap).call(discretebar);
2637 //------------------------------------------------------------
2641 defsEnter.append('clipPath')
2642 .attr('id', 'nv-x-label-clip-' + discretebar.id())
2645 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
2646 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
2648 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
2651 //------------------------------------------------------------
2656 .ticks( availableWidth / 100 )
2657 .tickSize(-availableHeight, 0);
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)
2666 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
2668 if (staggerLabels) {
2671 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2676 .ticks( availableHeight / 36 )
2677 .tickSize( -availableWidth, 0);
2679 d3.transition(g.select('.nv-y.nv-axis'))
2682 //------------------------------------------------------------
2685 //============================================================
2686 // Event Handling/Dispatching (in chart's scope)
2687 //------------------------------------------------------------
2689 dispatch.on('tooltipShow', function(e) {
2690 if (tooltips) showTooltip(e, that.parentNode);
2693 //============================================================
2701 //============================================================
2702 // Event Handling/Dispatching (out of chart's scope)
2703 //------------------------------------------------------------
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);
2710 discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
2711 dispatch.tooltipHide(e);
2714 dispatch.on('tooltipHide', function() {
2715 if (tooltips) nv.tooltip.cleanup();
2718 //============================================================
2721 //============================================================
2722 // Expose Public Variables
2723 //------------------------------------------------------------
2725 // expose chart's sub-components
2726 chart.dispatch = dispatch;
2727 chart.discretebar = discretebar;
2728 chart.xAxis = xAxis;
2729 chart.yAxis = yAxis;
2731 d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
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;
2742 chart.width = function(_) {
2743 if (!arguments.length) return width;
2748 chart.height = function(_) {
2749 if (!arguments.length) return height;
2754 chart.color = function(_) {
2755 if (!arguments.length) return color;
2756 color = nv.utils.getColor(_);
2757 discretebar.color(color);
2761 chart.staggerLabels = function(_) {
2762 if (!arguments.length) return staggerLabels;
2767 chart.tooltips = function(_) {
2768 if (!arguments.length) return tooltips;
2773 chart.tooltipContent = function(_) {
2774 if (!arguments.length) return tooltip;
2779 chart.noData = function(_) {
2780 if (!arguments.length) return noData;
2785 //============================================================
2791 nv.models.distribution = function() {
2793 //============================================================
2794 // Public Variables with Default Settings
2795 //------------------------------------------------------------
2797 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2798 , width = 400 //technically width or height depending on x or y....
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()
2807 //============================================================
2810 //============================================================
2811 // Private Variables
2812 //------------------------------------------------------------
2816 //============================================================
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);
2826 //------------------------------------------------------------
2829 scale0 = scale0 || scale;
2831 //------------------------------------------------------------
2834 //------------------------------------------------------------
2835 // Setup containers and skeleton of chart
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');
2842 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
2844 //------------------------------------------------------------
2847 var distWrap = g.selectAll('g.nv-dist')
2848 .data(function(d) { return d }, function(d) { return d.key });
2850 distWrap.enter().append('g');
2852 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
2853 .style('stroke', function(d,i) { return color(d, i) });
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)
2866 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
2867 .attr(naxis + '1', 0)
2868 .attr(naxis + '2', size);
2870 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
2871 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
2874 scale0 = scale.copy();
2882 //============================================================
2883 // Expose Public Variables
2884 //------------------------------------------------------------
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;
2895 chart.width = function(_) {
2896 if (!arguments.length) return width;
2901 chart.axis = function(_) {
2902 if (!arguments.length) return axis;
2907 chart.size = function(_) {
2908 if (!arguments.length) return size;
2913 chart.getData = function(_) {
2914 if (!arguments.length) return getData;
2915 getData = d3.functor(_);
2919 chart.scale = function(_) {
2920 if (!arguments.length) return scale;
2925 chart.color = function(_) {
2926 if (!arguments.length) return color;
2927 color = nv.utils.getColor(_);
2931 //============================================================
2936 //TODO: consider deprecating and using multibar with single series for this
2937 nv.models.historicalBar = function() {
2939 //============================================================
2940 // Public Variables with Default Settings
2941 //------------------------------------------------------------
2943 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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 }
2955 , color = nv.utils.defaultColor()
2958 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2961 //============================================================
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);
2971 //------------------------------------------------------------
2974 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
2977 x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
2979 x.range([0, availableWidth]);
2981 y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
2982 .range([availableHeight, 0]);
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])
2988 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
2991 if (y.domain()[0] === y.domain()[1])
2993 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
2996 //------------------------------------------------------------
2999 //------------------------------------------------------------
3000 // Setup containers and skeleton of chart
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');
3008 gEnter.append('g').attr('class', 'nv-bars');
3010 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3012 //------------------------------------------------------------
3016 .on('click', function(d,i) {
3017 dispatch.chartClick({
3026 defsEnter.append('clipPath')
3027 .attr('id', 'nv-chart-clip-path-' + id)
3030 wrap.select('#nv-chart-clip-path-' + id + ' rect')
3031 .attr('width', availableWidth)
3032 .attr('height', availableHeight);
3034 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3038 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3039 .data(function(d) { return d });
3041 bars.exit().remove();
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 })
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({
3054 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3061 .on('mouseout', function(d,i) {
3062 d3.select(this).classed('hover', false);
3063 dispatch.elementMouseout({
3071 .on('click', function(d,i) {
3072 dispatch.elementClick({
3077 pos: [x(getX(d,i)), y(getY(d,i))],
3081 d3.event.stopPropagation();
3083 .on('dblclick', function(d,i) {
3084 dispatch.elementDblClick({
3089 pos: [x(getX(d,i)), y(getY(d,i))],
3093 d3.event.stopPropagation();
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 )
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 ?
3108 y(0) - y(getY(d,i)) < 1 ?
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
3121 //============================================================
3122 // Expose Public Variables
3123 //------------------------------------------------------------
3125 chart.dispatch = dispatch;
3127 chart.x = function(_) {
3128 if (!arguments.length) return getX;
3133 chart.y = function(_) {
3134 if (!arguments.length) return getY;
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;
3148 chart.width = function(_) {
3149 if (!arguments.length) return width;
3154 chart.height = function(_) {
3155 if (!arguments.length) return height;
3160 chart.xScale = function(_) {
3161 if (!arguments.length) return x;
3166 chart.yScale = function(_) {
3167 if (!arguments.length) return y;
3172 chart.xDomain = function(_) {
3173 if (!arguments.length) return xDomain;
3178 chart.yDomain = function(_) {
3179 if (!arguments.length) return yDomain;
3184 chart.forceX = function(_) {
3185 if (!arguments.length) return forceX;
3190 chart.forceY = function(_) {
3191 if (!arguments.length) return forceY;
3196 chart.padData = function(_) {
3197 if (!arguments.length) return padData;
3202 chart.clipEdge = function(_) {
3203 if (!arguments.length) return clipEdge;
3208 chart.color = function(_) {
3209 if (!arguments.length) return color;
3210 color = nv.utils.getColor(_);
3214 chart.id = function(_) {
3215 if (!arguments.length) return id;
3220 //============================================================
3226 nv.models.historicalBarChart = function() {
3228 //============================================================
3229 // Public Variables with Default Settings
3230 //------------------------------------------------------------
3232 var bars = nv.models.historicalBar()
3233 , xAxis = nv.models.axis()
3234 , yAxis = nv.models.axis()
3235 , legend = nv.models.legend()
3238 var margin = {top: 30, right: 90, bottom: 50, left: 90}
3239 , color = nv.utils.defaultColor()
3242 , showLegend = false
3245 , rightAlignYAxis = false
3247 , tooltip = function(key, x, y, e, graph) {
3248 return '<h3>' + key + '</h3>' +
3249 '<p>' + y + ' at ' + x + '</p>'
3254 , defaultState = null
3255 , noData = 'No Data Available.'
3256 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
3264 .orient( (rightAlignYAxis) ? 'right' : 'left')
3267 //============================================================
3270 //============================================================
3271 // Private Variables
3272 //------------------------------------------------------------
3274 var showTooltip = function(e, offsetElement) {
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;
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;
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);
3294 nv.tooltip.show([left, top], content, null, null, offsetElement);
3297 //============================================================
3300 function chart(selection) {
3301 selection.each(function(data) {
3302 var container = d3.select(this),
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;
3311 chart.update = function() { chart(selection) };
3312 chart.container = this;
3314 //set state.disabled
3315 state.disabled = data.map(function(d) { return !!d.disabled });
3317 if (!defaultState) {
3320 for (key in state) {
3321 if (state[key] instanceof Array)
3322 defaultState[key] = state[key].slice(0);
3324 defaultState[key] = state[key];
3328 //------------------------------------------------------------
3329 // Display noData message if there's nothing to show.
3331 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3332 var noDataText = container.selectAll('.nv-noData').data([noData]);
3334 noDataText.enter().append('text')
3335 .attr('class', 'nvd3 nv-noData')
3336 .attr('dy', '-.7em')
3337 .style('text-anchor', 'middle');
3340 .attr('x', margin.left + availableWidth / 2)
3341 .attr('y', margin.top + availableHeight / 2)
3342 .text(function(d) { return d });
3346 container.selectAll('.nv-noData').remove();
3349 //------------------------------------------------------------
3352 //------------------------------------------------------------
3358 //------------------------------------------------------------
3361 //------------------------------------------------------------
3362 // Setup containers and skeleton of chart
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');
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');
3373 //------------------------------------------------------------
3376 //------------------------------------------------------------
3380 legend.width(availableWidth);
3382 g.select('.nv-legendWrap')
3386 if ( margin.top != legend.height()) {
3387 margin.top = legend.height();
3388 availableHeight = (height || parseInt(container.style('height')) || 400)
3389 - margin.top - margin.bottom;
3392 wrap.select('.nv-legendWrap')
3393 .attr('transform', 'translate(0,' + (-margin.top) +')')
3396 //------------------------------------------------------------
3398 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3400 if (rightAlignYAxis) {
3401 g.select(".nv-y.nv-axis")
3402 .attr("transform", "translate(" + availableWidth + ",0)");
3405 //------------------------------------------------------------
3406 // Main Chart Component(s)
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 }));
3416 var barsWrap = g.select('.nv-barsWrap')
3417 .datum(data.filter(function(d) { return !d.disabled }))
3419 d3.transition(barsWrap).call(bars);
3421 //------------------------------------------------------------
3424 //------------------------------------------------------------
3430 .tickSize(-availableHeight, 0);
3432 g.select('.nv-x.nv-axis')
3433 .attr('transform', 'translate(0,' + y.range()[0] + ')');
3434 g.select('.nv-x.nv-axis')
3442 .ticks( availableHeight / 36 )
3443 .tickSize( -availableWidth, 0);
3445 g.select('.nv-y.nv-axis')
3446 .transition().duration(0)
3449 //------------------------------------------------------------
3452 //============================================================
3453 // Event Handling/Dispatching (in chart's scope)
3454 //------------------------------------------------------------
3456 legend.dispatch.on('legendClick', function(d,i) {
3457 d.disabled = !d.disabled;
3459 if (!data.filter(function(d) { return !d.disabled }).length) {
3460 data.map(function(d) {
3462 wrap.selectAll('.nv-series').classed('disabled', false);
3467 state.disabled = data.map(function(d) { return !!d.disabled });
3468 dispatch.stateChange(state);
3470 selection.transition().call(chart);
3473 legend.dispatch.on('legendDblclick', function(d) {
3474 //Double clicking should always enable current series, and disabled all others.
3475 data.forEach(function(d) {
3480 state.disabled = data.map(function(d) { return !!d.disabled });
3481 dispatch.stateChange(state);
3487 legend.dispatch.on('legendMouseover', function(d, i) {
3489 selection.transition().call(chart)
3492 legend.dispatch.on('legendMouseout', function(d, i) {
3494 selection.transition().call(chart)
3498 dispatch.on('tooltipShow', function(e) {
3499 if (tooltips) showTooltip(e, that.parentNode);
3503 dispatch.on('changeState', function(e) {
3505 if (typeof e.disabled !== 'undefined') {
3506 data.forEach(function(series,i) {
3507 series.disabled = e.disabled[i];
3510 state.disabled = e.disabled;
3513 selection.call(chart);
3516 //============================================================
3524 //============================================================
3525 // Event Handling/Dispatching (out of chart's scope)
3526 //------------------------------------------------------------
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);
3533 bars.dispatch.on('elementMouseout.tooltip', function(e) {
3534 dispatch.tooltipHide(e);
3537 dispatch.on('tooltipHide', function() {
3538 if (tooltips) nv.tooltip.cleanup();
3541 //============================================================
3544 //============================================================
3545 // Expose Public Variables
3546 //------------------------------------------------------------
3548 // expose chart's sub-components
3549 chart.dispatch = dispatch;
3551 chart.legend = legend;
3552 chart.xAxis = xAxis;
3553 chart.yAxis = yAxis;
3555 d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate');
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;
3566 chart.width = function(_) {
3567 if (!arguments.length) return width;
3572 chart.height = function(_) {
3573 if (!arguments.length) return height;
3578 chart.color = function(_) {
3579 if (!arguments.length) return color;
3580 color = nv.utils.getColor(_);
3581 legend.color(color);
3585 chart.showLegend = function(_) {
3586 if (!arguments.length) return showLegend;
3591 chart.showXAxis = function(_) {
3592 if (!arguments.length) return showXAxis;
3597 chart.showYAxis = function(_) {
3598 if (!arguments.length) return showYAxis;
3603 chart.rightAlignYAxis = function(_) {
3604 if(!arguments.length) return rightAlignYAxis;
3605 rightAlignYAxis = _;
3606 yAxis.orient( (_) ? 'right' : 'left');
3610 chart.tooltips = function(_) {
3611 if (!arguments.length) return tooltips;
3616 chart.tooltipContent = function(_) {
3617 if (!arguments.length) return tooltip;
3622 chart.state = function(_) {
3623 if (!arguments.length) return state;
3628 chart.defaultState = function(_) {
3629 if (!arguments.length) return defaultState;
3634 chart.noData = function(_) {
3635 if (!arguments.length) return noData;
3640 //============================================================
3645 nv.models.indentedTree = function() {
3647 //============================================================
3648 // Public Variables with Default Settings
3649 //------------------------------------------------------------
3651 var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
3654 , color = nv.utils.defaultColor()
3655 , id = Math.floor(Math.random() * 10000)
3657 , filterZero = false
3658 , noData = "No Data Available."
3660 , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
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')
3667 //============================================================
3671 function chart(selection) {
3672 selection.each(function(data) {
3674 container = d3.select(this);
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
3680 chart.update = function() { container.transition().duration(600).call(chart) };
3683 //------------------------------------------------------------
3684 // Display No Data message if there's nothing to show.
3685 if (!data[0]) data[0] = {key: noData};
3687 //------------------------------------------------------------
3690 var nodes = tree.nodes(data[0]);
3692 // nodes.map(function(d) {
3696 //------------------------------------------------------------
3697 // Setup containers and skeleton of chart
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);
3704 //------------------------------------------------------------
3708 var thead = tableEnter.append('thead');
3710 var theadRow1 = thead.append('tr');
3712 columns.forEach(function(column) {
3715 .attr('width', column.width ? column.width : '10%')
3716 .style('text-align', column.type == 'numeric' ? 'right' : 'left')
3718 .text(column.label);
3723 var tbody = table.selectAll('tbody')
3724 .data(function(d) { return d });
3725 tbody.enter().append('tbody');
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
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
3740 node.exit().remove();
3742 node.select('img.nv-treeicon')
3744 .classed('folded', folded);
3746 var nodeEnter = node.enter().append('tr');
3749 columns.forEach(function(column, index) {
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');
3757 nodeName.append('img')
3758 .classed('nv-treeicon', true)
3759 .classed('nv-folded', folded)
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);
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] || '-') });
3774 if (column.showCount) {
3775 nodeName.append('span')
3776 .attr('class', 'nv-childrenCount');
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
3788 nodeName.select('span').on('click', column.click);
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)
3801 .on('dblclick', function(d) {
3802 dispatch.elementDblclick({
3808 .on('mouseover', function(d) {
3809 dispatch.elementMouseover({
3815 .on('mouseout', function(d) {
3816 dispatch.elementMouseout({
3826 // Toggle children on click.
3827 function click(d, _, unshift) {
3828 d3.event.stopPropagation();
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);
3840 if(!hasChildren(d)) {
3842 //window.location.href = d.url;
3846 d._values = d.values;
3849 d.values = d._values;
3857 return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
3860 function folded(d) {
3861 return (d._values && d._values.length);
3864 function hasChildren(d) {
3865 var values = d.values || d._values;
3867 return (values && values.length);
3877 //============================================================
3878 // Expose Public Variables
3879 //------------------------------------------------------------
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;
3890 chart.width = function(_) {
3891 if (!arguments.length) return width;
3896 chart.height = function(_) {
3897 if (!arguments.length) return height;
3902 chart.color = function(_) {
3903 if (!arguments.length) return color;
3904 color = nv.utils.getColor(_);
3905 scatter.color(color);
3909 chart.id = function(_) {
3910 if (!arguments.length) return id;
3915 chart.header = function(_) {
3916 if (!arguments.length) return header;
3921 chart.noData = function(_) {
3922 if (!arguments.length) return noData;
3927 chart.filterZero = function(_) {
3928 if (!arguments.length) return filterZero;
3933 chart.columns = function(_) {
3934 if (!arguments.length) return columns;
3939 chart.tableClass = function(_) {
3940 if (!arguments.length) return tableClass;
3945 chart.iconOpen = function(_){
3946 if (!arguments.length) return iconOpen;
3951 chart.iconClose = function(_){
3952 if (!arguments.length) return iconClose;
3957 //============================================================
3961 };nv.models.legend = function() {
3963 //============================================================
3964 // Public Variables with Default Settings
3965 //------------------------------------------------------------
3967 var margin = {top: 5, right: 0, bottom: 5, left: 0}
3970 , getKey = function(d) { return d.key }
3971 , color = nv.utils.defaultColor()
3973 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout')
3976 //============================================================
3979 function chart(selection) {
3980 selection.each(function(data) {
3981 var availableWidth = width - margin.left - margin.right,
3982 container = d3.select(this);
3985 //------------------------------------------------------------
3986 // Setup containers and skeleton of chart
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');
3992 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3994 //------------------------------------------------------------
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
4003 .on('mouseout', function(d,i) {
4004 dispatch.legendMouseout(d,i);
4006 .on('click', function(d,i) {
4007 dispatch.legendClick(d,i);
4009 .on('dblclick', function(d,i) {
4010 dispatch.legendDblclick(d,i);
4012 seriesEnter.append('circle')
4013 .style('stroke-width', 2)
4015 seriesEnter.append('text')
4016 .attr('text-anchor', 'start')
4017 .attr('dy', '.32em')
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);
4027 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4029 // NEW ALIGNING CODE, TODO: clean up
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
4040 //nv.log('Series Widths: ', JSON.stringify(seriesWidths));
4042 var seriesPerRow = 0;
4043 var legendWidth = 0;
4044 var columnWidths = [];
4046 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4047 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4048 legendWidth += seriesWidths[seriesPerRow++];
4052 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4056 for (k = 0; k < seriesWidths.length; k++) {
4057 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4058 columnWidths[k % seriesPerRow] = seriesWidths[k];
4061 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4065 //console.log(columnWidths, legendWidth, seriesPerRow);
4067 var xPositions = [];
4068 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4069 xPositions[i] = curX;
4070 curX += columnWidths[i];
4074 .attr('transform', function(d, i) {
4075 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4078 //position legend as far right as possible within the total width
4079 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4081 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4090 .attr('transform', function(d, i) {
4091 var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4094 if (width < margin.left + margin.right + xpos + length) {
4100 if (newxpos > maxwidth) maxwidth = newxpos;
4102 return 'translate(' + xpos + ',' + ypos + ')';
4105 //position legend as far right as possible within the total width
4106 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4108 height = margin.top + margin.bottom + ypos + 15;
4118 //============================================================
4119 // Expose Public Variables
4120 //------------------------------------------------------------
4122 chart.dispatch = dispatch;
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;
4133 chart.width = function(_) {
4134 if (!arguments.length) return width;
4139 chart.height = function(_) {
4140 if (!arguments.length) return height;
4145 chart.key = function(_) {
4146 if (!arguments.length) return getKey;
4151 chart.color = function(_) {
4152 if (!arguments.length) return color;
4153 color = nv.utils.getColor(_);
4157 chart.align = function(_) {
4158 if (!arguments.length) return align;
4163 //============================================================
4169 nv.models.line = function() {
4171 //============================================================
4172 // Public Variables with Default Settings
4173 //------------------------------------------------------------
4175 var scatter = nv.models.scatter()
4178 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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
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
4197 //============================================================
4200 //============================================================
4201 // Private Variables
4202 //------------------------------------------------------------
4204 var x0, y0 //used to store previous scales
4207 //============================================================
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);
4216 //------------------------------------------------------------
4219 x = scatter.xScale();
4220 y = scatter.yScale();
4225 //------------------------------------------------------------
4228 //------------------------------------------------------------
4229 // Setup containers and skeleton of chart
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')
4237 gEnter.append('g').attr('class', 'nv-groups');
4238 gEnter.append('g').attr('class', 'nv-scatterWrap');
4240 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4242 //------------------------------------------------------------
4248 .width(availableWidth)
4249 .height(availableHeight)
4251 var scatterWrap = wrap.select('.nv-scatterWrap');
4252 //.datum(data); // Data automatically trickles down from the wrap
4254 d3.transition(scatterWrap).call(scatter);
4258 defsEnter.append('clipPath')
4259 .attr('id', 'nv-edge-clip-' + scatter.id())
4262 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
4263 .attr('width', availableWidth)
4264 .attr('height', availableHeight);
4266 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4268 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
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)
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);
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)
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])
4307 d3.transition(groups.exit().selectAll('path.nv-area'))
4308 .attr('d', function(d) {
4309 return d3.svg.area()
4310 .interpolate(interpolate)
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])
4318 d3.transition(areaPaths)
4319 .attr('d', function(d) {
4320 return d3.svg.area()
4321 .interpolate(interpolate)
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])
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')
4338 .interpolate(interpolate)
4340 .x(function(d,i) { return x0(getX(d,i)) })
4341 .y(function(d,i) { return y0(getY(d,i)) })
4343 d3.transition(groups.exit().selectAll('path.nv-line'))
4346 .interpolate(interpolate)
4348 .x(function(d,i) { return x(getX(d,i)) })
4349 .y(function(d,i) { return y(getY(d,i)) })
4351 d3.transition(linePaths)
4354 .interpolate(interpolate)
4356 .x(function(d,i) { return x(getX(d,i)) })
4357 .y(function(d,i) { return y(getY(d,i)) })
4362 //store old scales for use in transitions on update
4372 //============================================================
4373 // Expose Public Variables
4374 //------------------------------------------------------------
4376 chart.dispatch = scatter.dispatch;
4377 chart.scatter = scatter;
4379 d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData');
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;
4390 chart.width = function(_) {
4391 if (!arguments.length) return width;
4396 chart.height = function(_) {
4397 if (!arguments.length) return height;
4402 chart.x = function(_) {
4403 if (!arguments.length) return getX;
4409 chart.y = function(_) {
4410 if (!arguments.length) return getY;
4416 chart.clipEdge = function(_) {
4417 if (!arguments.length) return clipEdge;
4422 chart.color = function(_) {
4423 if (!arguments.length) return color;
4424 color = nv.utils.getColor(_);
4425 scatter.color(color);
4429 chart.interpolate = function(_) {
4430 if (!arguments.length) return interpolate;
4435 chart.defined = function(_) {
4436 if (!arguments.length) return defined;
4441 chart.isArea = function(_) {
4442 if (!arguments.length) return isArea;
4443 isArea = d3.functor(_);
4447 //============================================================
4453 nv.models.lineChart = function() {
4455 //============================================================
4456 // Public Variables with Default Settings
4457 //------------------------------------------------------------
4459 var lines = nv.models.line()
4460 , xAxis = nv.models.axis()
4461 , yAxis = nv.models.axis()
4462 , legend = nv.models.legend()
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()
4473 , rightAlignYAxis = false
4475 , tooltip = function(key, x, y, e, graph) {
4476 return '<h3>' + key + '</h3>' +
4477 '<p>' + y + ' at ' + x + '</p>'
4482 , defaultState = null
4483 , noData = 'No Data Available.'
4484 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4492 .orient((rightAlignYAxis) ? 'right' : 'left')
4495 //============================================================
4498 //============================================================
4499 // Private Variables
4500 //------------------------------------------------------------
4502 var showTooltip = function(e, offsetElement) {
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;
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;
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);
4522 nv.tooltip.show([left, top], content, null, null, offsetElement);
4525 //============================================================
4528 function chart(selection) {
4529 selection.each(function(data) {
4530 var container = d3.select(this),
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;
4539 chart.update = function() { container.transition().call(chart) };
4540 chart.container = this;
4542 //set state.disabled
4543 state.disabled = data.map(function(d) { return !!d.disabled });
4545 if (!defaultState) {
4548 for (key in state) {
4549 if (state[key] instanceof Array)
4550 defaultState[key] = state[key].slice(0);
4552 defaultState[key] = state[key];
4556 //------------------------------------------------------------
4557 // Display noData message if there's nothing to show.
4559 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4560 var noDataText = container.selectAll('.nv-noData').data([noData]);
4562 noDataText.enter().append('text')
4563 .attr('class', 'nvd3 nv-noData')
4564 .attr('dy', '-.7em')
4565 .style('text-anchor', 'middle');
4568 .attr('x', margin.left + availableWidth / 2)
4569 .attr('y', margin.top + availableHeight / 2)
4570 .text(function(d) { return d });
4574 container.selectAll('.nv-noData').remove();
4577 //------------------------------------------------------------
4580 //------------------------------------------------------------
4586 //------------------------------------------------------------
4589 //------------------------------------------------------------
4590 // Setup containers and skeleton of chart
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');
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');
4601 //------------------------------------------------------------
4604 //------------------------------------------------------------
4608 legend.width(availableWidth);
4610 g.select('.nv-legendWrap')
4614 if ( margin.top != legend.height()) {
4615 margin.top = legend.height();
4616 availableHeight = (height || parseInt(container.style('height')) || 400)
4617 - margin.top - margin.bottom;
4620 wrap.select('.nv-legendWrap')
4621 .attr('transform', 'translate(0,' + (-margin.top) +')')
4624 //------------------------------------------------------------
4626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4628 if (rightAlignYAxis) {
4629 g.select(".nv-y.nv-axis")
4630 .attr("transform", "translate(" + availableWidth + ",0)");
4633 //------------------------------------------------------------
4634 // Main Chart Component(s)
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 }));
4644 var linesWrap = g.select('.nv-linesWrap')
4645 .datum(data.filter(function(d) { return !d.disabled }))
4647 d3.transition(linesWrap).call(lines);
4649 //------------------------------------------------------------
4652 //------------------------------------------------------------
4658 .ticks( availableWidth / 100 )
4659 .tickSize(-availableHeight, 0);
4661 g.select('.nv-x.nv-axis')
4662 .attr('transform', 'translate(0,' + y.range()[0] + ')');
4663 d3.transition(g.select('.nv-x.nv-axis'))
4670 .ticks( availableHeight / 36 )
4671 .tickSize( -availableWidth, 0);
4673 d3.transition(g.select('.nv-y.nv-axis'))
4676 //------------------------------------------------------------
4679 //============================================================
4680 // Event Handling/Dispatching (in chart's scope)
4681 //------------------------------------------------------------
4683 legend.dispatch.on('legendClick', function(d,i) {
4684 d.disabled = !d.disabled;
4686 if (!data.filter(function(d) { return !d.disabled }).length) {
4687 data.map(function(d) {
4689 wrap.selectAll('.nv-series').classed('disabled', false);
4694 state.disabled = data.map(function(d) { return !!d.disabled });
4695 dispatch.stateChange(state);
4697 // container.transition().call(chart);
4701 legend.dispatch.on('legendDblclick', function(d) {
4702 //Double clicking should always enable current series, and disabled all others.
4703 data.forEach(function(d) {
4708 state.disabled = data.map(function(d) { return !!d.disabled });
4709 dispatch.stateChange(state);
4715 legend.dispatch.on('legendMouseover', function(d, i) {
4717 selection.transition().call(chart)
4720 legend.dispatch.on('legendMouseout', function(d, i) {
4722 selection.transition().call(chart)
4726 dispatch.on('tooltipShow', function(e) {
4727 if (tooltips) showTooltip(e, that.parentNode);
4731 dispatch.on('changeState', function(e) {
4733 if (typeof e.disabled !== 'undefined') {
4734 data.forEach(function(series,i) {
4735 series.disabled = e.disabled[i];
4738 state.disabled = e.disabled;
4744 //============================================================
4752 //============================================================
4753 // Event Handling/Dispatching (out of chart's scope)
4754 //------------------------------------------------------------
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);
4761 lines.dispatch.on('elementMouseout.tooltip', function(e) {
4762 dispatch.tooltipHide(e);
4765 dispatch.on('tooltipHide', function() {
4766 if (tooltips) nv.tooltip.cleanup();
4769 //============================================================
4772 //============================================================
4773 // Expose Public Variables
4774 //------------------------------------------------------------
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;
4783 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate');
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;
4794 chart.width = function(_) {
4795 if (!arguments.length) return width;
4800 chart.height = function(_) {
4801 if (!arguments.length) return height;
4806 chart.color = function(_) {
4807 if (!arguments.length) return color;
4808 color = nv.utils.getColor(_);
4809 legend.color(color);
4813 chart.showLegend = function(_) {
4814 if (!arguments.length) return showLegend;
4819 chart.showXAxis = function(_) {
4820 if (!arguments.length) return showXAxis;
4825 chart.showYAxis = function(_) {
4826 if (!arguments.length) return showYAxis;
4831 chart.rightAlignYAxis = function(_) {
4832 if(!arguments.length) return rightAlignYAxis;
4833 rightAlignYAxis = _;
4834 yAxis.orient( (_) ? 'right' : 'left');
4838 chart.tooltips = function(_) {
4839 if (!arguments.length) return tooltips;
4844 chart.tooltipContent = function(_) {
4845 if (!arguments.length) return tooltip;
4850 chart.state = function(_) {
4851 if (!arguments.length) return state;
4856 chart.defaultState = function(_) {
4857 if (!arguments.length) return defaultState;
4862 chart.noData = function(_) {
4863 if (!arguments.length) return noData;
4868 //============================================================
4874 nv.models.linePlusBarChart = function() {
4876 //============================================================
4877 // Public Variables with Default Settings
4878 //------------------------------------------------------------
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()
4888 var margin = {top: 30, right: 60, bottom: 50, left: 60}
4891 , getX = function(d) { return d.x }
4892 , getY = function(d) { return d.y }
4893 , color = nv.utils.defaultColor()
4896 , tooltip = function(key, x, y, e, graph) {
4897 return '<h3>' + key + '</h3>' +
4898 '<p>' + y + ' at ' + x + '</p>';
4904 , defaultState = null
4905 , noData = "No Data Available."
4906 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4919 .highlightZero(false)
4928 //============================================================
4931 //============================================================
4932 // Private Variables
4933 //------------------------------------------------------------
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);
4942 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
4946 //------------------------------------------------------------
4950 function chart(selection) {
4951 selection.each(function(data) {
4952 var container = d3.select(this),
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;
4960 chart.update = function() { container.transition().call(chart); };
4961 // chart.container = this;
4963 //set state.disabled
4964 state.disabled = data.map(function(d) { return !!d.disabled });
4966 if (!defaultState) {
4969 for (key in state) {
4970 if (state[key] instanceof Array)
4971 defaultState[key] = state[key].slice(0);
4973 defaultState[key] = state[key];
4977 //------------------------------------------------------------
4978 // Display No Data message if there's nothing to show.
4980 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4981 var noDataText = container.selectAll('.nv-noData').data([noData]);
4983 noDataText.enter().append('text')
4984 .attr('class', 'nvd3 nv-noData')
4985 .attr('dy', '-.7em')
4986 .style('text-anchor', 'middle');
4989 .attr('x', margin.left + availableWidth / 2)
4990 .attr('y', margin.top + availableHeight / 2)
4991 .text(function(d) { return d });
4995 container.selectAll('.nv-noData').remove();
4998 //------------------------------------------------------------
5001 //------------------------------------------------------------
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
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
5011 y2 = lines.yScale();
5013 //------------------------------------------------------------
5015 //------------------------------------------------------------
5016 // Setup containers and skeleton of chart
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');
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');
5029 //------------------------------------------------------------
5032 //------------------------------------------------------------
5036 legend.width( availableWidth / 2 );
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)');
5046 if ( margin.top != legend.height()) {
5047 margin.top = legend.height();
5048 availableHeight = (height || parseInt(container.style('height')) || 400)
5049 - margin.top - margin.bottom;
5052 g.select('.nv-legendWrap')
5053 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5056 //------------------------------------------------------------
5059 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5062 //------------------------------------------------------------
5063 // Main Chart Component(s)
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 }))
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 }))
5082 var barsWrap = g.select('.nv-barsWrap')
5083 .datum(dataBars.length ? dataBars : [{values:[]}])
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] }) }] );
5089 d3.transition(barsWrap).call(bars);
5090 d3.transition(linesWrap).call(lines);
5092 //------------------------------------------------------------
5095 //------------------------------------------------------------
5100 .ticks( availableWidth / 100 )
5101 .tickSize(-availableHeight, 0);
5103 g.select('.nv-x.nv-axis')
5104 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5105 d3.transition(g.select('.nv-x.nv-axis'))
5111 .ticks( availableHeight / 36 )
5112 .tickSize(-availableWidth, 0);
5114 d3.transition(g.select('.nv-y1.nv-axis'))
5115 .style('opacity', dataBars.length ? 1 : 0)
5121 .ticks( availableHeight / 36 )
5122 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
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)');
5129 d3.transition(g.select('.nv-y2.nv-axis'))
5132 //------------------------------------------------------------
5135 //============================================================
5136 // Event Handling/Dispatching (in chart's scope)
5137 //------------------------------------------------------------
5139 legend.dispatch.on('legendClick', function(d,i) {
5140 d.disabled = !d.disabled;
5142 if (!data.filter(function(d) { return !d.disabled }).length) {
5143 data.map(function(d) {
5145 wrap.selectAll('.nv-series').classed('disabled', false);
5150 state.disabled = data.map(function(d) { return !!d.disabled });
5151 dispatch.stateChange(state);
5156 legend.dispatch.on('legendDblclick', function(d) {
5157 //Double clicking should always enable current series, and disabled all others.
5158 data.forEach(function(d) {
5163 state.disabled = data.map(function(d) { return !!d.disabled });
5164 dispatch.stateChange(state);
5169 dispatch.on('tooltipShow', function(e) {
5170 if (tooltips) showTooltip(e, that.parentNode);
5174 // Update chart from a state object passed to event handler
5175 dispatch.on('changeState', function(e) {
5177 if (typeof e.disabled !== 'undefined') {
5178 data.forEach(function(series,i) {
5179 series.disabled = e.disabled[i];
5182 state.disabled = e.disabled;
5188 //============================================================
5197 //============================================================
5198 // Event Handling/Dispatching (out of chart's scope)
5199 //------------------------------------------------------------
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);
5206 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5207 dispatch.tooltipHide(e);
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);
5215 bars.dispatch.on('elementMouseout.tooltip', function(e) {
5216 dispatch.tooltipHide(e);
5219 dispatch.on('tooltipHide', function() {
5220 if (tooltips) nv.tooltip.cleanup();
5223 //============================================================
5226 //============================================================
5227 // Expose Public Variables
5228 //------------------------------------------------------------
5230 // expose chart's sub-components
5231 chart.dispatch = dispatch;
5232 chart.legend = legend;
5233 chart.lines = lines;
5235 chart.xAxis = xAxis;
5236 chart.y1Axis = y1Axis;
5237 chart.y2Axis = y2Axis;
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');
5243 chart.x = function(_) {
5244 if (!arguments.length) return getX;
5251 chart.y = function(_) {
5252 if (!arguments.length) return getY;
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;
5268 chart.width = function(_) {
5269 if (!arguments.length) return width;
5274 chart.height = function(_) {
5275 if (!arguments.length) return height;
5280 chart.color = function(_) {
5281 if (!arguments.length) return color;
5282 color = nv.utils.getColor(_);
5283 legend.color(color);
5287 chart.showLegend = function(_) {
5288 if (!arguments.length) return showLegend;
5293 chart.tooltips = function(_) {
5294 if (!arguments.length) return tooltips;
5299 chart.tooltipContent = function(_) {
5300 if (!arguments.length) return tooltip;
5305 chart.state = function(_) {
5306 if (!arguments.length) return state;
5311 chart.defaultState = function(_) {
5312 if (!arguments.length) return defaultState;
5317 chart.noData = function(_) {
5318 if (!arguments.length) return noData;
5323 //============================================================
5329 nv.models.lineWithFocusChart = function() {
5331 //============================================================
5332 // Public Variables with Default Settings
5333 //------------------------------------------------------------
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()
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()
5356 , brushExtent = null
5358 , tooltip = function(key, x, y, e, graph) {
5359 return '<h3>' + key + '</h3>' +
5360 '<p>' + y + ' at ' + x + '</p>'
5362 , noData = "No Data Available."
5363 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
5386 //============================================================
5389 //============================================================
5390 // Private Variables
5391 //------------------------------------------------------------
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);
5400 nv.tooltip.show([left, top], content, null, null, offsetElement);
5403 //============================================================
5406 function chart(selection) {
5407 selection.each(function(data) {
5408 var container = d3.select(this),
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;
5417 chart.update = function() { container.transition().call(chart) };
5418 chart.container = this;
5421 //------------------------------------------------------------
5422 // Display No Data message if there's nothing to show.
5424 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5425 var noDataText = container.selectAll('.nv-noData').data([noData]);
5427 noDataText.enter().append('text')
5428 .attr('class', 'nvd3 nv-noData')
5429 .attr('dy', '-.7em')
5430 .style('text-anchor', 'middle');
5433 .attr('x', margin.left + availableWidth / 2)
5434 .attr('y', margin.top + availableHeight1 / 2)
5435 .text(function(d) { return d });
5439 container.selectAll('.nv-noData').remove();
5442 //------------------------------------------------------------
5445 //------------------------------------------------------------
5450 x2 = lines2.xScale();
5451 y2 = lines2.yScale();
5453 //------------------------------------------------------------
5456 //------------------------------------------------------------
5457 // Setup containers and skeleton of chart
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');
5463 gEnter.append('g').attr('class', 'nv-legendWrap');
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');
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');
5477 //------------------------------------------------------------
5480 //------------------------------------------------------------
5484 legend.width(availableWidth);
5486 g.select('.nv-legendWrap')
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;
5496 g.select('.nv-legendWrap')
5497 .attr('transform', 'translate(0,' + (-margin.top) +')')
5500 //------------------------------------------------------------
5503 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5506 //------------------------------------------------------------
5507 // Main Chart Component(s)
5510 .width(availableWidth)
5511 .height(availableHeight1)
5514 .map(function(d,i) {
5515 return d.color || color(d, i);
5517 .filter(function(d,i) {
5518 return !data[i].disabled;
5523 .defined(lines.defined())
5524 .width(availableWidth)
5525 .height(availableHeight2)
5528 .map(function(d,i) {
5529 return d.color || color(d, i);
5531 .filter(function(d,i) {
5532 return !data[i].disabled;
5536 g.select('.nv-context')
5537 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
5539 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
5540 .datum(data.filter(function(d) { return !d.disabled }))
5542 d3.transition(contextLinesWrap).call(lines2);
5544 //------------------------------------------------------------
5548 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5549 .datum(data.filter(function(d) { return !d.disabled }))
5551 d3.transition(focusLinesWrap).call(lines);
5555 //------------------------------------------------------------
5556 // Setup Main (Focus) Axes
5560 .ticks( availableWidth / 100 )
5561 .tickSize(-availableHeight1, 0);
5565 .ticks( availableHeight1 / 36 )
5566 .tickSize( -availableWidth, 0);
5568 g.select('.nv-focus .nv-x.nv-axis')
5569 .attr('transform', 'translate(0,' + availableHeight1 + ')');
5571 //------------------------------------------------------------
5574 //------------------------------------------------------------
5579 .on('brush', onBrush);
5581 if (brushExtent) brush.extent(brushExtent);
5583 var brushBG = g.select('.nv-brushBackground').selectAll('g')
5584 .data([brushExtent || brush.extent()])
5586 var brushBGenter = brushBG.enter()
5589 brushBGenter.append('rect')
5590 .attr('class', 'left')
5593 .attr('height', availableHeight2);
5595 brushBGenter.append('rect')
5596 .attr('class', 'right')
5599 .attr('height', availableHeight2);
5601 gBrush = g.select('.nv-x.nv-brush')
5603 gBrush.selectAll('rect')
5605 .attr('height', availableHeight2);
5606 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
5610 //------------------------------------------------------------
5613 //------------------------------------------------------------
5614 // Setup Secondary (Context) Axes
5618 .ticks( availableWidth / 100 )
5619 .tickSize(-availableHeight2, 0);
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'))
5629 .ticks( availableHeight2 / 36 )
5630 .tickSize( -availableWidth, 0);
5632 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
5635 g.select('.nv-context .nv-x.nv-axis')
5636 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
5638 //------------------------------------------------------------
5641 //============================================================
5642 // Event Handling/Dispatching (in chart's scope)
5643 //------------------------------------------------------------
5645 legend.dispatch.on('legendClick', function(d,i) {
5646 d.disabled = !d.disabled;
5648 if (!data.filter(function(d) { return !d.disabled }).length) {
5649 data.map(function(d) {
5651 wrap.selectAll('.nv-series').classed('disabled', false);
5656 container.transition().call(chart);
5659 dispatch.on('tooltipShow', function(e) {
5660 if (tooltips) showTooltip(e, that.parentNode);
5663 //============================================================
5666 //============================================================
5668 //------------------------------------------------------------
5670 // Taken from crossfilter (http://square.github.com/crossfilter/)
5671 function resizePath(d) {
5672 var e = +(d == 'e'),
5674 y = availableHeight2 / 3;
5675 return 'M' + (.5 * x) + ',' + y
5676 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
5678 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
5680 + 'M' + (2.5 * x) + ',' + (y + 8)
5682 + 'M' + (4.5 * x) + ',' + (y + 8)
5683 + 'V' + (2 * y - 8);
5687 function updateBrushBG() {
5688 if (!brush.empty()) brush.extent(brushExtent);
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);
5697 d3.select(this).select('.right')
5698 .attr('x', x2(d[1]))
5699 .attr('width', rightWidth < 0 ? 0 : rightWidth);
5704 function onBrush() {
5705 brushExtent = brush.empty() ? null : brush.extent();
5706 extent = brush.empty() ? x2.domain() : brush.extent();
5709 dispatch.brush({extent: extent, brush: brush});
5714 // Update Main (Focus)
5715 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5718 .filter(function(d) { return !d.disabled })
5719 .map(function(d,i) {
5722 values: d.values.filter(function(d,i) {
5723 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
5728 d3.transition(focusLinesWrap).call(lines);
5731 // Update Main (Focus) Axes
5732 d3.transition(g.select('.nv-focus .nv-x.nv-axis'))
5734 d3.transition(g.select('.nv-focus .nv-y.nv-axis'))
5738 //============================================================
5747 //============================================================
5748 // Event Handling/Dispatching (out of chart's scope)
5749 //------------------------------------------------------------
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);
5756 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5757 dispatch.tooltipHide(e);
5760 dispatch.on('tooltipHide', function() {
5761 if (tooltips) nv.tooltip.cleanup();
5764 //============================================================
5767 //============================================================
5768 // Expose Public Variables
5769 //------------------------------------------------------------
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;
5781 d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
5783 chart.x = function(_) {
5784 if (!arguments.length) return lines.x;
5790 chart.y = function(_) {
5791 if (!arguments.length) return lines.y;
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;
5806 chart.margin2 = function(_) {
5807 if (!arguments.length) return margin2;
5812 chart.width = function(_) {
5813 if (!arguments.length) return width;
5818 chart.height = function(_) {
5819 if (!arguments.length) return height;
5824 chart.height2 = function(_) {
5825 if (!arguments.length) return height2;
5830 chart.color = function(_) {
5831 if (!arguments.length) return color;
5832 color =nv.utils.getColor(_);
5833 legend.color(color);
5837 chart.showLegend = function(_) {
5838 if (!arguments.length) return showLegend;
5843 chart.tooltips = function(_) {
5844 if (!arguments.length) return tooltips;
5849 chart.tooltipContent = function(_) {
5850 if (!arguments.length) return tooltip;
5855 chart.interpolate = function(_) {
5856 if (!arguments.length) return lines.interpolate();
5857 lines.interpolate(_);
5858 lines2.interpolate(_);
5862 chart.noData = function(_) {
5863 if (!arguments.length) return noData;
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(_);
5876 chart.yTickFormat = function(_) {
5877 if (!arguments.length) return yAxis.tickFormat();
5878 yAxis.tickFormat(_);
5879 y2Axis.tickFormat(_);
5883 //============================================================
5889 nv.models.linePlusBarWithFocusChart = function() {
5891 //============================================================
5892 // Public Variables with Default Settings
5893 //------------------------------------------------------------
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()
5909 var margin = {top: 30, right: 30, bottom: 30, left: 60}
5910 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5914 , getX = function(d) { return d.x }
5915 , getY = function(d) { return d.y }
5916 , color = nv.utils.defaultColor()
5919 , brushExtent = null
5921 , tooltip = function(key, x, y, e, graph) {
5922 return '<h3>' + key + '</h3>' +
5923 '<p>' + y + ' at ' + x + '</p>';
5931 , noData = "No Data Available."
5932 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
5962 //============================================================
5965 //============================================================
5966 // Private Variables
5967 //------------------------------------------------------------
5969 var showTooltip = function(e, offsetElement) {
5971 e.pointIndex += Math.ceil(extent[0]);
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);
5979 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5982 //------------------------------------------------------------
5986 function chart(selection) {
5987 selection.each(function(data) {
5988 var container = d3.select(this),
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;
5997 chart.update = function() { container.transition().call(chart); };
5998 chart.container = this;
6001 //------------------------------------------------------------
6002 // Display No Data message if there's nothing to show.
6004 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6005 var noDataText = container.selectAll('.nv-noData').data([noData]);
6007 noDataText.enter().append('text')
6008 .attr('class', 'nvd3 nv-noData')
6009 .attr('dy', '-.7em')
6010 .style('text-anchor', 'middle');
6013 .attr('x', margin.left + availableWidth / 2)
6014 .attr('y', margin.top + availableHeight1 / 2)
6015 .text(function(d) { return d });
6019 container.selectAll('.nv-noData').remove();
6022 //------------------------------------------------------------
6025 //------------------------------------------------------------
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
6032 x2 = x2Axis.scale();
6034 y2 = lines.yScale();
6035 y3 = bars2.yScale();
6036 y4 = lines2.yScale();
6039 .filter(function(d) { return !d.disabled && d.bar })
6041 return d.values.map(function(d,i) {
6042 return { x: getX(d,i), y: getY(d,i) }
6047 .filter(function(d) { return !d.disabled && !d.bar })
6049 return d.values.map(function(d,i) {
6050 return { x: getX(d,i), y: getY(d,i) }
6054 x .range([0, availableWidth]);
6056 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6057 .range([0, availableWidth]);
6060 //------------------------------------------------------------
6063 //------------------------------------------------------------
6064 // Setup containers and skeleton of chart
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');
6070 gEnter.append('g').attr('class', 'nv-legendWrap');
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');
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');
6089 //------------------------------------------------------------
6092 //------------------------------------------------------------
6096 legend.width( availableWidth / 2 );
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)');
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;
6112 g.select('.nv-legendWrap')
6113 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6116 //------------------------------------------------------------
6119 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6122 //------------------------------------------------------------
6123 // Context Components
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 }));
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 }));
6139 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6140 .datum(dataBars.length ? dataBars : [{values:[]}]);
6142 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6143 .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
6145 g.select('.nv-context')
6146 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6148 d3.transition(bars2Wrap).call(bars2);
6149 d3.transition(lines2Wrap).call(lines2);
6151 //------------------------------------------------------------
6155 //------------------------------------------------------------
6160 .on('brush', onBrush);
6162 if (brushExtent) brush.extent(brushExtent);
6164 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6165 .data([brushExtent || brush.extent()])
6167 var brushBGenter = brushBG.enter()
6170 brushBGenter.append('rect')
6171 .attr('class', 'left')
6174 .attr('height', availableHeight2);
6176 brushBGenter.append('rect')
6177 .attr('class', 'right')
6180 .attr('height', availableHeight2);
6182 var gBrush = g.select('.nv-x.nv-brush')
6184 gBrush.selectAll('rect')
6186 .attr('height', availableHeight2);
6187 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6189 //------------------------------------------------------------
6191 //------------------------------------------------------------
6192 // Setup Secondary (Context) Axes
6195 .ticks( availableWidth / 100 )
6196 .tickSize(-availableHeight2, 0);
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'))
6206 .ticks( availableHeight2 / 36 )
6207 .tickSize( -availableWidth, 0);
6209 g.select('.nv-context .nv-y1.nv-axis')
6210 .style('opacity', dataBars.length ? 1 : 0)
6211 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6213 d3.transition(g.select('.nv-context .nv-y1.nv-axis'))
6219 .ticks( availableHeight2 / 36 )
6220 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6222 g.select('.nv-context .nv-y2.nv-axis')
6223 .style('opacity', dataLines.length ? 1 : 0)
6224 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6226 d3.transition(g.select('.nv-context .nv-y2.nv-axis'))
6229 //------------------------------------------------------------
6231 //============================================================
6232 // Event Handling/Dispatching (in chart's scope)
6233 //------------------------------------------------------------
6235 legend.dispatch.on('legendClick', function(d,i) {
6236 d.disabled = !d.disabled;
6238 if (!data.filter(function(d) { return !d.disabled }).length) {
6239 data.map(function(d) {
6241 wrap.selectAll('.nv-series').classed('disabled', false);
6249 dispatch.on('tooltipShow', function(e) {
6250 if (tooltips) showTooltip(e, that.parentNode);
6253 //============================================================
6256 //============================================================
6258 //------------------------------------------------------------
6260 // Taken from crossfilter (http://square.github.com/crossfilter/)
6261 function resizePath(d) {
6262 var e = +(d == 'e'),
6264 y = availableHeight2 / 3;
6265 return 'M' + (.5 * x) + ',' + y
6266 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6268 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6270 + 'M' + (2.5 * x) + ',' + (y + 8)
6272 + 'M' + (4.5 * x) + ',' + (y + 8)
6273 + 'V' + (2 * y - 8);
6277 function updateBrushBG() {
6278 if (!brush.empty()) brush.extent(brushExtent);
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);
6287 d3.select(this).select('.right')
6288 .attr('x', x2(d[1]))
6289 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6294 function onBrush() {
6295 brushExtent = brush.empty() ? null : brush.extent();
6296 extent = brush.empty() ? x2.domain() : brush.extent();
6299 dispatch.brush({extent: extent, brush: brush});
6304 //------------------------------------------------------------
6305 // Prepare Main (Focus) Bars and Lines
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 }));
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 }));
6322 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
6323 .datum(!dataBars.length ? [{values:[]}] :
6325 .map(function(d,i) {
6328 values: d.values.filter(function(d,i) {
6329 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
6335 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6336 .datum(dataLines[0].disabled ? [{values:[]}] :
6338 .map(function(d,i) {
6341 values: d.values.filter(function(d,i) {
6342 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6348 //------------------------------------------------------------
6351 //------------------------------------------------------------
6352 // Update Main (Focus) X Axis
6354 if (dataBars.length) {
6362 .ticks( availableWidth / 100 )
6363 .tickSize(-availableHeight1, 0);
6365 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
6367 d3.transition(g.select('.nv-x.nv-axis'))
6369 //------------------------------------------------------------
6372 //------------------------------------------------------------
6373 // Update Main (Focus) Bars and Lines
6375 d3.transition(focusBarsWrap).call(bars);
6376 d3.transition(focusLinesWrap).call(lines);
6378 //------------------------------------------------------------
6381 //------------------------------------------------------------
6382 // Setup and Update Main (Focus) Y Axes
6384 g.select('.nv-focus .nv-x.nv-axis')
6385 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6390 .ticks( availableHeight1 / 36 )
6391 .tickSize(-availableWidth, 0);
6393 g.select('.nv-focus .nv-y1.nv-axis')
6394 .style('opacity', dataBars.length ? 1 : 0);
6399 .ticks( availableHeight1 / 36 )
6400 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6402 g.select('.nv-focus .nv-y2.nv-axis')
6403 .style('opacity', dataLines.length ? 1 : 0)
6404 .attr('transform', 'translate(' + x.range()[1] + ',0)');
6406 d3.transition(g.select('.nv-focus .nv-y1.nv-axis'))
6408 d3.transition(g.select('.nv-focus .nv-y2.nv-axis'))
6412 //============================================================
6422 //============================================================
6423 // Event Handling/Dispatching (out of chart's scope)
6424 //------------------------------------------------------------
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);
6431 lines.dispatch.on('elementMouseout.tooltip', function(e) {
6432 dispatch.tooltipHide(e);
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);
6440 bars.dispatch.on('elementMouseout.tooltip', function(e) {
6441 dispatch.tooltipHide(e);
6444 dispatch.on('tooltipHide', function() {
6445 if (tooltips) nv.tooltip.cleanup();
6448 //============================================================
6451 //============================================================
6452 // Expose Public Variables
6453 //------------------------------------------------------------
6455 // expose chart's sub-components
6456 chart.dispatch = dispatch;
6457 chart.legend = legend;
6458 chart.lines = lines;
6459 chart.lines2 = lines2;
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;
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');
6473 chart.x = function(_) {
6474 if (!arguments.length) return getX;
6481 chart.y = function(_) {
6482 if (!arguments.length) return getY;
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;
6498 chart.width = function(_) {
6499 if (!arguments.length) return width;
6504 chart.height = function(_) {
6505 if (!arguments.length) return height;
6510 chart.color = function(_) {
6511 if (!arguments.length) return color;
6512 color = nv.utils.getColor(_);
6513 legend.color(color);
6517 chart.showLegend = function(_) {
6518 if (!arguments.length) return showLegend;
6523 chart.tooltips = function(_) {
6524 if (!arguments.length) return tooltips;
6529 chart.tooltipContent = function(_) {
6530 if (!arguments.length) return tooltip;
6535 chart.noData = function(_) {
6536 if (!arguments.length) return noData;
6541 chart.brushExtent = function(_) {
6542 if (!arguments.length) return brushExtent;
6548 //============================================================
6554 nv.models.multiBar = function() {
6556 //============================================================
6557 // Public Variables with Default Settings
6558 //------------------------------------------------------------
6560 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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
6571 , color = nv.utils.defaultColor()
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
6579 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
6582 //============================================================
6585 //============================================================
6586 // Private Variables
6587 //------------------------------------------------------------
6589 var x0, y0 //used to store previous scales
6592 //============================================================
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);
6601 if(hideable && data.length) hideable = [{
6602 values: data[0].values.map(function(d) {
6612 data = d3.layout.stack()
6614 .values(function(d){ return d.values })
6616 (!data.length && hideable ? hideable : data);
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) {
6629 //------------------------------------------------------------
6630 // HACK for negative value stacking
6632 data[0].values.map(function(d,i) {
6633 var posBase = 0, negBase = 0;
6634 data.map(function(d) {
6636 f.size = Math.abs(f.y);
6639 negBase = negBase - f.size;
6642 f.y1 = f.size + posBase;
6643 posBase = posBase + f.size;
6648 //------------------------------------------------------------
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 }
6659 x .domain(d3.merge(seriesData).map(function(d) { return d.x }))
6660 .rangeBands([0, availableWidth], .1);
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]);
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])
6670 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
6673 if (y.domain()[0] === y.domain()[1])
6675 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
6682 //------------------------------------------------------------
6685 //------------------------------------------------------------
6686 // Setup containers and skeleton of chart
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')
6694 gEnter.append('g').attr('class', 'nv-groups');
6696 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6698 //------------------------------------------------------------
6702 defsEnter.append('clipPath')
6703 .attr('id', 'nv-edge-clip-' + id)
6705 wrap.select('#nv-edge-clip-' + id + ' rect')
6706 .attr('width', availableWidth)
6707 .attr('height', availableHeight);
6709 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
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);
6721 .selectAll('rect.nv-bar')
6723 .delay(function(d,i) { return i * delay/ data[0].values.length })
6724 .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
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);
6737 var bars = groups.selectAll('rect.nv-bar')
6738 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
6740 bars.exit().remove();
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 )
6748 .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
6750 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
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({
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
6762 seriesIndex: d.series,
6766 .on('mouseout', function(d,i) {
6767 d3.select(this).classed('hover', false);
6768 dispatch.elementMouseout({
6771 series: data[d.series],
6773 seriesIndex: d.series,
6777 .on('click', function(d,i) {
6778 dispatch.elementClick({
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
6784 seriesIndex: d.series,
6787 d3.event.stopPropagation();
6789 .on('dblclick', function(d,i) {
6790 dispatch.elementDblClick({
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
6796 seriesIndex: d.series,
6799 d3.event.stopPropagation();
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)'; })
6806 if (!disabled) disabled = data.map(function() { return true });
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(); });
6820 .delay(function(d,i) { return i * delay / data[0].values.length })
6821 .attr('y', function(d,i) {
6823 return y((stacked ? d.y1 : 0));
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);
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 )
6834 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
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
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 ?
6848 y(0) - y(getY(d,i)) < 1 ?
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;
6859 //store old scales for use in transitions on update
6869 //============================================================
6870 // Expose Public Variables
6871 //------------------------------------------------------------
6873 chart.dispatch = dispatch;
6875 chart.x = function(_) {
6876 if (!arguments.length) return getX;
6881 chart.y = function(_) {
6882 if (!arguments.length) return getY;
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;
6896 chart.width = function(_) {
6897 if (!arguments.length) return width;
6902 chart.height = function(_) {
6903 if (!arguments.length) return height;
6908 chart.xScale = function(_) {
6909 if (!arguments.length) return x;
6914 chart.yScale = function(_) {
6915 if (!arguments.length) return y;
6920 chart.xDomain = function(_) {
6921 if (!arguments.length) return xDomain;
6926 chart.yDomain = function(_) {
6927 if (!arguments.length) return yDomain;
6932 chart.forceY = function(_) {
6933 if (!arguments.length) return forceY;
6938 chart.stacked = function(_) {
6939 if (!arguments.length) return stacked;
6944 chart.clipEdge = function(_) {
6945 if (!arguments.length) return clipEdge;
6950 chart.color = function(_) {
6951 if (!arguments.length) return color;
6952 color = nv.utils.getColor(_);
6956 chart.barColor = function(_) {
6957 if (!arguments.length) return barColor;
6958 barColor = nv.utils.getColor(_);
6962 chart.disabled = function(_) {
6963 if (!arguments.length) return disabled;
6968 chart.id = function(_) {
6969 if (!arguments.length) return id;
6974 chart.hideable = function(_) {
6975 if (!arguments.length) return hideable;
6980 chart.delay = function(_) {
6981 if (!arguments.length) return delay;
6986 chart.drawTime = function(_) {
6987 if (!arguments.length) return drawTime;
6992 //============================================================
6998 nv.models.multiBarChart = function() {
7000 //============================================================
7001 // Public Variables with Default Settings
7002 //------------------------------------------------------------
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()
7011 var margin = {top: 30, right: 20, bottom: 50, left: 60}
7014 , color = nv.utils.defaultColor()
7015 , showControls = true
7018 , reduceXTicks = true // if false a tick will show for every data point
7019 , staggerLabels = false
7022 , tooltip = function(key, x, y, e, graph, logScale) {
7023 //alert(y+ " " + logScale);
7025 var fmt = d3.format(',.2f');
7026 return '<h3>' + key + '</h3>' +
7027 '<p>' + fmt(Math.pow(10,y)) + ' on ' + x + '</p>'
7029 return '<h3>' + key + '</h3>' +
7030 '<p>' + y + ' on ' + x + '</p>'
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 }
7048 .highlightZero(true)
7050 .tickFormat(function(d) { return d })
7054 .tickFormat(d3.format(',.1f'))
7057 //============================================================
7060 //============================================================
7061 // Private Variables
7062 //------------------------------------------------------------
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);
7071 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
7074 //============================================================
7077 function chart(selection) {
7078 selection.each(function(data) {
7079 var container = d3.select(this),
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;
7087 chart.update = function() { container.transition().call(chart) };
7088 chart.container = this;
7090 //set state.disabled
7091 state.disabled = data.map(function(d) { return !!d.disabled });
7093 if (!defaultState) {
7096 for (key in state) {
7097 if (state[key] instanceof Array)
7098 defaultState[key] = state[key].slice(0);
7100 defaultState[key] = state[key];
7103 //------------------------------------------------------------
7104 // Display noData message if there's nothing to show.
7106 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7107 var noDataText = container.selectAll('.nv-noData').data([noData]);
7109 noDataText.enter().append('text')
7110 .attr('class', 'nvd3 nv-noData')
7111 .attr('dy', '-.7em')
7112 .style('text-anchor', 'middle');
7115 .attr('x', margin.left + availableWidth / 2)
7116 .attr('y', margin.top + availableHeight / 2)
7117 .text(function(d) { return d });
7121 container.selectAll('.nv-noData').remove();
7124 //------------------------------------------------------------
7127 //------------------------------------------------------------
7130 x = multibar.xScale();
7131 y = multibar.yScale();
7133 //------------------------------------------------------------
7136 //------------------------------------------------------------
7137 // Setup containers and skeleton of chart
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');
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');
7149 //------------------------------------------------------------
7152 //------------------------------------------------------------
7156 legend.width(availableWidth - controlWidth());
7158 if (multibar.barColor())
7159 data.forEach(function(series,i) {
7160 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
7163 g.select('.nv-legendWrap')
7167 if ( margin.top != legend.height()) {
7168 margin.top = legend.height();
7169 availableHeight = (height || parseInt(container.style('height')) || 400)
7170 - margin.top - margin.bottom;
7173 g.select('.nv-legendWrap')
7174 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
7177 //------------------------------------------------------------
7180 //------------------------------------------------------------
7184 var controlsData = [
7185 { key: 'Grouped', disabled: multibar.stacked() },
7186 { key: 'Stacked', disabled: !multibar.stacked() }
7189 controls.width(controlWidth()).color(['#444', '#444', '#444']);
7190 g.select('.nv-controlsWrap')
7191 .datum(controlsData)
7192 .attr('transform', 'translate(0,' + (-margin.top) +')')
7196 //------------------------------------------------------------
7199 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7202 //------------------------------------------------------------
7203 // Main Chart Component(s)
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 }))
7214 var barsWrap = g.select('.nv-barsWrap')
7215 .datum(data.filter(function(d) { return !d.disabled }))
7217 d3.transition(barsWrap).call(multibar);
7219 //------------------------------------------------------------
7222 //------------------------------------------------------------
7227 .ticks( availableWidth / 100 )
7228 .tickSize(-availableHeight, 0);
7230 g.select('.nv-x.nv-axis')
7231 .attr('transform', 'translate(0,' + y.range()[0] + ')');
7232 d3.transition(g.select('.nv-x.nv-axis'))
7235 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
7238 .selectAll('line, text')
7239 .style('opacity', 1)
7241 if (staggerLabels) {
7242 var getTranslate = function(x,y) {
7243 return "translate(" + x + "," + y + ")";
7246 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
7250 .attr('transform', function(d,i,j) {
7251 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
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);
7264 .filter(function(d,i) {
7265 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
7267 .selectAll('text, line')
7268 .style('opacity', 0);
7273 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
7274 .attr('text-anchor', rotateLabels > 0 ? 'start' : 'end');
7276 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
7277 .style('opacity', 1);
7281 .ticks( availableHeight / 36 )
7282 .tickSize( -availableWidth, 0);
7284 d3.transition(g.select('.nv-y.nv-axis'))
7287 //------------------------------------------------------------
7291 //============================================================
7292 // Event Handling/Dispatching (in chart's scope)
7293 //------------------------------------------------------------
7295 legend.dispatch.on('legendClick', function(d,i) {
7296 d.disabled = !d.disabled;
7298 if (!data.filter(function(d) { return !d.disabled }).length) {
7299 data.map(function(d) {
7301 wrap.selectAll('.nv-series').classed('disabled', false);
7306 state.disabled = data.map(function(d) { return !!d.disabled });
7307 dispatch.stateChange(state);
7312 legend.dispatch.on('legendDblclick', function(d) {
7313 //Double clicking should always enable current series, and disabled all others.
7314 data.forEach(function(d) {
7319 state.disabled = data.map(function(d) { return !!d.disabled });
7320 dispatch.stateChange(state);
7325 controls.dispatch.on('legendClick', function(d,i) {
7326 if (!d.disabled) return;
7327 controlsData = controlsData.map(function(s) {
7335 multibar.stacked(false);
7338 multibar.stacked(true);
7342 state.stacked = multibar.stacked();
7343 dispatch.stateChange(state);
7348 dispatch.on('tooltipShow', function(e) {
7349 if (tooltips) showTooltip(e, that.parentNode)
7352 // Update chart from a state object passed to event handler
7353 dispatch.on('changeState', function(e) {
7355 if (typeof e.disabled !== 'undefined') {
7356 data.forEach(function(series,i) {
7357 series.disabled = e.disabled[i];
7360 state.disabled = e.disabled;
7363 if (typeof e.stacked !== 'undefined') {
7364 multibar.stacked(e.stacked);
7365 state.stacked = e.stacked;
7371 //============================================================
7380 //============================================================
7381 // Event Handling/Dispatching (out of chart's scope)
7382 //------------------------------------------------------------
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);
7389 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
7390 dispatch.tooltipHide(e);
7392 dispatch.on('tooltipHide', function() {
7393 if (tooltips) nv.tooltip.cleanup();
7396 //============================================================
7399 //============================================================
7400 // Expose Public Variables
7401 //------------------------------------------------------------
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;
7410 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay', 'barColor');
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;
7421 chart.width = function(_) {
7422 if (!arguments.length) return width;
7427 chart.height = function(_) {
7428 if (!arguments.length) return height;
7433 chart.color = function(_) {
7434 if (!arguments.length) return color;
7435 color = nv.utils.getColor(_);
7436 legend.color(color);
7440 chart.showControls = function(_) {
7441 if (!arguments.length) return showControls;
7446 chart.showLegend = function(_) {
7447 if (!arguments.length) return showLegend;
7452 chart.logScale = function(_) {
7453 if (!arguments.length) return logScale;
7458 chart.reduceXTicks= function(_) {
7459 if (!arguments.length) return reduceXTicks;
7464 chart.rotateLabels = function(_) {
7465 if (!arguments.length) return rotateLabels;
7470 chart.staggerLabels = function(_) {
7471 if (!arguments.length) return staggerLabels;
7476 chart.tooltip = function(_) {
7477 if (!arguments.length) return tooltip;
7482 chart.tooltips = function(_) {
7483 if (!arguments.length) return tooltips;
7488 chart.tooltipContent = function(_) {
7489 if (!arguments.length) return tooltip;
7494 chart.state = function(_) {
7495 if (!arguments.length) return state;
7500 chart.defaultState = function(_) {
7501 if (!arguments.length) return defaultState;
7506 chart.noData = function(_) {
7507 if (!arguments.length) return noData;
7512 //============================================================
7518 nv.models.multiBarHorizontal = function() {
7520 //============================================================
7521 // Public Variables with Default Settings
7522 //------------------------------------------------------------
7524 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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
7537 , showValues = false
7539 , valueFormat = d3.format(',.2f')
7543 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7546 //============================================================
7549 //============================================================
7550 // Private Variables
7551 //------------------------------------------------------------
7553 var x0, y0 //used to store previous scales
7556 //============================================================
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);
7567 data = d3.layout.stack()
7569 .values(function(d){ return d.values })
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) {
7585 //------------------------------------------------------------
7586 // HACK for negative value stacking
7588 data[0].values.map(function(d,i) {
7589 var posBase = 0, negBase = 0;
7590 data.map(function(d) {
7592 f.size = Math.abs(f.y);
7594 f.y1 = negBase - f.size;
7595 negBase = negBase - f.size;
7599 posBase = posBase + f.size;
7606 //------------------------------------------------------------
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 }
7617 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7618 .rangeBands([0, availableHeight], .1);
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)))
7623 if (showValues && !stacked)
7624 y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
7626 y.range([0, availableWidth]);
7629 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
7631 //------------------------------------------------------------
7634 //------------------------------------------------------------
7635 // Setup containers and skeleton of chart
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');
7643 gEnter.append('g').attr('class', 'nv-groups');
7645 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7647 //------------------------------------------------------------
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)
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);
7670 var bars = groups.selectAll('g.nv-bar')
7671 .data(function(d) { return d.values });
7673 bars.exit().remove();
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))) + ')'
7681 barsEnter.append('rect')
7683 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
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({
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) ],
7694 seriesIndex: d.series,
7698 .on('mouseout', function(d,i) {
7699 d3.select(this).classed('hover', false);
7700 dispatch.elementMouseout({
7703 series: data[d.series],
7705 seriesIndex: d.series,
7709 .on('click', function(d,i) {
7710 dispatch.elementClick({
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
7716 seriesIndex: d.series,
7719 d3.event.stopPropagation();
7721 .on('dblclick', function(d,i) {
7722 dispatch.elementDblClick({
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
7728 seriesIndex: d.series,
7731 d3.event.stopPropagation();
7735 barsEnter.append('text');
7737 if (showValues && !stacked) {
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)) })
7744 //.delay(function(d,i) { return i * delay / data[0].values.length })
7746 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
7748 //bars.selectAll('text').remove();
7749 bars.selectAll('text').text('');
7753 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7756 if (!disabled) disabled = data.map(function() { return true });
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(); });
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)) + ')'
7775 .attr('width', function(d,i) {
7776 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
7778 .attr('height', x.rangeBand() );
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))
7787 (d.series * x.rangeBand() / data.length
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)
7799 //store old scales for use in transitions on update
7809 //============================================================
7810 // Expose Public Variables
7811 //------------------------------------------------------------
7813 chart.dispatch = dispatch;
7815 chart.x = function(_) {
7816 if (!arguments.length) return getX;
7821 chart.y = function(_) {
7822 if (!arguments.length) return getY;
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;
7836 chart.width = function(_) {
7837 if (!arguments.length) return width;
7842 chart.height = function(_) {
7843 if (!arguments.length) return height;
7848 chart.xScale = function(_) {
7849 if (!arguments.length) return x;
7854 chart.yScale = function(_) {
7855 if (!arguments.length) return y;
7860 chart.xDomain = function(_) {
7861 if (!arguments.length) return xDomain;
7866 chart.yDomain = function(_) {
7867 if (!arguments.length) return yDomain;
7872 chart.forceY = function(_) {
7873 if (!arguments.length) return forceY;
7878 chart.stacked = function(_) {
7879 if (!arguments.length) return stacked;
7884 chart.color = function(_) {
7885 if (!arguments.length) return color;
7886 color = nv.utils.getColor(_);
7890 chart.barColor = function(_) {
7891 if (!arguments.length) return barColor;
7892 barColor = nv.utils.getColor(_);
7896 chart.disabled = function(_) {
7897 if (!arguments.length) return disabled;
7902 chart.id = function(_) {
7903 if (!arguments.length) return id;
7908 chart.delay = function(_) {
7909 if (!arguments.length) return delay;
7914 chart.showValues = function(_) {
7915 if (!arguments.length) return showValues;
7920 chart.valueFormat= function(_) {
7921 if (!arguments.length) return valueFormat;
7926 chart.valuePadding = function(_) {
7927 if (!arguments.length) return valuePadding;
7932 //============================================================
7938 nv.models.multiBarHorizontalChart = function() {
7940 //============================================================
7941 // Public Variables with Default Settings
7942 //------------------------------------------------------------
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)
7951 var margin = {top: 30, right: 20, bottom: 50, left: 60}
7954 , color = nv.utils.defaultColor()
7955 , showControls = true
7960 , tooltip = function(key, x, y, e, graph, logScale) {
7961 //alert(y+ " " + logScale + " " + Math.pow(10,y) + " " + Math.pow(10,y).toFixed(0));
7963 //var fmt = d3.format(',.2f');
7964 return '<h3>' + key + ' - ' + x + '</h3>' +
7965 '<p>' + yAxis.tickFormat()(Math.pow(10,y)) + '</p>'
7967 return '<h3>' + key + ' - ' + x + '</h3>' +
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 }
7987 .highlightZero(false)
7989 .tickFormat(function(d) { return d })
7993 .tickFormat(d3.format(',.1f'))
7996 //============================================================
7999 //============================================================
8000 // Private Variables
8001 //------------------------------------------------------------
8003 var showTooltip = function(e, offsetElement) {
8004 //var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8005 //alert(offsetElement.offsetLeft + " " + e.pos[0]);
8007 if(e.pos[0] >=200) leftPos = 200;
8008 else leftPos = e.pos[0];
8010 y = multibar.y()(e.point, e.pointIndex);
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));
8019 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
8022 //============================================================
8025 function chart(selection) {
8026 selection.each(function(data) {
8027 var container = d3.select(this),
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;
8035 chart.update = function() { container.transition().call(chart) };
8036 chart.container = this;
8038 //set state.disabled
8039 state.disabled = data.map(function(d) { return !!d.disabled });
8041 if (!defaultState) {
8044 for (key in state) {
8045 if (state[key] instanceof Array)
8046 defaultState[key] = state[key].slice(0);
8048 defaultState[key] = state[key];
8052 //------------------------------------------------------------
8053 // Display No Data message if there's nothing to show.
8055 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8056 var noDataText = container.selectAll('.nv-noData').data([noData]);
8058 noDataText.enter().append('text')
8059 .attr('class', 'nvd3 nv-noData')
8060 .attr('dy', '-.7em')
8061 .style('text-anchor', 'middle');
8064 .attr('x', margin.left + availableWidth / 2)
8065 .attr('y', margin.top + availableHeight / 2)
8066 .text(function(d) { return d });
8070 container.selectAll('.nv-noData').remove();
8073 //------------------------------------------------------------
8076 //------------------------------------------------------------
8079 x = multibar.xScale();
8080 y = multibar.yScale();
8082 //------------------------------------------------------------
8085 //------------------------------------------------------------
8086 // Setup containers and skeleton of chart
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');
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');
8098 //------------------------------------------------------------
8101 //------------------------------------------------------------
8105 legend.width(availableWidth - controlWidth());
8107 if (multibar.barColor())
8108 data.forEach(function(series,i) {
8109 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8112 g.select('.nv-legendWrap')
8116 if ( margin.top != legend.height()) {
8117 margin.top = legend.height();
8118 availableHeight = (height || parseInt(container.style('height')) || 400)
8119 - margin.top - margin.bottom;
8122 g.select('.nv-legendWrap')
8123 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8126 //------------------------------------------------------------
8129 //------------------------------------------------------------
8133 var controlsData = [
8134 { key: 'Grouped', disabled: multibar.stacked() },
8135 { key: 'Stacked', disabled: !multibar.stacked() }
8138 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8139 g.select('.nv-controlsWrap')
8140 .datum(controlsData)
8141 .attr('transform', 'translate(0,' + (-margin.top) +')')
8145 //------------------------------------------------------------
8148 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8151 //------------------------------------------------------------
8152 // Main Chart Component(s)
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 }))
8163 var barsWrap = g.select('.nv-barsWrap')
8164 .datum(data.filter(function(d) { return !d.disabled }))
8166 d3.transition(barsWrap).call(multibar);
8168 //------------------------------------------------------------
8171 //------------------------------------------------------------
8176 .ticks( availableHeight / 24 )
8177 .tickSize(-availableWidth, 0);
8179 d3.transition(g.select('.nv-x.nv-axis'))
8182 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
8185 .selectAll('line, text')
8186 .style('opacity', 1)
8191 .ticks( availableWidth / 100 )
8192 .tickSize( -availableHeight, 0);
8194 g.select('.nv-y.nv-axis')
8195 .attr('transform', 'translate(0,' + availableHeight + ')');
8196 d3.transition(g.select('.nv-y.nv-axis'))
8199 //------------------------------------------------------------
8203 //============================================================
8204 // Event Handling/Dispatching (in chart's scope)
8205 //------------------------------------------------------------
8207 legend.dispatch.on('legendClick', function(d,i) {
8208 d.disabled = !d.disabled;
8210 if (!data.filter(function(d) { return !d.disabled }).length) {
8211 data.map(function(d) {
8213 wrap.selectAll('.nv-series').classed('disabled', false);
8218 state.disabled = data.map(function(d) { return !!d.disabled });
8219 dispatch.stateChange(state);
8224 legend.dispatch.on('legendDblclick', function(d) {
8225 //Double clicking should always enable current series, and disabled all others.
8226 data.forEach(function(d) {
8231 state.disabled = data.map(function(d) { return !!d.disabled });
8232 dispatch.stateChange(state);
8236 controls.dispatch.on('legendClick', function(d,i) {
8237 if (!d.disabled) return;
8238 controlsData = controlsData.map(function(s) {
8246 multibar.stacked(false);
8249 multibar.stacked(true);
8253 state.stacked = multibar.stacked();
8254 dispatch.stateChange(state);
8259 dispatch.on('tooltipShow', function(e) {
8260 if (tooltips) showTooltip(e, that.parentNode);
8263 // Update chart from a state object passed to event handler
8264 dispatch.on('changeState', function(e) {
8266 if (typeof e.disabled !== 'undefined') {
8267 data.forEach(function(series,i) {
8268 series.disabled = e.disabled[i];
8271 state.disabled = e.disabled;
8274 if (typeof e.stacked !== 'undefined') {
8275 multibar.stacked(e.stacked);
8276 state.stacked = e.stacked;
8279 selection.call(chart);
8281 //============================================================
8290 //============================================================
8291 // Event Handling/Dispatching (out of chart's scope)
8292 //------------------------------------------------------------
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);
8299 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8300 dispatch.tooltipHide(e);
8302 dispatch.on('tooltipHide', function() {
8303 if (tooltips) nv.tooltip.cleanup();
8306 //============================================================
8309 //============================================================
8310 // Expose Public Variables
8311 //------------------------------------------------------------
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;
8320 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor');
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;
8331 chart.width = function(_) {
8332 if (!arguments.length) return width;
8337 chart.height = function(_) {
8338 if (!arguments.length) return height;
8343 chart.color = function(_) {
8344 if (!arguments.length) return color;
8345 color = nv.utils.getColor(_);
8346 legend.color(color);
8350 chart.showControls = function(_) {
8351 if (!arguments.length) return showControls;
8356 chart.showLegend = function(_) {
8357 if (!arguments.length) return showLegend;
8362 chart.logScale = function(_) {
8363 if (!arguments.length) return logScale;
8368 chart.tooltip = function(_) {
8369 if (!arguments.length) return tooltip;
8374 chart.tooltips = function(_) {
8375 if (!arguments.length) return tooltips;
8380 chart.tooltipContent = function(_) {
8381 if (!arguments.length) return tooltip;
8386 chart.state = function(_) {
8387 if (!arguments.length) return state;
8392 chart.defaultState = function(_) {
8393 if (!arguments.length) return defaultState;
8398 chart.noData = function(_) {
8399 if (!arguments.length) return noData;
8404 //============================================================
8409 nv.models.multiChart = function() {
8411 //============================================================
8412 // Public Variables with Default Settings
8413 //------------------------------------------------------------
8415 var margin = {top: 30, right: 20, bottom: 50, left: 60},
8416 color = d3.scale.category20().range(),
8421 tooltip = function(key, x, y, e, graph) {
8422 return '<h3>' + key + '</h3>' +
8423 '<p>' + y + ' at ' + x + '</p>'
8425 x, y; //can be accessed via chart.lines.[x/y]Scale()
8427 //============================================================
8428 // Private Variables
8429 //------------------------------------------------------------
8431 var x = d3.scale.linear(),
8432 yScale1 = d3.scale.linear(),
8433 yScale2 = d3.scale.linear(),
8435 lines1 = nv.models.line().yScale(yScale1),
8436 lines2 = nv.models.line().yScale(yScale2),
8438 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
8439 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
8441 stack1 = nv.models.stackedArea().yScale(yScale1),
8442 stack2 = nv.models.stackedArea().yScale(yScale2),
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'),
8448 legend = nv.models.legend().height(30),
8449 dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
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);
8458 nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
8461 function chart(selection) {
8462 selection.each(function(data) {
8463 var container = d3.select(this),
8466 chart.update = function() { container.transition().call(chart); };
8467 chart.container = this;
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;
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})
8481 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
8483 return d.values.map(function(d,i) {
8484 return { x: d.x, y: d.y }
8488 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
8490 return d.values.map(function(d,i) {
8491 return { x: d.x, y: d.y }
8495 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
8496 .range([0, availableWidth]);
8498 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
8499 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
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');
8512 var g = wrap.select('g');
8515 legend.width( availableWidth / 2 );
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)');
8525 if ( margin.top != legend.height()) {
8526 margin.top = legend.height();
8527 availableHeight = (height || parseInt(container.style('height')) || 400)
8528 - margin.top - margin.bottom;
8531 g.select('.legendWrap')
8532 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
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'}));
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'}));
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'}));
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'}));
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'}));
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'}));
8580 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8583 var lines1Wrap = g.select('.lines1Wrap')
8585 var bars1Wrap = g.select('.bars1Wrap')
8587 var stack1Wrap = g.select('.stack1Wrap')
8590 var lines2Wrap = g.select('.lines2Wrap')
8592 var bars2Wrap = g.select('.bars2Wrap')
8594 var stack2Wrap = g.select('.stack2Wrap')
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}]) : []
8604 yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
8605 .range([0, availableHeight])
8607 yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
8608 .range([0, availableHeight])
8610 lines1.yDomain(yScale1.domain())
8611 bars1.yDomain(yScale1.domain())
8612 stack1.yDomain(yScale1.domain())
8614 lines2.yDomain(yScale2.domain())
8615 bars2.yDomain(yScale2.domain())
8616 stack2.yDomain(yScale2.domain())
8618 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
8619 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
8621 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
8622 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
8624 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
8625 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
8630 .ticks( availableWidth / 100 )
8631 .tickSize(-availableHeight, 0);
8634 .attr('transform', 'translate(0,' + availableHeight + ')');
8635 d3.transition(g.select('.x.axis'))
8639 .ticks( availableHeight / 36 )
8640 .tickSize( -availableWidth, 0);
8643 d3.transition(g.select('.y1.axis'))
8647 .ticks( availableHeight / 36 )
8648 .tickSize( -availableWidth, 0);
8650 d3.transition(g.select('.y2.axis'))
8653 g.select('.y2.axis')
8654 .style('opacity', series2.length ? 1 : 0)
8655 .attr('transform', 'translate(' + x.range()[1] + ',0)');
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) {
8663 wrap.selectAll('.series').classed('disabled', false);
8671 dispatch.on('tooltipShow', function(e) {
8672 if (tooltips) showTooltip(e, that.parentNode);
8681 //============================================================
8682 // Event Handling/Dispatching (out of chart's scope)
8683 //------------------------------------------------------------
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);
8690 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8691 dispatch.tooltipHide(e);
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);
8699 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8700 dispatch.tooltipHide(e);
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);
8708 bars1.dispatch.on('elementMouseout.tooltip', function(e) {
8709 dispatch.tooltipHide(e);
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);
8717 bars2.dispatch.on('elementMouseout.tooltip', function(e) {
8718 dispatch.tooltipHide(e);
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);
8729 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8730 dispatch.tooltipShow(e);
8733 stack1.dispatch.on('tooltipHide', function(e) {
8734 dispatch.tooltipHide(e);
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);
8745 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8746 dispatch.tooltipShow(e);
8749 stack2.dispatch.on('tooltipHide', function(e) {
8750 dispatch.tooltipHide(e);
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);
8758 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8759 dispatch.tooltipHide(e);
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);
8767 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8768 dispatch.tooltipHide(e);
8771 dispatch.on('tooltipHide', function() {
8772 if (tooltips) nv.tooltip.cleanup();
8777 //============================================================
8778 // Global getters and setters
8779 //------------------------------------------------------------
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;
8792 chart.x = function(_) {
8793 if (!arguments.length) return getX;
8800 chart.y = function(_) {
8801 if (!arguments.length) return getY;
8808 chart.margin = function(_) {
8809 if (!arguments.length) return margin;
8814 chart.width = function(_) {
8815 if (!arguments.length) return width;
8820 chart.height = function(_) {
8821 if (!arguments.length) return height;
8826 chart.color = function(_) {
8827 if (!arguments.length) return color;
8833 chart.showLegend = function(_) {
8834 if (!arguments.length) return showLegend;
8839 chart.tooltips = function(_) {
8840 if (!arguments.length) return tooltips;
8845 chart.tooltipContent = function(_) {
8846 if (!arguments.length) return tooltip;
8855 nv.models.ohlcBar = function() {
8857 //============================================================
8858 // Public Variables with Default Settings
8859 //------------------------------------------------------------
8861 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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 }
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
8877 , color = nv.utils.defaultColor()
8880 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8883 //============================================================
8885 //============================================================
8886 // Private Variables
8887 //------------------------------------------------------------
8889 //TODO: store old scales for transitions
8891 //============================================================
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);
8901 //------------------------------------------------------------
8904 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
8907 x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
8909 x.range([0, availableWidth]);
8911 y .domain(yDomain || [
8912 d3.min(data[0].values.map(getLow).concat(forceY)),
8913 d3.max(data[0].values.map(getHigh).concat(forceY))
8915 .range([availableHeight, 0]);
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])
8921 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8924 if (y.domain()[0] === y.domain()[1])
8926 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8929 //------------------------------------------------------------
8932 //------------------------------------------------------------
8933 // Setup containers and skeleton of chart
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');
8941 gEnter.append('g').attr('class', 'nv-ticks');
8943 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8945 //------------------------------------------------------------
8949 .on('click', function(d,i) {
8950 dispatch.chartClick({
8959 defsEnter.append('clipPath')
8960 .attr('id', 'nv-chart-clip-path-' + id)
8963 wrap.select('#nv-chart-clip-path-' + id + ' rect')
8964 .attr('width', availableWidth)
8965 .attr('height', availableHeight);
8967 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
8971 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
8972 .data(function(d) { return d });
8974 ticks.exit().remove();
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;
8989 + (y(getLow(d,i)) - y(getOpen(d,i)))
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]; })
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({
9010 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
9017 .on('mouseout', function(d,i) {
9018 d3.select(this).classed('hover', false);
9019 dispatch.elementMouseout({
9027 .on('click', function(d,i) {
9028 dispatch.elementClick({
9033 pos: [x(getX(d,i)), y(getY(d,i))],
9037 d3.event.stopPropagation();
9039 .on('dblclick', function(d,i) {
9040 dispatch.elementDblClick({
9045 pos: [x(getX(d,i)), y(getY(d,i))],
9049 d3.event.stopPropagation();
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;
9077 //.attr('width', (availableWidth / data[0].values.length) * .9 )
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
9091 //============================================================
9092 // Expose Public Variables
9093 //------------------------------------------------------------
9095 chart.dispatch = dispatch;
9097 chart.x = function(_) {
9098 if (!arguments.length) return getX;
9103 chart.y = function(_) {
9104 if (!arguments.length) return getY;
9109 chart.open = function(_) {
9110 if (!arguments.length) return getOpen;
9115 chart.close = function(_) {
9116 if (!arguments.length) return getClose;
9121 chart.high = function(_) {
9122 if (!arguments.length) return getHigh;
9127 chart.low = function(_) {
9128 if (!arguments.length) return getLow;
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;
9142 chart.width = function(_) {
9143 if (!arguments.length) return width;
9148 chart.height = function(_) {
9149 if (!arguments.length) return height;
9154 chart.xScale = function(_) {
9155 if (!arguments.length) return x;
9160 chart.yScale = function(_) {
9161 if (!arguments.length) return y;
9166 chart.xDomain = function(_) {
9167 if (!arguments.length) return xDomain;
9172 chart.yDomain = function(_) {
9173 if (!arguments.length) return yDomain;
9178 chart.forceX = function(_) {
9179 if (!arguments.length) return forceX;
9184 chart.forceY = function(_) {
9185 if (!arguments.length) return forceY;
9190 chart.padData = function(_) {
9191 if (!arguments.length) return padData;
9196 chart.clipEdge = function(_) {
9197 if (!arguments.length) return clipEdge;
9202 chart.color = function(_) {
9203 if (!arguments.length) return color;
9204 color = nv.utils.getColor(_);
9208 chart.id = function(_) {
9209 if (!arguments.length) return id;
9214 //============================================================
9219 nv.models.pie = function() {
9221 //============================================================
9222 // Public Variables with Default Settings
9223 //------------------------------------------------------------
9225 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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')
9236 , pieLabelsOutside = true
9237 , donutLabelsOutside = false
9238 , labelThreshold = .02 //if slice percentage is under this, don't show label
9240 , labelSunbeamLayout = false
9241 , startAngle = false
9244 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9247 //============================================================
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);
9259 //------------------------------------------------------------
9260 // Setup containers and skeleton of chart
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');
9268 gEnter.append('g').attr('class', 'nv-pie');
9270 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9271 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
9273 //------------------------------------------------------------
9277 .on('click', function(d,i) {
9278 dispatch.chartClick({
9287 var arc = d3.svg.arc()
9288 .outerRadius(arcRadius);
9290 if (startAngle) arc.startAngle(startAngle)
9291 if (endAngle) arc.endAngle(endAngle);
9292 if (donut) arc.innerRadius(radius * donutRatio);
9294 // Setup the Pie chart and choose the data element
9295 var pie = d3.layout.pie()
9297 .value(function(d) { return d.disabled ? 0 : getY(d) });
9299 var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
9302 slices.exit().remove();
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),
9313 pos: [d3.event.pageX, d3.event.pageY],
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),
9327 .on('click', function(d,i) {
9328 dispatch.elementClick({
9329 label: getX(d.data),
9330 value: getY(d.data),
9336 d3.event.stopPropagation();
9338 .on('dblclick', function(d,i) {
9339 dispatch.elementDblClick({
9340 label: getX(d.data),
9341 value: getY(d.data),
9347 d3.event.stopPropagation();
9351 .attr('fill', function(d,i) { return color(d, i); })
9352 .attr('stroke', function(d,i) { return color(d, i); });
9354 var paths = ae.append('path')
9355 .each(function(d) { this._current = d; });
9358 d3.transition(slices.select('path'))
9360 .attrTween('d', arcTween);
9363 // This does the normal label
9364 var labelsArc = d3.svg.arc().innerRadius(0);
9366 if (pieLabelsOutside){ labelsArc = arc; }
9368 if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
9370 ae.append("g").classed("nv-label", true)
9371 .each(function(d, i) {
9372 var group = d3.select(this);
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) {
9385 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
9387 d.outerRadius = radius + 10; // Set Outer Coordinate
9388 d.innerRadius = radius + 15; // Set Inner Coordinate
9389 return 'translate(' + labelsArc.centroid(d) + ')'
9393 group.append('rect')
9394 .style('stroke', '#fff')
9395 .style('fill', '#fff')
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')
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) {
9417 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
9419 d.outerRadius = radius + 10; // Set Outer Coordinate
9420 d.innerRadius = radius + 15; // Set Inner Coordinate
9421 return 'translate(' + labelsArc.centroid(d) + ')'
9425 slices.each(function(d, i) {
9426 var slice = d3.select(this);
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) : '';
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] + ")";
9447 // Computes the angle of an arc, converting from radians to degrees.
9449 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
9450 return a > 90 ? a - 180 : a;
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) {
9464 function tweenPie(b) {
9466 var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
9467 return function(t) {
9478 //============================================================
9479 // Expose Public Variables
9480 //------------------------------------------------------------
9482 chart.dispatch = dispatch;
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;
9493 chart.width = function(_) {
9494 if (!arguments.length) return width;
9499 chart.height = function(_) {
9500 if (!arguments.length) return height;
9505 chart.values = function(_) {
9506 if (!arguments.length) return getValues;
9511 chart.x = function(_) {
9512 if (!arguments.length) return getX;
9517 chart.y = function(_) {
9518 if (!arguments.length) return getY;
9519 getY = d3.functor(_);
9523 chart.description = function(_) {
9524 if (!arguments.length) return getDescription;
9529 chart.showLabels = function(_) {
9530 if (!arguments.length) return showLabels;
9535 chart.labelSunbeamLayout = function(_) {
9536 if (!arguments.length) return labelSunbeamLayout;
9537 labelSunbeamLayout = _;
9541 chart.donutLabelsOutside = function(_) {
9542 if (!arguments.length) return donutLabelsOutside;
9543 donutLabelsOutside = _;
9547 chart.pieLabelsOutside = function(_) {
9548 if (!arguments.length) return pieLabelsOutside;
9549 pieLabelsOutside = _;
9553 chart.donut = function(_) {
9554 if (!arguments.length) return donut;
9559 chart.donutRatio = function(_) {
9560 if (!arguments.length) return donutRatio;
9565 chart.startAngle = function(_) {
9566 if (!arguments.length) return startAngle;
9571 chart.endAngle = function(_) {
9572 if (!arguments.length) return endAngle;
9577 chart.id = function(_) {
9578 if (!arguments.length) return id;
9583 chart.color = function(_) {
9584 if (!arguments.length) return color;
9585 color = nv.utils.getColor(_);
9589 chart.valueFormat = function(_) {
9590 if (!arguments.length) return valueFormat;
9595 chart.labelThreshold = function(_) {
9596 if (!arguments.length) return labelThreshold;
9600 //============================================================
9605 nv.models.pieChart = function() {
9607 //============================================================
9608 // Public Variables with Default Settings
9609 //------------------------------------------------------------
9611 var pie = nv.models.pie()
9612 , legend = nv.models.legend()
9615 var margin = {top: 30, right: 20, bottom: 20, left: 20}
9619 , color = nv.utils.defaultColor()
9621 , tooltip = function(key, y, e, graph) {
9622 return '<h3>' + key + '</h3>' +
9626 , defaultState = null
9627 , noData = "No Data Available."
9628 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
9631 //============================================================
9634 //============================================================
9635 // Private Variables
9636 //------------------------------------------------------------
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);
9645 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
9648 //============================================================
9651 function chart(selection) {
9652 selection.each(function(data) {
9653 var container = d3.select(this),
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;
9661 chart.update = function() { container.transition().call(chart); };
9662 chart.container = this;
9664 //set state.disabled
9665 state.disabled = data[0].map(function(d) { return !!d.disabled });
9667 if (!defaultState) {
9670 for (key in state) {
9671 if (state[key] instanceof Array)
9672 defaultState[key] = state[key].slice(0);
9674 defaultState[key] = state[key];
9678 //------------------------------------------------------------
9679 // Display No Data message if there's nothing to show.
9681 if (!data[0] || !data[0].length) {
9682 var noDataText = container.selectAll('.nv-noData').data([noData]);
9684 noDataText.enter().append('text')
9685 .attr('class', 'nvd3 nv-noData')
9686 .attr('dy', '-.7em')
9687 .style('text-anchor', 'middle');
9690 .attr('x', margin.left + availableWidth / 2)
9691 .attr('y', margin.top + availableHeight / 2)
9692 .text(function(d) { return d });
9696 container.selectAll('.nv-noData').remove();
9699 //------------------------------------------------------------
9702 //------------------------------------------------------------
9703 // Setup containers and skeleton of chart
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');
9709 gEnter.append('g').attr('class', 'nv-pieWrap');
9710 gEnter.append('g').attr('class', 'nv-legendWrap');
9712 //------------------------------------------------------------
9715 //------------------------------------------------------------
9720 .width( availableWidth )
9723 wrap.select('.nv-legendWrap')
9724 .datum(pie.values()(data[0]))
9727 if ( margin.top != legend.height()) {
9728 margin.top = legend.height();
9729 availableHeight = (height || parseInt(container.style('height')) || 400)
9730 - margin.top - margin.bottom;
9733 wrap.select('.nv-legendWrap')
9734 .attr('transform', 'translate(0,' + (-margin.top) +')');
9737 //------------------------------------------------------------
9740 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9743 //------------------------------------------------------------
9744 // Main Chart Component(s)
9747 .width(availableWidth)
9748 .height(availableHeight);
9751 var pieWrap = g.select('.nv-pieWrap')
9754 d3.transition(pieWrap).call(pie);
9756 //------------------------------------------------------------
9759 //============================================================
9760 // Event Handling/Dispatching (in chart's scope)
9761 //------------------------------------------------------------
9763 legend.dispatch.on('legendClick', function(d,i, that) {
9764 d.disabled = !d.disabled;
9766 if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) {
9767 pie.values()(data[0]).map(function(d) {
9769 wrap.selectAll('.nv-series').classed('disabled', false);
9774 state.disabled = data[0].map(function(d) { return !!d.disabled });
9775 dispatch.stateChange(state);
9780 pie.dispatch.on('elementMouseout.tooltip', function(e) {
9781 dispatch.tooltipHide(e);
9784 // Update chart from a state object passed to event handler
9785 dispatch.on('changeState', function(e) {
9787 if (typeof e.disabled !== 'undefined') {
9788 data[0].forEach(function(series,i) {
9789 series.disabled = e.disabled[i];
9792 state.disabled = e.disabled;
9798 //============================================================
9806 //============================================================
9807 // Event Handling/Dispatching (out of chart's scope)
9808 //------------------------------------------------------------
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);
9815 dispatch.on('tooltipShow', function(e) {
9816 if (tooltips) showTooltip(e);
9819 dispatch.on('tooltipHide', function() {
9820 if (tooltips) nv.tooltip.cleanup();
9823 //============================================================
9826 //============================================================
9827 // Expose Public Variables
9828 //------------------------------------------------------------
9830 // expose chart's sub-components
9831 chart.legend = legend;
9832 chart.dispatch = dispatch;
9835 d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'donut', 'donutRatio', 'labelThreshold');
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;
9846 chart.width = function(_) {
9847 if (!arguments.length) return width;
9852 chart.height = function(_) {
9853 if (!arguments.length) return height;
9858 chart.color = function(_) {
9859 if (!arguments.length) return color;
9860 color = nv.utils.getColor(_);
9861 legend.color(color);
9866 chart.showLegend = function(_) {
9867 if (!arguments.length) return showLegend;
9872 chart.tooltips = function(_) {
9873 if (!arguments.length) return tooltips;
9878 chart.tooltipContent = function(_) {
9879 if (!arguments.length) return tooltip;
9884 chart.state = function(_) {
9885 if (!arguments.length) return state;
9890 chart.defaultState = function(_) {
9891 if (!arguments.length) return defaultState;
9896 chart.noData = function(_) {
9897 if (!arguments.length) return noData;
9902 //============================================================
9908 nv.models.scatter = function() {
9910 //============================================================
9911 // Public Variables with Default Settings
9912 //------------------------------------------------------------
9914 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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
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
9942 , singlePoint = false
9943 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
9947 //============================================================
9950 //============================================================
9951 // Private Variables
9952 //------------------------------------------------------------
9954 var x0, y0, z0 // used to store previous scales
9956 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
9959 //============================================================
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);
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) {
9977 //------------------------------------------------------------
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
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) }
9990 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
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 ]);
9996 x.range([0, availableWidth]);
9998 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
9999 .range([availableHeight, 0]);
10001 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10002 .range(sizeRange || [16, 256]);
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])
10008 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10009 : x.domain([-1,1]);
10011 if (y.domain()[0] === y.domain()[1])
10013 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
10014 : y.domain([-1,1]);
10016 if ( isNaN(x.domain()[0])) {
10020 if ( isNaN(y.domain()[0])) {
10029 //------------------------------------------------------------
10032 //------------------------------------------------------------
10033 // Setup containers and skeleton of chart
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');
10041 gEnter.append('g').attr('class', 'nv-groups');
10042 gEnter.append('g').attr('class', 'nv-point-paths');
10044 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10046 //------------------------------------------------------------
10049 defsEnter.append('clipPath')
10050 .attr('id', 'nv-edge-clip-' + id)
10053 wrap.select('#nv-edge-clip-' + id + ' rect')
10054 .attr('width', availableWidth)
10055 .attr('height', availableHeight);
10057 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10060 function updateInteractiveLayer() {
10062 if (!interactive) return false;
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.
10073 var pX = getX(point,pointIndex) + Math.random() * 1e-7;
10074 var pY = getY(point,pointIndex) + Math.random() * 1e-7;
10079 pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
10081 .filter(function(pointArray, pointIndex) {
10082 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10089 //inject series and point index for reference into voronoi
10090 if (useVoronoi === true) {
10093 var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
10097 pointClipsEnter.append('clipPath')
10098 .attr('class', 'nv-point-clips')
10099 .attr('id', 'nv-points-clip-' + id);
10101 var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
10103 pointClips.enter().append('circle')
10104 .attr('r', clipRadius);
10105 pointClips.exit().remove();
10107 .attr('cx', function(d) { return d[0] })
10108 .attr('cy', function(d) { return d[1] });
10110 wrap.select('.nv-point-paths')
10111 .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
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]);
10123 var bounds = d3.geom.polygon([
10126 [width + 10,height + 10],
10130 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
10132 'data': bounds.clip(d),
10133 'series': vertices[i][2],
10134 'point': vertices[i][3]
10139 var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
10141 pointPaths.enter().append('path')
10142 .attr('class', function(d,i) { return 'nv-path-'+i; });
10143 pointPaths.exit().remove();
10145 .attr('d', function(d) {
10146 if (d.data.length === 0)
10149 return 'M' + d.data.join('L') + 'Z';
10153 .on('click', function(d) {
10154 if (needsUpdate) return 0;
10155 var series = data[d.series],
10156 point = series.values[d.point];
10158 dispatch.elementClick({
10161 pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
10162 seriesIndex: d.series,
10163 pointIndex: d.point
10166 .on('mouseover', function(d) {
10167 if (needsUpdate) return 0;
10168 var series = data[d.series],
10169 point = series.values[d.point];
10171 dispatch.elementMouseover({
10174 pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
10175 seriesIndex: d.series,
10176 pointIndex: d.point
10179 .on('mouseout', function(d, i) {
10180 if (needsUpdate) return 0;
10181 var series = data[d.series],
10182 point = series.values[d.point];
10184 dispatch.elementMouseout({
10187 seriesIndex: d.series,
10188 pointIndex: d.point
10195 // bring data in form needed for click handlers
10196 var dataWithPoints = vertices.map(function(d, i) {
10199 'series': vertices[i][2],
10200 'point': vertices[i][3]
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];
10216 dispatch.elementClick({
10219 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
10220 seriesIndex: d.series,
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];
10229 dispatch.elementMouseover({
10232 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
10233 seriesIndex: d.series,
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];
10242 dispatch.elementMouseout({
10245 seriesIndex: d.series,
10251 needsUpdate = false;
10254 needsUpdate = true;
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)
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);
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)) })
10288 points.each(function(d,i) {
10290 .classed('nv-point', true)
10291 .classed('nv-point-' + i, true);
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) });
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)) + ')'
10309 .size(function(d,i) { return z(getSize(d,i)) })
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)) + ')'
10317 points.each(function(d,i) {
10319 .classed('nv-point', true)
10320 .classed('nv-point-' + i, true);
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)) + ')'
10330 .size(function(d,i) { return z(getSize(d,i)) })
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();
10340 //store old scales for use in transitions on update
10351 //============================================================
10352 // Event Handling/Dispatching (out of chart's scope)
10353 //------------------------------------------------------------
10355 dispatch.on('elementMouseover.point', function(d) {
10357 d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
10358 .classed('hover', true);
10361 dispatch.on('elementMouseout.point', function(d) {
10363 d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
10364 .classed('hover', false);
10367 //============================================================
10370 //============================================================
10371 // Expose Public Variables
10372 //------------------------------------------------------------
10374 chart.dispatch = dispatch;
10376 chart.x = function(_) {
10377 if (!arguments.length) return getX;
10378 getX = d3.functor(_);
10382 chart.y = function(_) {
10383 if (!arguments.length) return getY;
10384 getY = d3.functor(_);
10388 chart.size = function(_) {
10389 if (!arguments.length) return getSize;
10390 getSize = d3.functor(_);
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;
10403 chart.width = function(_) {
10404 if (!arguments.length) return width;
10409 chart.height = function(_) {
10410 if (!arguments.length) return height;
10415 chart.xScale = function(_) {
10416 if (!arguments.length) return x;
10421 chart.yScale = function(_) {
10422 if (!arguments.length) return y;
10427 chart.zScale = function(_) {
10428 if (!arguments.length) return z;
10433 chart.xDomain = function(_) {
10434 if (!arguments.length) return xDomain;
10439 chart.yDomain = function(_) {
10440 if (!arguments.length) return yDomain;
10445 chart.sizeDomain = function(_) {
10446 if (!arguments.length) return sizeDomain;
10451 chart.sizeRange = function(_) {
10452 if (!arguments.length) return sizeRange;
10457 chart.forceX = function(_) {
10458 if (!arguments.length) return forceX;
10463 chart.forceY = function(_) {
10464 if (!arguments.length) return forceY;
10469 chart.forceSize = function(_) {
10470 if (!arguments.length) return forceSize;
10475 chart.interactive = function(_) {
10476 if (!arguments.length) return interactive;
10481 chart.pointKey = function(_) {
10482 if (!arguments.length) return pointKey;
10487 chart.pointActive = function(_) {
10488 if (!arguments.length) return pointActive;
10493 chart.padData = function(_) {
10494 if (!arguments.length) return padData;
10499 chart.padDataOuter = function(_) {
10500 if (!arguments.length) return padDataOuter;
10505 chart.clipEdge = function(_) {
10506 if (!arguments.length) return clipEdge;
10511 chart.clipVoronoi= function(_) {
10512 if (!arguments.length) return clipVoronoi;
10517 chart.useVoronoi= function(_) {
10518 if (!arguments.length) return useVoronoi;
10520 if (useVoronoi === false) {
10521 clipVoronoi = false;
10526 chart.clipRadius = function(_) {
10527 if (!arguments.length) return clipRadius;
10532 chart.color = function(_) {
10533 if (!arguments.length) return color;
10534 color = nv.utils.getColor(_);
10538 chart.shape = function(_) {
10539 if (!arguments.length) return getShape;
10544 chart.onlyCircles = function(_) {
10545 if (!arguments.length) return onlyCircles;
10550 chart.id = function(_) {
10551 if (!arguments.length) return id;
10556 chart.singlePoint = function(_) {
10557 if (!arguments.length) return singlePoint;
10562 //============================================================
10568 nv.models.scatterChart = function() {
10570 //============================================================
10571 // Public Variables with Default Settings
10572 //------------------------------------------------------------
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()
10583 var margin = {top: 30, right: 20, bottom: 50, left: 75}
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()
10591 , showDistX = false
10592 , showDistY = false
10593 , showLegend = true
10594 , showControls = !!d3.fisheye
10596 , pauseFisheye = false
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>' }
10603 , defaultState = null
10604 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10605 , noData = "No Data Available."
10627 //============================================================
10630 //============================================================
10631 // Private Variables
10632 //------------------------------------------------------------
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?)
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));
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);
10656 var controlsData = [
10657 { key: 'Magnify', disabled: true }
10660 //============================================================
10663 function chart(selection) {
10664 selection.each(function(data) {
10665 var container = d3.select(this),
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;
10673 chart.update = function() { container.transition().call(chart); };
10674 // chart.container = this;
10676 //set state.disabled
10677 state.disabled = data.map(function(d) { return !!d.disabled });
10679 if (!defaultState) {
10682 for (key in state) {
10683 if (state[key] instanceof Array)
10684 defaultState[key] = state[key].slice(0);
10686 defaultState[key] = state[key];
10690 //------------------------------------------------------------
10691 // Display noData message if there's nothing to show.
10693 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
10694 var noDataText = container.selectAll('.nv-noData').data([noData]);
10696 noDataText.enter().append('text')
10697 .attr('class', 'nvd3 nv-noData')
10698 .attr('dy', '-.7em')
10699 .style('text-anchor', 'middle');
10702 .attr('x', margin.left + availableWidth / 2)
10703 .attr('y', margin.top + availableHeight / 2)
10704 .text(function(d) { return d });
10708 container.selectAll('.nv-noData').remove();
10711 //------------------------------------------------------------
10714 //------------------------------------------------------------
10720 //------------------------------------------------------------
10723 //------------------------------------------------------------
10724 // Setup containers and skeleton of chart
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');
10731 // background for pointer events
10732 gEnter.append('rect').attr('class', 'nvd3 nv-background');
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');
10741 //------------------------------------------------------------
10744 //------------------------------------------------------------
10748 legend.width( availableWidth / 2 );
10750 wrap.select('.nv-legendWrap')
10754 if ( margin.top != legend.height()) {
10755 margin.top = legend.height();
10756 availableHeight = (height || parseInt(container.style('height')) || 400)
10757 - margin.top - margin.bottom;
10760 wrap.select('.nv-legendWrap')
10761 .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
10764 //------------------------------------------------------------
10767 //------------------------------------------------------------
10770 if (showControls) {
10771 controls.width(180).color(['#444']);
10772 g.select('.nv-controlsWrap')
10773 .datum(controlsData)
10774 .attr('transform', 'translate(0,' + (-margin.top) +')')
10778 //------------------------------------------------------------
10781 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10784 //------------------------------------------------------------
10785 // Main Chart Component(s)
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 }))
10796 wrap.select('.nv-scatterWrap')
10797 .datum(data.filter(function(d) { return !d.disabled }))
10801 //Adjust for x and y padding
10803 var xRange = x.domain()[1] - x.domain()[0];
10804 scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
10808 var yRange = y.domain()[1] - y.domain()[0];
10809 scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
10812 wrap.select('.nv-scatterWrap')
10813 .datum(data.filter(function(d) { return !d.disabled }))
10816 //------------------------------------------------------------
10819 //------------------------------------------------------------
10824 .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
10825 .tickSize( -availableHeight , 0);
10827 g.select('.nv-x.nv-axis')
10828 .attr('transform', 'translate(0,' + y.range()[0] + ')')
10834 .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
10835 .tickSize( -availableWidth, 0);
10837 g.select('.nv-y.nv-axis')
10843 .getData(scatter.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 }))
10859 .getData(scatter.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 }))
10873 //------------------------------------------------------------
10879 g.select('.nv-background')
10880 .attr('width', availableWidth)
10881 .attr('height', availableHeight);
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;
10891 function updateFisheye() {
10892 if (pauseFisheye) {
10893 g.select('.nv-point-paths').style('pointer-events', 'all');
10897 g.select('.nv-point-paths').style('pointer-events', 'none' );
10899 var mouse = d3.mouse(this);
10900 x.distortion(fisheye).focus(mouse[0]);
10901 y.distortion(fisheye).focus(mouse[1]);
10903 g.select('.nv-scatterWrap')
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 }))
10911 g.select('.nv-distributionY')
10912 .datum(data.filter(function(d) { return !d.disabled }))
10918 //============================================================
10919 // Event Handling/Dispatching (in chart's scope)
10920 //------------------------------------------------------------
10922 controls.dispatch.on('legendClick', function(d,i) {
10923 d.disabled = !d.disabled;
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' );
10930 x.distortion(fisheye).focus(0);
10931 y.distortion(fisheye).focus(0);
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);
10937 pauseFisheye = false;
10943 legend.dispatch.on('legendClick', function(d,i, that) {
10944 d.disabled = !d.disabled;
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);
10954 state.disabled = data.map(function(d) { return !!d.disabled });
10955 dispatch.stateChange(state);
10960 legend.dispatch.on('legendDblclick', function(d) {
10961 //Double clicking should always enable current series, and disabled all others.
10962 data.forEach(function(d) {
10965 d.disabled = false;
10967 state.disabled = data.map(function(d) { return !!d.disabled });
10968 dispatch.stateChange(state);
10974 legend.dispatch.on('legendMouseover', function(d, i) {
10979 legend.dispatch.on('legendMouseout', function(d, i) {
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());
10991 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10992 dispatch.tooltipShow(e);
10995 dispatch.on('tooltipShow', function(e) {
10996 if (tooltips) showTooltip(e, that.parentNode);
10999 // Update chart from a state object passed to event handler
11000 dispatch.on('changeState', function(e) {
11002 if (typeof e.disabled !== 'undefined') {
11003 data.forEach(function(series,i) {
11004 series.disabled = e.disabled[i];
11007 state.disabled = e.disabled;
11013 //============================================================
11016 //store old scales for use in transitions on update
11027 //============================================================
11028 // Event Handling/Dispatching (out of chart's scope)
11029 //------------------------------------------------------------
11031 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11032 dispatch.tooltipHide(e);
11034 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11036 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11037 .attr('x2', distY.size());
11039 dispatch.on('tooltipHide', function() {
11040 if (tooltips) nv.tooltip.cleanup();
11043 //============================================================
11046 //============================================================
11047 // Expose Public Variables
11048 //------------------------------------------------------------
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;
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');
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;
11071 chart.width = function(_) {
11072 if (!arguments.length) return width;
11077 chart.height = function(_) {
11078 if (!arguments.length) return height;
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);
11092 chart.showDistX = function(_) {
11093 if (!arguments.length) return showDistX;
11098 chart.showDistY = function(_) {
11099 if (!arguments.length) return showDistY;
11104 chart.showControls = function(_) {
11105 if (!arguments.length) return showControls;
11110 chart.showLegend = function(_) {
11111 if (!arguments.length) return showLegend;
11116 chart.fisheye = function(_) {
11117 if (!arguments.length) return fisheye;
11122 chart.xPadding = function(_) {
11123 if (!arguments.length) return xPadding;
11128 chart.yPadding = function(_) {
11129 if (!arguments.length) return yPadding;
11134 chart.tooltips = function(_) {
11135 if (!arguments.length) return tooltips;
11140 chart.tooltipContent = function(_) {
11141 if (!arguments.length) return tooltip;
11146 chart.tooltipXContent = function(_) {
11147 if (!arguments.length) return tooltipX;
11152 chart.tooltipYContent = function(_) {
11153 if (!arguments.length) return tooltipY;
11158 chart.state = function(_) {
11159 if (!arguments.length) return state;
11164 chart.defaultState = function(_) {
11165 if (!arguments.length) return defaultState;
11170 chart.noData = function(_) {
11171 if (!arguments.length) return noData;
11176 //============================================================
11182 nv.models.scatterPlusLineChart = function() {
11184 //============================================================
11185 // Public Variables with Default Settings
11186 //------------------------------------------------------------
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()
11197 var margin = {top: 30, right: 20, bottom: 50, left: 75}
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
11208 , pauseFisheye = false
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>' }
11216 , defaultState = null
11217 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11218 , noData = "No Data Available."
11240 //============================================================
11243 //============================================================
11244 // Private Variables
11245 //------------------------------------------------------------
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?)
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));
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);
11269 var controlsData = [
11270 { key: 'Magnify', disabled: true }
11273 //============================================================
11276 function chart(selection) {
11277 selection.each(function(data) {
11278 var container = d3.select(this),
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;
11286 chart.update = function() { container.transition().call(chart); };
11287 chart.container = this;
11289 //set state.disabled
11290 state.disabled = data.map(function(d) { return !!d.disabled });
11292 if (!defaultState) {
11295 for (key in state) {
11296 if (state[key] instanceof Array)
11297 defaultState[key] = state[key].slice(0);
11299 defaultState[key] = state[key];
11303 //------------------------------------------------------------
11304 // Display noData message if there's nothing to show.
11306 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11307 var noDataText = container.selectAll('.nv-noData').data([noData]);
11309 noDataText.enter().append('text')
11310 .attr('class', 'nvd3 nv-noData')
11311 .attr('dy', '-.7em')
11312 .style('text-anchor', 'middle');
11315 .attr('x', margin.left + availableWidth / 2)
11316 .attr('y', margin.top + availableHeight / 2)
11317 .text(function(d) { return d });
11321 container.selectAll('.nv-noData').remove();
11324 //------------------------------------------------------------
11327 //------------------------------------------------------------
11330 x = scatter.xScale();
11331 y = scatter.yScale();
11336 //------------------------------------------------------------
11339 //------------------------------------------------------------
11340 // Setup containers and skeleton of chart
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')
11347 // background for pointer events
11348 gEnter.append('rect').attr('class', 'nvd3 nv-background')
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');
11358 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11360 //------------------------------------------------------------
11363 //------------------------------------------------------------
11367 legend.width( availableWidth / 2 );
11369 wrap.select('.nv-legendWrap')
11373 if ( margin.top != legend.height()) {
11374 margin.top = legend.height();
11375 availableHeight = (height || parseInt(container.style('height')) || 400)
11376 - margin.top - margin.bottom;
11379 wrap.select('.nv-legendWrap')
11380 .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
11383 //------------------------------------------------------------
11386 //------------------------------------------------------------
11389 if (showControls) {
11390 controls.width(180).color(['#444']);
11391 g.select('.nv-controlsWrap')
11392 .datum(controlsData)
11393 .attr('transform', 'translate(0,' + (-margin.top) +')')
11397 //------------------------------------------------------------
11400 //------------------------------------------------------------
11401 // Main Chart Component(s)
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 }))
11410 wrap.select('.nv-scatterWrap')
11411 .datum(data.filter(function(d) { return !d.disabled }))
11415 wrap.select('.nv-regressionLinesWrap')
11416 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
11418 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
11419 .data(function(d) { return d });
11421 var reglines = regWrap.enter()
11422 .append('g').attr('class', 'nv-regLines')
11423 .append('line').attr('class', 'nv-regLine')
11424 .style('stroke-opacity', 0);
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
11438 //------------------------------------------------------------
11441 //------------------------------------------------------------
11446 .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
11447 .tickSize( -availableHeight , 0);
11449 g.select('.nv-x.nv-axis')
11450 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11456 .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
11457 .tickSize( -availableWidth, 0);
11459 g.select('.nv-y.nv-axis')
11465 .getData(scatter.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 }))
11481 .getData(scatter.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 }))
11495 //------------------------------------------------------------
11501 g.select('.nv-background')
11502 .attr('width', availableWidth)
11503 .attr('height', availableHeight);
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;
11513 function updateFisheye() {
11514 if (pauseFisheye) {
11515 g.select('.nv-point-paths').style('pointer-events', 'all');
11519 g.select('.nv-point-paths').style('pointer-events', 'none' );
11521 var mouse = d3.mouse(this);
11522 x.distortion(fisheye).focus(mouse[0]);
11523 y.distortion(fisheye).focus(mouse[1]);
11525 g.select('.nv-scatterWrap')
11526 .datum(data.filter(function(d) { return !d.disabled }))
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 }))
11533 g.select('.nv-distributionY')
11534 .datum(data.filter(function(d) { return !d.disabled }))
11540 //============================================================
11541 // Event Handling/Dispatching (in chart's scope)
11542 //------------------------------------------------------------
11544 controls.dispatch.on('legendClick', function(d,i) {
11545 d.disabled = !d.disabled;
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' );
11552 x.distortion(fisheye).focus(0);
11553 y.distortion(fisheye).focus(0);
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);
11559 pauseFisheye = false;
11565 legend.dispatch.on('legendClick', function(d,i, that) {
11566 d.disabled = !d.disabled;
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);
11576 state.disabled = data.map(function(d) { return !!d.disabled });
11577 dispatch.stateChange(state);
11582 legend.dispatch.on('legendDblclick', function(d) {
11583 //Double clicking should always enable current series, and disabled all others.
11584 data.forEach(function(d) {
11587 d.disabled = false;
11589 state.disabled = data.map(function(d) { return !!d.disabled });
11590 dispatch.stateChange(state);
11596 legend.dispatch.on('legendMouseover', function(d, i) {
11601 legend.dispatch.on('legendMouseout', function(d, i) {
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());
11613 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11614 dispatch.tooltipShow(e);
11617 dispatch.on('tooltipShow', function(e) {
11618 if (tooltips) showTooltip(e, that.parentNode);
11621 // Update chart from a state object passed to event handler
11622 dispatch.on('changeState', function(e) {
11624 if (typeof e.disabled !== 'undefined') {
11625 data.forEach(function(series,i) {
11626 series.disabled = e.disabled[i];
11629 state.disabled = e.disabled;
11635 //============================================================
11638 //store old scales for use in transitions on update
11649 //============================================================
11650 // Event Handling/Dispatching (out of chart's scope)
11651 //------------------------------------------------------------
11653 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11654 dispatch.tooltipHide(e);
11656 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11658 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11659 .attr('x2', distY.size());
11661 dispatch.on('tooltipHide', function() {
11662 if (tooltips) nv.tooltip.cleanup();
11665 //============================================================
11668 //============================================================
11669 // Expose Public Variables
11670 //------------------------------------------------------------
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;
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');
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;
11693 chart.width = function(_) {
11694 if (!arguments.length) return width;
11699 chart.height = function(_) {
11700 if (!arguments.length) return height;
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);
11714 chart.showDistX = function(_) {
11715 if (!arguments.length) return showDistX;
11720 chart.showDistY = function(_) {
11721 if (!arguments.length) return showDistY;
11726 chart.showControls = function(_) {
11727 if (!arguments.length) return showControls;
11732 chart.showLegend = function(_) {
11733 if (!arguments.length) return showLegend;
11738 chart.fisheye = function(_) {
11739 if (!arguments.length) return fisheye;
11744 chart.tooltips = function(_) {
11745 if (!arguments.length) return tooltips;
11750 chart.tooltipContent = function(_) {
11751 if (!arguments.length) return tooltip;
11756 chart.tooltipXContent = function(_) {
11757 if (!arguments.length) return tooltipX;
11762 chart.tooltipYContent = function(_) {
11763 if (!arguments.length) return tooltipY;
11768 chart.state = function(_) {
11769 if (!arguments.length) return state;
11774 chart.defaultState = function(_) {
11775 if (!arguments.length) return defaultState;
11780 chart.noData = function(_) {
11781 if (!arguments.length) return noData;
11786 //============================================================
11792 nv.models.sparkline = function() {
11794 //============================================================
11795 // Public Variables with Default Settings
11796 //------------------------------------------------------------
11798 var margin = {top: 2, right: 0, bottom: 2, left: 0}
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'])
11811 //============================================================
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);
11821 //------------------------------------------------------------
11824 x .domain(xDomain || d3.extent(data, getX ))
11825 .range([0, availableWidth]);
11827 y .domain(yDomain || d3.extent(data, getY ))
11828 .range([availableHeight, 0]);
11830 //------------------------------------------------------------
11833 //------------------------------------------------------------
11834 // Setup containers and skeleton of chart
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');
11841 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
11843 //------------------------------------------------------------
11846 var paths = wrap.selectAll('path')
11847 .data(function(d) { return [d] });
11848 paths.enter().append('path');
11849 paths.exit().remove();
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)) })
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) {
11864 var result = data[index];
11865 result.pointIndex = index;
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;});
11876 points.enter().append('circle');
11877 points.exit().remove();
11879 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
11880 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
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'
11892 //============================================================
11893 // Expose Public Variables
11894 //------------------------------------------------------------
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;
11905 chart.width = function(_) {
11906 if (!arguments.length) return width;
11911 chart.height = function(_) {
11912 if (!arguments.length) return height;
11917 chart.x = function(_) {
11918 if (!arguments.length) return getX;
11919 getX = d3.functor(_);
11923 chart.y = function(_) {
11924 if (!arguments.length) return getY;
11925 getY = d3.functor(_);
11929 chart.xScale = function(_) {
11930 if (!arguments.length) return x;
11935 chart.yScale = function(_) {
11936 if (!arguments.length) return y;
11941 chart.xDomain = function(_) {
11942 if (!arguments.length) return xDomain;
11947 chart.yDomain = function(_) {
11948 if (!arguments.length) return yDomain;
11953 chart.animate = function(_) {
11954 if (!arguments.length) return animate;
11959 chart.color = function(_) {
11960 if (!arguments.length) return color;
11961 color = nv.utils.getColor(_);
11965 //============================================================
11971 nv.models.sparklinePlus = function() {
11973 //============================================================
11974 // Public Variables with Default Settings
11975 //------------------------------------------------------------
11977 var sparkline = nv.models.sparkline();
11979 var margin = {top: 15, right: 100, bottom: 10, left: 50}
11986 , xTickFormat = d3.format(',r')
11987 , yTickFormat = d3.format(',.2f')
11989 , alignValue = true
11990 , rightAlignValue = false
11991 , noData = "No Data Available."
11994 //============================================================
11997 function chart(selection) {
11998 selection.each(function(data) {
11999 var container = d3.select(this);
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;
12008 chart.update = function() { chart(selection) };
12009 chart.container = this;
12012 //------------------------------------------------------------
12013 // Display No Data message if there's nothing to show.
12015 if (!data || !data.length) {
12016 var noDataText = container.selectAll('.nv-noData').data([noData]);
12018 noDataText.enter().append('text')
12019 .attr('class', 'nvd3 nv-noData')
12020 .attr('dy', '-.7em')
12021 .style('text-anchor', 'middle');
12024 .attr('x', margin.left + availableWidth / 2)
12025 .attr('y', margin.top + availableHeight / 2)
12026 .text(function(d) { return d });
12030 container.selectAll('.nv-noData').remove();
12033 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
12035 //------------------------------------------------------------
12039 //------------------------------------------------------------
12042 x = sparkline.xScale();
12043 y = sparkline.yScale();
12045 //------------------------------------------------------------
12048 //------------------------------------------------------------
12049 // Setup containers and skeleton of chart
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');
12056 gEnter.append('g').attr('class', 'nv-sparklineWrap');
12057 gEnter.append('g').attr('class', 'nv-valueWrap');
12058 gEnter.append('g').attr('class', 'nv-hoverArea');
12060 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12062 //------------------------------------------------------------
12065 //------------------------------------------------------------
12066 // Main Chart Component(s)
12068 var sparklineWrap = g.select('.nv-sparklineWrap');
12071 .width(availableWidth)
12072 .height(availableHeight);
12077 //------------------------------------------------------------
12080 var valueWrap = g.select('.nv-valueWrap');
12082 var value = valueWrap.selectAll('.nv-currentValue')
12083 .data([currentValue]);
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');
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));
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(); });
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);
12111 function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
12112 if (paused) return;
12114 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
12116 var hoverEnter = hoverValue.enter()
12117 .append('g').attr('class', 'nv-hoverValue')
12118 .style('stroke-opacity', 0)
12119 .style('fill-opacity', 0);
12122 .transition().duration(250)
12123 .style('stroke-opacity', 0)
12124 .style('fill-opacity', 0)
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);
12133 if (!index.length) return;
12135 hoverEnter.append('line')
12137 .attr('y1', -margin.top)
12139 .attr('y2', availableHeight);
12142 hoverEnter.append('text').attr('class', 'nv-xValue')
12144 .attr('y', -margin.top)
12145 .attr('text-anchor', 'end')
12146 .attr('dy', '.9em')
12149 g.select('.nv-hoverValue .nv-xValue')
12150 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
12152 hoverEnter.append('text').attr('class', 'nv-yValue')
12154 .attr('y', -margin.top)
12155 .attr('text-anchor', 'start')
12156 .attr('dy', '.9em')
12158 g.select('.nv-hoverValue .nv-yValue')
12159 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
12164 function sparklineHover() {
12165 if (paused) return;
12167 var pos = d3.mouse(this)[0] - margin.left;
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);
12178 return closestIndex;
12181 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12192 //============================================================
12193 // Expose Public Variables
12194 //------------------------------------------------------------
12196 // expose chart's sub-components
12197 chart.sparkline = sparkline;
12199 d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
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;
12210 chart.width = function(_) {
12211 if (!arguments.length) return width;
12216 chart.height = function(_) {
12217 if (!arguments.length) return height;
12222 chart.xTickFormat = function(_) {
12223 if (!arguments.length) return xTickFormat;
12228 chart.yTickFormat = function(_) {
12229 if (!arguments.length) return yTickFormat;
12234 chart.showValue = function(_) {
12235 if (!arguments.length) return showValue;
12240 chart.alignValue = function(_) {
12241 if (!arguments.length) return alignValue;
12246 chart.rightAlignValue = function(_) {
12247 if (!arguments.length) return rightAlignValue;
12248 rightAlignValue = _;
12252 chart.noData = function(_) {
12253 if (!arguments.length) return noData;
12258 //============================================================
12264 nv.models.stackedArea = function() {
12266 //============================================================
12267 // Public Variables with Default Settings
12268 //------------------------------------------------------------
12270 var margin = {top: 0, right: 0, bottom: 0, left: 0}
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
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')
12290 .size(2.2) // default size
12291 .sizeDomain([2.2,2.2]) // all the same size by default
12294 /************************************
12296 * 'wiggle' (stream)
12298 * 'expand' (normalize to 100%)
12299 * 'silhouette' (simple centered)
12302 * 'inside-out' (stream)
12303 * 'default' (input order)
12304 ************************************/
12306 //============================================================
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);
12315 //------------------------------------------------------------
12318 x = scatter.xScale();
12319 y = scatter.yScale();
12321 //------------------------------------------------------------
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) {
12329 d.stackedY = aseries.disabled ? 0 : getY(d,j);
12336 data = d3.layout.stack()
12339 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
12341 .y(function(d) { return d.stackedY })
12342 .out(function(d, y0, y) {
12351 //------------------------------------------------------------
12352 // Setup containers and skeleton of chart
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');
12360 gEnter.append('g').attr('class', 'nv-areaWrap');
12361 gEnter.append('g').attr('class', 'nv-scatterWrap');
12363 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12365 //------------------------------------------------------------
12369 .width(availableWidth)
12370 .height(availableHeight)
12372 .y(function(d) { return d.display.y + d.display.y0 })
12374 .color(data.map(function(d,i) {
12375 return d.color || color(d, i);
12376 }).filter(function(d,i) { return !data[i].disabled }));
12379 var scatterWrap = g.select('.nv-scatterWrap')
12380 .datum(data.filter(function(d) { return !d.disabled }))
12382 //d3.transition(scatterWrap).call(scatter);
12383 scatterWrap.call(scatter);
12389 defsEnter.append('clipPath')
12390 .attr('id', 'nv-edge-clip-' + id)
12393 wrap.select('#nv-edge-clip-' + id + ' rect')
12394 .attr('width', availableWidth)
12395 .attr('height', availableHeight);
12397 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
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);
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) });
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({
12423 pos: [d3.event.pageX, d3.event.pageY],
12427 .on('mouseout', function(d,i) {
12428 d3.select(this).classed('hover', false);
12429 dispatch.areaMouseout({
12432 pos: [d3.event.pageX, d3.event.pageY],
12436 .on('click', function(d,i) {
12437 d3.select(this).classed('hover', false);
12438 dispatch.areaClick({
12441 pos: [d3.event.pageX, d3.event.pageY],
12445 //d3.transition(path.exit())
12447 .attr('d', function(d,i) { return zeroArea(d.values,i) })
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)
12454 .attr('d', function(d,i) { return area(d.values,i) })
12457 //============================================================
12458 // Event Handling/Dispatching (in chart's scope)
12459 //------------------------------------------------------------
12461 scatter.dispatch.on('elementMouseover.area', function(e) {
12462 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
12464 scatter.dispatch.on('elementMouseout.area', function(e) {
12465 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
12468 //============================================================
12477 //============================================================
12478 // Event Handling/Dispatching (out of chart's scope)
12479 //------------------------------------------------------------
12481 scatter.dispatch.on('elementClick.area', function(e) {
12482 dispatch.areaClick(e);
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);
12488 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12489 dispatch.tooltipHide(e);
12492 //============================================================
12495 //============================================================
12496 // Global getters and setters
12497 //------------------------------------------------------------
12499 chart.dispatch = dispatch;
12500 chart.scatter = scatter;
12502 d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius');
12504 chart.x = function(_) {
12505 if (!arguments.length) return getX;
12506 getX = d3.functor(_);
12510 chart.y = function(_) {
12511 if (!arguments.length) return getY;
12512 getY = d3.functor(_);
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;
12525 chart.width = function(_) {
12526 if (!arguments.length) return width;
12531 chart.height = function(_) {
12532 if (!arguments.length) return height;
12537 chart.clipEdge = function(_) {
12538 if (!arguments.length) return clipEdge;
12543 chart.color = function(_) {
12544 if (!arguments.length) return color;
12545 color = nv.utils.getColor(_);
12549 chart.offset = function(_) {
12550 if (!arguments.length) return offset;
12555 chart.order = function(_) {
12556 if (!arguments.length) return order;
12561 //shortcut for offset + order
12562 chart.style = function(_) {
12563 if (!arguments.length) return style;
12568 chart.offset('zero');
12569 chart.order('default');
12572 chart.offset('wiggle');
12573 chart.order('inside-out');
12575 case 'stream-center':
12576 chart.offset('silhouette');
12577 chart.order('inside-out');
12580 chart.offset('expand');
12581 chart.order('default');
12588 chart.interpolate = function(_) {
12589 if (!arguments.length) return interpolate;
12591 return interpolate;
12595 //============================================================
12601 nv.models.stackedAreaChart = function() {
12603 //============================================================
12604 // Public Variables with Default Settings
12605 //------------------------------------------------------------
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()
12614 var margin = {top: 30, right: 25, bottom: 50, left: 60}
12617 , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
12618 , showControls = true
12619 , showLegend = true
12621 , tooltip = function(key, x, y, e, graph) {
12622 return '<h3>' + key + '</h3>' +
12623 '<p>' + y + ' on ' + x + '</p>'
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
12644 .pointActive(function(d) {
12645 //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100));
12646 return !!Math.round(stacked.y()(d) * 100);
12650 //============================================================
12653 //============================================================
12654 // Private Variables
12655 //------------------------------------------------------------
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);
12665 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
12668 //============================================================
12671 function chart(selection) {
12672 selection.each(function(data) {
12673 var container = d3.select(this),
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;
12681 chart.update = function() { container.transition().call(chart); };
12682 chart.container = this;
12684 //set state.disabled
12685 state.disabled = data.map(function(d) { return !!d.disabled });
12687 if (!defaultState) {
12690 for (key in state) {
12691 if (state[key] instanceof Array)
12692 defaultState[key] = state[key].slice(0);
12694 defaultState[key] = state[key];
12698 //------------------------------------------------------------
12699 // Display No Data message if there's nothing to show.
12701 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12702 var noDataText = container.selectAll('.nv-noData').data([noData]);
12704 noDataText.enter().append('text')
12705 .attr('class', 'nvd3 nv-noData')
12706 .attr('dy', '-.7em')
12707 .style('text-anchor', 'middle');
12710 .attr('x', margin.left + availableWidth / 2)
12711 .attr('y', margin.top + availableHeight / 2)
12712 .text(function(d) { return d });
12716 container.selectAll('.nv-noData').remove();
12719 //------------------------------------------------------------
12722 //------------------------------------------------------------
12725 x = stacked.xScale();
12726 y = stacked.yScale();
12728 //------------------------------------------------------------
12731 //------------------------------------------------------------
12732 // Setup containers and skeleton of chart
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');
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');
12744 //------------------------------------------------------------
12747 //------------------------------------------------------------
12752 .width( availableWidth - controlWidth );
12754 g.select('.nv-legendWrap')
12758 if ( margin.top != legend.height()) {
12759 margin.top = legend.height();
12760 availableHeight = (height || parseInt(container.style('height')) || 400)
12761 - margin.top - margin.bottom;
12764 g.select('.nv-legendWrap')
12765 .attr('transform', 'translate(' + controlWidth + ',' + (-margin.top) +')');
12768 //------------------------------------------------------------
12771 //------------------------------------------------------------
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' }
12782 .width( controlWidth )
12783 .color(['#444', '#444', '#444']);
12785 g.select('.nv-controlsWrap')
12786 .datum(controlsData)
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;
12797 g.select('.nv-controlsWrap')
12798 .attr('transform', 'translate(0,' + (-margin.top) +')');
12801 //------------------------------------------------------------
12804 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12807 //------------------------------------------------------------
12808 // Main Chart Component(s)
12811 .width(availableWidth)
12812 .height(availableHeight)
12814 var stackedWrap = g.select('.nv-stackedWrap')
12816 //d3.transition(stackedWrap).call(stacked);
12817 stackedWrap.call(stacked);
12819 //------------------------------------------------------------
12822 //------------------------------------------------------------
12827 .ticks( availableWidth / 100 )
12828 .tickSize( -availableHeight, 0);
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)
12839 .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
12840 .tickSize(-availableWidth, 0)
12841 .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
12843 //d3.transition(g.select('.nv-y.nv-axis'))
12844 g.select('.nv-y.nv-axis')
12845 .transition().duration(0)
12848 //------------------------------------------------------------
12851 //============================================================
12852 // Event Handling/Dispatching (in chart's scope)
12853 //------------------------------------------------------------
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;
12862 data = data.map(function(d,i) {
12863 d.disabled = (i != e.seriesIndex);
12867 state.disabled = data.map(function(d) { return !!d.disabled });
12868 dispatch.stateChange(state);
12870 //selection.transition().call(chart);
12874 legend.dispatch.on('legendClick', function(d,i) {
12875 d.disabled = !d.disabled;
12877 if (!data.filter(function(d) { return !d.disabled }).length) {
12878 data.map(function(d) {
12879 d.disabled = false;
12884 state.disabled = data.map(function(d) { return !!d.disabled });
12885 dispatch.stateChange(state);
12887 //selection.transition().call(chart);
12891 legend.dispatch.on('legendDblclick', function(d) {
12892 //Double clicking should always enable current series, and disabled all others.
12893 data.forEach(function(d) {
12896 d.disabled = false;
12898 state.disabled = data.map(function(d) { return !!d.disabled });
12899 dispatch.stateChange(state);
12903 controls.dispatch.on('legendClick', function(d,i) {
12904 if (!d.disabled) return;
12906 controlsData = controlsData.map(function(s) {
12910 d.disabled = false;
12914 stacked.style('stack');
12917 stacked.style('stream');
12920 stacked.style('expand');
12924 state.style = stacked.style();
12925 dispatch.stateChange(state);
12927 //selection.transition().call(chart);
12931 dispatch.on('tooltipShow', function(e) {
12932 if (tooltips) showTooltip(e, that.parentNode);
12935 // Update chart from a state object passed to event handler
12936 dispatch.on('changeState', function(e) {
12938 if (typeof e.disabled !== 'undefined') {
12939 data.forEach(function(series,i) {
12940 series.disabled = e.disabled[i];
12943 state.disabled = e.disabled;
12946 if (typeof e.style !== 'undefined') {
12947 stacked.style(e.style);
12960 //============================================================
12961 // Event Handling/Dispatching (out of chart's scope)
12962 //------------------------------------------------------------
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
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);
12974 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
12975 dispatch.tooltipShow(e);
12978 stacked.dispatch.on('tooltipHide', function(e) {
12979 dispatch.tooltipHide(e);
12982 dispatch.on('tooltipHide', function() {
12983 if (tooltips) nv.tooltip.cleanup();
12986 //============================================================
12989 //============================================================
12990 // Expose Public Variables
12991 //------------------------------------------------------------
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;
13001 d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
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;
13012 chart.width = function(_) {
13013 if (!arguments.length) return getWidth;
13018 chart.height = function(_) {
13019 if (!arguments.length) return getHeight;
13024 chart.color = function(_) {
13025 if (!arguments.length) return color;
13026 color = nv.utils.getColor(_);
13027 legend.color(color);
13028 stacked.color(color);
13032 chart.showControls = function(_) {
13033 if (!arguments.length) return showControls;
13038 chart.showLegend = function(_) {
13039 if (!arguments.length) return showLegend;
13044 chart.tooltip = function(_) {
13045 if (!arguments.length) return tooltip;
13050 chart.tooltips = function(_) {
13051 if (!arguments.length) return tooltips;
13056 chart.tooltipContent = function(_) {
13057 if (!arguments.length) return tooltip;
13062 chart.yAxisTooltipFormat = function(_) {
13063 if (!arguments.length) return yAxisTooltipFormat;
13064 yAxisTooltipFormat = _;
13067 chart.state = function(_) {
13068 if (!arguments.length) return state;
13073 chart.defaultState = function(_) {
13074 if (!arguments.length) return defaultState;
13079 chart.noData = function(_) {
13080 if (!arguments.length) return noData;
13085 yAxis.setTickFormat = yAxis.tickFormat;
13087 yAxis.tickFormat = function(_) {
13088 if (!arguments.length) return yAxisTickFormat;
13089 yAxisTickFormat = _;
13093 //============================================================