3 var nv = window.nv || {};
6 nv.version = '1.1.10b';
7 nv.dev = true //set false when in production
11 nv.tooltip = {}; // For the tooltip system
12 nv.utils = {}; // Utility subsystem
13 nv.models = {}; //stores all the possible models/components
14 nv.charts = {}; //stores all the ready to use charts
15 nv.graphs = []; //stores all the graphs currently on the page
16 nv.logs = {}; //stores some statistics and potential error messages
18 nv.dispatch = d3.dispatch('render_start', 'render_end');
20 // *************************************************************************
21 // Development render timers - disabled if dev = false
24 nv.dispatch.on('render_start', function(e) {
25 nv.logs.startTime = +new Date();
28 nv.dispatch.on('render_end', function(e) {
29 nv.logs.endTime = +new Date();
30 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
31 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
35 // ********************************************
36 // Public Core NV functions
38 // Logs all arguments, and returns the last so you can test things in place
39 // Note: in IE8 console.log is an object not a function, and if modernizr is used
40 // then calling Function.prototype.bind with with anything other than a function
41 // causes a TypeError to be thrown.
43 if (nv.dev && console.log && console.log.apply)
44 console.log.apply(console, arguments)
45 else if (nv.dev && typeof console.log == "function" && Function.prototype.bind) {
46 var log = Function.prototype.bind.call(console.log, console);
47 log.apply(console, arguments);
49 return arguments[arguments.length - 1];
53 nv.render = function render(step) {
54 step = step || 1; // number of graphs to generate in each timeout loop
56 nv.render.active = true;
57 nv.dispatch.render_start();
59 setTimeout(function() {
62 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
63 chart = graph.generate();
64 if (typeof graph.callback == typeof(Function)) graph.callback(chart);
65 nv.graphs.push(chart);
68 nv.render.queue.splice(0, i);
70 if (nv.render.queue.length) setTimeout(arguments.callee, 0);
71 else { nv.render.active = false; nv.dispatch.render_end(); }
75 nv.render.active = false;
78 nv.addGraph = function(obj) {
79 if (typeof arguments[0] === typeof(Function))
80 obj = {generate: arguments[0], callback: arguments[1]};
82 nv.render.queue.push(obj);
84 if (!nv.render.active) nv.render();
87 nv.identity = function(d) { return d; };
89 nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
91 function daysInMonth(month,year) {
92 return (new Date(year, month+1, 0)).getDate();
95 function d3_time_range(floor, step, number) {
96 return function(t0, t1, dt) {
97 var time = floor(t0), times = [];
98 if (time < t0) step(time);
101 var date = new Date(+time);
102 if ((number(date) % dt === 0)) times.push(date);
106 while (time < t1) { times.push(new Date(+time)); step(time); }
112 d3.time.monthEnd = function(date) {
113 return new Date(date.getFullYear(), date.getMonth(), 0);
116 d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
117 date.setUTCDate(date.getUTCDate() + 1);
118 date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
120 return date.getMonth();
124 /* Utility class to handle creation of an interactive layer.
125 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
126 containing the X-coordinate. It can also render a vertical line where the mouse is located.
128 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
129 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
130 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
132 nv.interactiveGuideline = function() {
134 var tooltip = nv.models.tooltip();
138 //Please pass in the bounding chart's top and left margins
139 //This is important for calculating the correct mouseX/Y positions.
140 , margin = {left: 0, top: 0}
141 , xScale = d3.scale.linear()
142 , yScale = d3.scale.linear()
143 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout')
144 , showGuideLine = true
145 , svgContainer = null
146 //Must pass in the bounding chart's <svg> container.
147 //The mousemove event is attached to this container.
151 var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1 //Check user-agent for Microsoft Internet Explorer.
155 function layer(selection) {
156 selection.each(function(data) {
157 var container = d3.select(this);
159 var availableWidth = (width || 960), availableHeight = (height || 400);
161 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
162 var wrapEnter = wrap.enter()
163 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
166 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
172 function mouseHandler() {
173 var d3mouse = d3.mouse(this);
174 var mouseX = d3mouse[0];
175 var mouseY = d3mouse[1];
176 var subtractMargin = true;
177 var mouseOutAnyReason = false;
180 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
181 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
182 over a rect in IE 10.
183 However, d3.event.offsetX/Y also returns the mouse coordinates
184 relative to the triggering <rect>. So we use offsetX/Y on IE.
186 mouseX = d3.event.offsetX;
187 mouseY = d3.event.offsetY;
190 On IE, if you attach a mouse event listener to the <svg> container,
191 it will actually trigger it for all the child elements (like <path>, <circle>, etc).
192 When this happens on IE, the offsetX/Y is set to where ever the child element
194 As a result, we do NOT need to subtract margins to figure out the mouse X/Y
195 position under this scenario. Removing the line below *will* cause
196 the interactive layer to not work right on IE.
198 if(d3.event.target.tagName !== "svg")
199 subtractMargin = false;
201 if (d3.event.target.className.baseVal.match("nv-legend"))
202 mouseOutAnyReason = true;
207 mouseX -= margin.left;
208 mouseY -= margin.top;
211 /* If mouseX/Y is outside of the chart's bounds,
212 trigger a mouseOut event.
214 if (mouseX < 0 || mouseY < 0
215 || mouseX > availableWidth || mouseY > availableHeight
216 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
221 if (d3.event.relatedTarget
222 && d3.event.relatedTarget.ownerSVGElement === undefined
223 && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
227 dispatch.elementMouseout({
231 layer.renderGuideLine(null); //hide the guideline
235 var pointXValue = xScale.invert(mouseX);
236 dispatch.elementMousemove({
239 pointXValue: pointXValue
244 .on("mousemove",mouseHandler, true)
245 .on("mouseout",mouseHandler,true)
248 //Draws a vertical guideline at the given X postion.
249 layer.renderGuideLine = function(x) {
250 if (!showGuideLine) return;
251 var line = wrap.select(".nv-interactiveGuideLine")
253 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
257 .attr("class", "nv-guideline")
258 .attr("x1", function(d) { return d;})
259 .attr("x2", function(d) { return d;})
260 .attr("y1", availableHeight)
263 line.exit().remove();
269 layer.dispatch = dispatch;
270 layer.tooltip = tooltip;
272 layer.margin = function(_) {
273 if (!arguments.length) return margin;
274 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
275 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
279 layer.width = function(_) {
280 if (!arguments.length) return width;
285 layer.height = function(_) {
286 if (!arguments.length) return height;
291 layer.xScale = function(_) {
292 if (!arguments.length) return xScale;
297 layer.showGuideLine = function(_) {
298 if (!arguments.length) return showGuideLine;
303 layer.svgContainer = function(_) {
304 if (!arguments.length) return svgContainer;
313 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
314 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
316 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
317 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
318 because 28 is closer to 30 than 10.
320 Unit tests can be found in: interactiveBisectTest.html
322 Has the following known issues:
323 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
324 * Won't work if there are duplicate x coordinate values.
326 nv.interactiveBisect = function (values, searchVal, xAccessor) {
328 if (! values instanceof Array) return null;
329 if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
331 var bisect = d3.bisector(xAccessor).left;
332 var index = d3.max([0, bisect(values,searchVal) - 1]);
333 var currentValue = xAccessor(values[index], index);
334 if (typeof currentValue === 'undefined') currentValue = index;
336 if (currentValue === searchVal) return index; //found exact match
338 var nextIndex = d3.min([index+1, values.length - 1]);
339 var nextValue = xAccessor(values[nextIndex], nextIndex);
340 if (typeof nextValue === 'undefined') nextValue = nextIndex;
342 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
346 };/* Tooltip rendering model for nvd3 charts.
347 window.nv.models.tooltip is the updated,new way to render tooltips.
349 window.nv.tooltip.show is the old tooltip code.
350 window.nv.tooltip.* also has various helper methods.
354 window.nv.tooltip = {};
356 /* Model which can be instantiated to handle tooltip rendering.
358 var tip = nv.models.tooltip().gravity('w').distance(23)
361 tip(); //just invoke the returned function to render tooltip.
363 window.nv.models.tooltip = function() {
364 var content = null //HTML contents of the tooltip. If null, the content is generated via the data variable.
365 , data = null /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
369 value: "August 2009",
386 , gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
387 , distance = 50 //Distance to offset tooltip from the mouse location.
388 , snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
389 , fixedTop = null //If not null, this fixes the top position of the tooltip.
390 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
391 , chartContainer = null //Parent DIV, of the SVG Container that holds the chart.
392 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
393 , enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
394 //Generates a unique id when you create a new tooltip() object
395 , id = "nvtooltip-" + Math.floor(Math.random() * 100000)
398 //CSS class to specify whether element should not have mouse events.
399 var nvPointerEventsClass = "nv-pointer-events-none";
401 //Format function for the tooltip values column
402 var valueFormatter = function(d,i) {
406 //Format function for the tooltip header value.
407 var headerFormatter = function(d) {
411 //By default, the tooltip model renders a beautiful table inside a DIV.
412 //You can override this function if a custom tooltip is desired.
413 var contentGenerator = function(d) {
414 if (content != null) return content;
416 if (d == null) return '';
418 var html = "<table><thead><tr><td colspan='3'><strong class='x-value'>" + headerFormatter(d.value) + "</strong></td></tr></thead><tbody>";
419 if (d.series instanceof Array) {
420 d.series.forEach(function(item, i) {
422 html += "<td class='legend-color-guide'><div style='background-color: " + item.color + ";'></div></td>";
423 html += "<td class='key'>" + item.key + ":</td>";
424 html += "<td class='value'>" + valueFormatter(item.value,i) + "</td></tr>";
427 html += "</tbody></table>";
431 var dataSeriesExists = function(d) {
432 if (d && d.series && d.series.length > 0) return true;
437 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
438 function convertViewBoxRatio() {
439 if (chartContainer) {
440 var svg = d3.select(chartContainer);
441 if (svg.node().tagName !== "svg") {
442 svg = svg.select("svg");
444 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
446 viewBox = viewBox.split(' ');
447 var ratio = parseInt(svg.style('width')) / viewBox[2];
449 position.left = position.left * ratio;
450 position.top = position.top * ratio;
455 //Creates new tooltip container, or uses existing one on DOM.
456 function getTooltipContainer(newContent) {
459 body = d3.select(chartContainer);
461 body = d3.select("body");
463 var container = body.select(".nvtooltip");
464 if (container.node() === null) {
465 //Create new tooltip div if it doesn't exist on DOM.
466 container = body.append("div")
467 .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
473 container.node().innerHTML = newContent;
474 container.style("top",0).style("left",0).style("opacity",0);
475 container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
476 container.classed(nvPointerEventsClass,true);
477 return container.node();
482 //Draw the tooltip onto the DOM.
483 function nvtooltip() {
484 if (!enabled) return;
485 if (!dataSeriesExists(data)) return;
487 convertViewBoxRatio();
489 var left = position.left;
490 var top = (fixedTop != null) ? fixedTop : position.top;
491 var container = getTooltipContainer(contentGenerator(data));
493 if (chartContainer) {
494 var svgComp = chartContainer.getElementsByTagName("svg")[0];
495 var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
496 var svgOffset = {left:0,top:0};
498 var svgBound = svgComp.getBoundingClientRect();
499 var chartBound = chartContainer.getBoundingClientRect();
500 svgOffset.top = Math.abs(svgBound.top - chartBound.top);
501 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
503 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
504 //You need to also add any offset between the <svg> element and its containing <div>
505 //Finally, add any offset of the containing <div> on the whole page.
506 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
507 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
510 if (snapDistance && snapDistance > 0) {
511 top = Math.floor(top/snapDistance) * snapDistance;
514 nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
518 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
520 nvtooltip.content = function(_) {
521 if (!arguments.length) return content;
526 nvtooltip.contentGenerator = function(_) {
527 if (!arguments.length) return contentGenerator;
528 if (typeof _ === 'function') {
529 contentGenerator = _;
534 nvtooltip.data = function(_) {
535 if (!arguments.length) return data;
540 nvtooltip.gravity = function(_) {
541 if (!arguments.length) return gravity;
546 nvtooltip.distance = function(_) {
547 if (!arguments.length) return distance;
552 nvtooltip.snapDistance = function(_) {
553 if (!arguments.length) return snapDistance;
558 nvtooltip.classes = function(_) {
559 if (!arguments.length) return classes;
564 nvtooltip.chartContainer = function(_) {
565 if (!arguments.length) return chartContainer;
570 nvtooltip.position = function(_) {
571 if (!arguments.length) return position;
572 position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
573 position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
577 nvtooltip.fixedTop = function(_) {
578 if (!arguments.length) return fixedTop;
583 nvtooltip.enabled = function(_) {
584 if (!arguments.length) return enabled;
589 nvtooltip.valueFormatter = function(_) {
590 if (!arguments.length) return valueFormatter;
591 if (typeof _ === 'function') {
597 nvtooltip.headerFormatter = function(_) {
598 if (!arguments.length) return headerFormatter;
599 if (typeof _ === 'function') {
605 //id() is a read-only function. You can't use it to set the id.
606 nvtooltip.id = function() {
615 //Original tooltip.show function. Kept for backward compatibility.
617 nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
619 //Create new tooltip div if it doesn't exist on DOM.
620 var container = document.createElement('div');
621 container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
623 var body = parentContainer;
624 if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
625 //If the parent element is an SVG element, place tooltip in the <body> element.
626 body = document.getElementsByTagName('body')[0];
629 container.style.left = 0;
630 container.style.top = 0;
631 container.style.opacity = 0;
632 container.innerHTML = content;
633 body.appendChild(container);
635 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
636 if (parentContainer) {
637 pos[0] = pos[0] - parentContainer.scrollLeft;
638 pos[1] = pos[1] - parentContainer.scrollTop;
640 nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
643 //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
644 nv.tooltip.findFirstNonSVGParent = function(Elem) {
645 while(Elem.tagName.match(/^g|svg$/i) !== null) {
646 Elem = Elem.parentNode;
651 //Finds the total offsetTop of a given DOM element.
652 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
653 nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
654 var offsetTop = initialTop;
657 if( !isNaN( Elem.offsetTop ) ) {
658 offsetTop += (Elem.offsetTop);
660 } while( Elem = Elem.offsetParent );
664 //Finds the total offsetLeft of a given DOM element.
665 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
666 nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
667 var offsetLeft = initialLeft;
670 if( !isNaN( Elem.offsetLeft ) ) {
671 offsetLeft += (Elem.offsetLeft);
673 } while( Elem = Elem.offsetParent );
677 //Global utility function to render a tooltip on the DOM.
678 //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
679 //gravity = how to orient the tooltip
680 //dist = how far away from the mouse to place tooltip
681 //container = tooltip DIV
682 nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
684 var height = parseInt(container.offsetHeight),
685 width = parseInt(container.offsetWidth),
686 windowWidth = nv.utils.windowSize().width,
687 windowHeight = nv.utils.windowSize().height,
688 scrollTop = window.pageYOffset,
689 scrollLeft = window.pageXOffset,
692 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
693 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
695 gravity = gravity || 's';
698 var tooltipTop = function ( Elem ) {
699 return nv.tooltip.findTotalOffsetTop(Elem, top);
702 var tooltipLeft = function ( Elem ) {
703 return nv.tooltip.findTotalOffsetLeft(Elem,left);
708 left = pos[0] - width - dist;
709 top = pos[1] - (height / 2);
710 var tLeft = tooltipLeft(container);
711 var tTop = tooltipTop(container);
712 if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
713 if (tTop < scrollTop) top = scrollTop - tTop + top;
714 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
717 left = pos[0] + dist;
718 top = pos[1] - (height / 2);
719 var tLeft = tooltipLeft(container);
720 var tTop = tooltipTop(container);
721 if (tLeft + width > windowWidth) left = pos[0] - width - dist;
722 if (tTop < scrollTop) top = scrollTop + 5;
723 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
726 left = pos[0] - (width / 2) - 5;
728 var tLeft = tooltipLeft(container);
729 var tTop = tooltipTop(container);
730 if (tLeft < scrollLeft) left = scrollLeft + 5;
731 if (tLeft + width > windowWidth) left = left - width/2 + 5;
732 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
735 left = pos[0] - (width / 2);
736 top = pos[1] - height - dist;
737 var tLeft = tooltipLeft(container);
738 var tTop = tooltipTop(container);
739 if (tLeft < scrollLeft) left = scrollLeft + 5;
740 if (tLeft + width > windowWidth) left = left - width/2 + 5;
741 if (scrollTop > tTop) top = scrollTop;
746 var tLeft = tooltipLeft(container);
747 var tTop = tooltipTop(container);
752 container.style.left = left+'px';
753 container.style.top = top+'px';
754 container.style.opacity = 1;
755 container.style.position = 'absolute';
760 //Global utility function to remove tooltips from the DOM.
761 nv.tooltip.cleanup = function() {
763 // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
764 var tooltips = document.getElementsByClassName('nvtooltip');
766 while(tooltips.length) {
767 purging.push(tooltips[0]);
768 tooltips[0].style.transitionDelay = '0 !important';
769 tooltips[0].style.opacity = 0;
770 tooltips[0].className = 'nvtooltip-pending-removal';
773 setTimeout(function() {
775 while (purging.length) {
776 var removeMe = purging.pop();
777 removeMe.parentNode.removeChild(removeMe);
784 nv.utils.windowSize = function() {
786 var size = {width: 640, height: 480};
788 // Earlier IE uses Doc.body
789 if (document.body && document.body.offsetWidth) {
790 size.width = document.body.offsetWidth;
791 size.height = document.body.offsetHeight;
794 // IE can use depending on mode it is in
795 if (document.compatMode=='CSS1Compat' &&
796 document.documentElement &&
797 document.documentElement.offsetWidth ) {
798 size.width = document.documentElement.offsetWidth;
799 size.height = document.documentElement.offsetHeight;
802 // Most recent browsers use
803 if (window.innerWidth && window.innerHeight) {
804 size.width = window.innerWidth;
805 size.height = window.innerHeight;
812 // Easy way to bind multiple functions to window.onresize
813 // TODO: give a way to remove a function after its bound, other than removing all of them
814 nv.utils.windowResize = function(fun){
815 if (fun === undefined) return;
816 var oldresize = window.onresize;
818 window.onresize = function(e) {
819 if (typeof oldresize == 'function') oldresize(e);
824 // Backwards compatible way to implement more d3-like coloring of graphs.
825 // If passed an array, wrap it in a function which implements the old default
827 nv.utils.getColor = function(color) {
828 if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
830 if( Object.prototype.toString.call( color ) === '[object Array]' )
831 return function(d, i) { return d.color || color[i % color.length]; };
834 //can't really help it if someone passes rubbish as color
837 // Default color chooser uses the index of an object as before.
838 nv.utils.defaultColor = function() {
839 var colors = d3.scale.category20().range();
840 return function(d, i) { return d.color || colors[i % colors.length] };
844 // Returns a color function that takes the result of 'getKey' for each series and
845 // looks for a corresponding color from the dictionary,
846 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
847 getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
848 defaultColors = defaultColors || d3.scale.category20().range(); //default color function
850 var defIndex = defaultColors.length; //current default color (going in reverse)
852 return function(series, index) {
853 var key = getKey(series);
855 if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
857 if (typeof dictionary[key] !== "undefined")
858 return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
860 return defaultColors[--defIndex]; // no match in dictionary, use default color
866 // From the PJAX example on d3js.org, while this is not really directly needed
867 // it's a very cool method for doing pjax, I may expand upon it a little bit,
868 // open to suggestions on anything that may be useful
869 nv.utils.pjax = function(links, content) {
870 d3.selectAll(links).on("click", function() {
871 history.pushState(this.href, this.textContent, this.href);
873 d3.event.preventDefault();
876 function load(href) {
877 d3.html(href, function(fragment) {
878 var target = d3.select(content).node();
879 target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
880 nv.utils.pjax(links, content);
884 d3.select(window).on("popstate", function() {
885 if (d3.event.state) load(d3.event.state);
889 /* For situations where we want to approximate the width in pixels for an SVG:text element.
890 Most common instance is when the element is in a display:none; container.
891 Forumla is : text.length * font-size * constant_factor
893 nv.utils.calcApproxTextWidth = function (svgTextElem) {
894 if (svgTextElem instanceof d3.selection) {
895 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
896 var textLength = svgTextElem.text().length;
898 return textLength * fontSize * 0.5;
903 /* Numbers that are undefined, null or NaN, convert them to zeros.
905 nv.utils.NaNtoZero = function(n) {
906 if (typeof n !== 'number'
909 || n === Infinity) return 0;
915 Snippet of code you can insert into each nv.models.* to give you the ability to
922 To enable in the chart:
923 chart.options = nv.utils.optionsFunc.bind(chart);
925 nv.utils.optionsFunc = function(args) {
927 d3.map(args).forEach((function(key,value) {
928 if (typeof this[key] === "function") {
934 };nv.models.axis = function() {
936 //============================================================
937 // Public Variables with Default Settings
938 //------------------------------------------------------------
940 var axis = d3.svg.axis()
943 var margin = {top: 0, right: 0, bottom: 0, left: 0}
944 , width = 75 //only used for tickLabel currently
945 , height = 60 //only used for tickLabel currently
946 , scale = d3.scale.linear()
947 , axisLabelText = null
948 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
949 , highlightZero = true
951 , rotateYLabel = true
952 , staggerLabels = false
960 .tickFormat(function(d) { return d })
963 //============================================================
966 //============================================================
968 //------------------------------------------------------------
972 //============================================================
975 function chart(selection) {
976 selection.each(function(data) {
977 var container = d3.select(this);
980 //------------------------------------------------------------
981 // Setup containers and skeleton of chart
983 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
984 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
985 var gEnter = wrapEnter.append('g');
986 var g = wrap.select('g')
988 //------------------------------------------------------------
993 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
994 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
997 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1000 g.transition().call(axis);
1002 scale0 = scale0 || axis.scale();
1004 var fmt = axis.tickFormat();
1006 fmt = scale0.tickFormat();
1009 var axisLabel = g.selectAll('text.nv-axislabel')
1010 .data([axisLabelText || null]);
1011 axisLabel.exit().remove();
1012 switch (axis.orient()) {
1014 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1015 var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1017 .attr('text-anchor', 'middle')
1021 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1022 .data(scale.domain());
1023 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1024 axisMaxMin.exit().remove();
1026 .attr('transform', function(d,i) {
1027 return 'translate(' + scale(d) + ',0)'
1031 .attr('y', -axis.tickPadding())
1032 .attr('text-anchor', 'middle')
1033 .text(function(d,i) {
1035 return ('' + v).match('NaN') ? '' : v;
1037 axisMaxMin.transition()
1038 .attr('transform', function(d,i) {
1039 return 'translate(' + scale.range()[i] + ',0)'
1044 var xLabelMargin = 36;
1045 var maxTextWidth = 30;
1046 var xTicks = g.selectAll('g').select("text");
1047 if (rotateLabels%360) {
1048 //Calculate the longest xTick width
1049 xTicks.each(function(d,i){
1050 var width = this.getBBox().width;
1051 if(width > maxTextWidth) maxTextWidth = width;
1053 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1054 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1055 var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1058 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1059 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1061 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1062 var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1064 .attr('text-anchor', 'middle')
1065 .attr('y', xLabelMargin)
1068 //if (showMaxMin && !isOrdinal) {
1069 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1070 //.data(scale.domain())
1071 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1072 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1073 axisMaxMin.exit().remove();
1075 .attr('transform', function(d,i) {
1076 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1079 .attr('dy', '.71em')
1080 .attr('y', axis.tickPadding())
1081 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1082 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1083 .text(function(d,i) {
1085 return ('' + v).match('NaN') ? '' : v;
1087 axisMaxMin.transition()
1088 .attr('transform', function(d,i) {
1089 //return 'translate(' + scale.range()[i] + ',0)'
1090 //return 'translate(' + scale(d) + ',0)'
1091 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1096 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1100 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1102 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1103 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1104 .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1105 .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1107 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1108 .data(scale.domain());
1109 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1110 .style('opacity', 0);
1111 axisMaxMin.exit().remove();
1113 .attr('transform', function(d,i) {
1114 return 'translate(0,' + scale(d) + ')'
1117 .attr('dy', '.32em')
1119 .attr('x', axis.tickPadding())
1120 .style('text-anchor', 'start')
1121 .text(function(d,i) {
1123 return ('' + v).match('NaN') ? '' : v;
1125 axisMaxMin.transition()
1126 .attr('transform', function(d,i) {
1127 return 'translate(0,' + scale.range()[i] + ')'
1130 .style('opacity', 1);
1135 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1136 var yTicks = g.selectAll('g').select("text");
1137 yTicks.each(function(d,i){
1138 var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
1139 if(labelPadding > width) width = labelPadding;
1142 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1144 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1145 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1146 .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1147 .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1149 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1150 .data(scale.domain());
1151 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1152 .style('opacity', 0);
1153 axisMaxMin.exit().remove();
1155 .attr('transform', function(d,i) {
1156 return 'translate(0,' + scale0(d) + ')'
1159 .attr('dy', '.32em')
1161 .attr('x', -axis.tickPadding())
1162 .attr('text-anchor', 'end')
1163 .text(function(d,i) {
1165 return ('' + v).match('NaN') ? '' : v;
1167 axisMaxMin.transition()
1168 .attr('transform', function(d,i) {
1169 return 'translate(0,' + scale.range()[i] + ')'
1172 .style('opacity', 1);
1177 .text(function(d) { return d });
1180 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1181 //check if max and min overlap other values, if so, hide the values that overlap
1182 g.selectAll('g') // the g's wrapping each tick
1183 .each(function(d,i) {
1184 d3.select(this).select('text').attr('opacity', 1);
1185 if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
1186 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1187 d3.select(this).attr('opacity', 0);
1189 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1193 //if Max and Min = 0 only show min, Issue #281
1194 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1195 wrap.selectAll('g.nv-axisMaxMin')
1196 .style('opacity', function(d,i) { return !i ? 1 : 0 });
1200 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1201 var maxMinRange = [];
1202 wrap.selectAll('g.nv-axisMaxMin')
1203 .each(function(d,i) {
1205 if (i) // i== 1, max position
1206 maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
1207 else // i==0, min position
1208 maxMinRange.push(scale(d) + this.getBBox().width + 4)
1210 if (i) // i== 1, max position
1211 maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
1212 else // i==0, min position
1213 maxMinRange.push(scale(d) + 4)
1216 g.selectAll('g') // the g's wrapping each tick
1217 .each(function(d,i) {
1218 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1219 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1220 d3.select(this).remove();
1222 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1228 //highlight zero line ... Maybe should not be an option and should just be in CSS?
1230 g.selectAll('.tick')
1231 .filter(function(d) { return !parseFloat(Math.round(d.__data__*100000)/1000000) && (d.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1232 .classed('zero', true);
1234 //store old scales for use in transitions on update
1235 scale0 = scale.copy();
1243 //============================================================
1244 // Expose Public Variables
1245 //------------------------------------------------------------
1247 // expose chart's sub-components
1250 d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1251 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1253 chart.options = nv.utils.optionsFunc.bind(chart);
1255 chart.margin = function(_) {
1256 if(!arguments.length) return margin;
1257 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1258 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1259 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1260 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1264 chart.width = function(_) {
1265 if (!arguments.length) return width;
1270 chart.ticks = function(_) {
1271 if (!arguments.length) return ticks;
1276 chart.height = function(_) {
1277 if (!arguments.length) return height;
1282 chart.axisLabel = function(_) {
1283 if (!arguments.length) return axisLabelText;
1288 chart.showMaxMin = function(_) {
1289 if (!arguments.length) return showMaxMin;
1294 chart.highlightZero = function(_) {
1295 if (!arguments.length) return highlightZero;
1300 chart.scale = function(_) {
1301 if (!arguments.length) return scale;
1304 isOrdinal = typeof scale.rangeBands === 'function';
1305 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1309 chart.rotateYLabel = function(_) {
1310 if(!arguments.length) return rotateYLabel;
1315 chart.rotateLabels = function(_) {
1316 if(!arguments.length) return rotateLabels;
1321 chart.staggerLabels = function(_) {
1322 if (!arguments.length) return staggerLabels;
1327 //============================================================
1333 // Chart design based on the recommendations of Stephen Few. Implementation
1334 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1335 // http://projects.instantcognition.com/protovis/bulletchart/
1337 nv.models.bullet = function() {
1339 //============================================================
1340 // Public Variables with Default Settings
1341 //------------------------------------------------------------
1343 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1344 , orient = 'left' // TODO top & bottom
1346 , ranges = function(d) { return d.ranges }
1347 , markers = function(d) { return d.markers }
1348 , measures = function(d) { return d.measures }
1349 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1350 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
1351 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
1352 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1356 , color = nv.utils.getColor(['#1f77b4'])
1357 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1360 //============================================================
1363 function chart(selection) {
1364 selection.each(function(d, i) {
1365 var availableWidth = width - margin.left - margin.right,
1366 availableHeight = height - margin.top - margin.bottom,
1367 container = d3.select(this);
1369 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1370 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1371 measurez = measures.call(this, d, i).slice().sort(d3.descending),
1372 rangeLabelz = rangeLabels.call(this, d, i).slice(),
1373 markerLabelz = markerLabels.call(this, d, i).slice(),
1374 measureLabelz = measureLabels.call(this, d, i).slice();
1377 //------------------------------------------------------------
1380 // Compute the new x-scale.
1381 var x1 = d3.scale.linear()
1382 .domain( d3.extent(d3.merge([forceX, rangez])) )
1383 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1385 // Retrieve the old x-scale, if this is an update.
1386 var x0 = this.__chart__ || d3.scale.linear()
1387 .domain([0, Infinity])
1390 // Stash the new scale.
1391 this.__chart__ = x1;
1394 var rangeMin = d3.min(rangez), //rangez[2]
1395 rangeMax = d3.max(rangez), //rangez[0]
1396 rangeAvg = rangez[1];
1398 //------------------------------------------------------------
1401 //------------------------------------------------------------
1402 // Setup containers and skeleton of chart
1404 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1405 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1406 var gEnter = wrapEnter.append('g');
1407 var g = wrap.select('g');
1409 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1410 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1411 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1412 gEnter.append('rect').attr('class', 'nv-measure');
1413 gEnter.append('path').attr('class', 'nv-markerTriangle');
1415 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1417 //------------------------------------------------------------
1421 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1422 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1423 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1424 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1427 g.select('rect.nv-rangeMax')
1428 .attr('height', availableHeight)
1429 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1430 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1431 .datum(rangeMax > 0 ? rangeMax : rangeMin)
1433 .attr('x', rangeMin < 0 ?
1440 g.select('rect.nv-rangeAvg')
1441 .attr('height', availableHeight)
1442 .attr('width', w1(rangeAvg))
1443 .attr('x', xp1(rangeAvg))
1446 .attr('width', rangeMax <= 0 ?
1447 x1(rangeMax) - x1(rangeAvg)
1448 : x1(rangeAvg) - x1(rangeMin))
1449 .attr('x', rangeMax <= 0 ?
1454 g.select('rect.nv-rangeMin')
1455 .attr('height', availableHeight)
1456 .attr('width', w1(rangeMax))
1457 .attr('x', xp1(rangeMax))
1458 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1459 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1460 .datum(rangeMax > 0 ? rangeMin : rangeMax)
1462 .attr('width', rangeMax <= 0 ?
1463 x1(rangeAvg) - x1(rangeMin)
1464 : x1(rangeMax) - x1(rangeAvg))
1465 .attr('x', rangeMax <= 0 ?
1470 g.select('rect.nv-measure')
1471 .style('fill', color)
1472 .attr('height', availableHeight / 3)
1473 .attr('y', availableHeight / 3)
1474 .attr('width', measurez < 0 ?
1475 x1(0) - x1(measurez[0])
1476 : x1(measurez[0]) - x1(0))
1477 .attr('x', xp1(measurez))
1478 .on('mouseover', function() {
1479 dispatch.elementMouseover({
1481 label: measureLabelz[0] || 'Current',
1482 pos: [x1(measurez[0]), availableHeight/2]
1485 .on('mouseout', function() {
1486 dispatch.elementMouseout({
1488 label: measureLabelz[0] || 'Current'
1492 var h3 = availableHeight / 6;
1494 g.selectAll('path.nv-markerTriangle')
1495 .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1496 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1497 .on('mouseover', function() {
1498 dispatch.elementMouseover({
1500 label: markerLabelz[0] || 'Previous',
1501 pos: [x1(markerz[0]), availableHeight/2]
1504 .on('mouseout', function() {
1505 dispatch.elementMouseout({
1507 label: markerLabelz[0] || 'Previous'
1511 g.selectAll('path.nv-markerTriangle').remove();
1515 wrap.selectAll('.nv-range')
1516 .on('mouseover', function(d,i) {
1517 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1519 dispatch.elementMouseover({
1522 pos: [x1(d), availableHeight/2]
1525 .on('mouseout', function(d,i) {
1526 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1528 dispatch.elementMouseout({
1534 /* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
1535 // Update the range rects.
1536 var range = g.selectAll('rect.nv-range')
1539 range.enter().append('rect')
1540 .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
1542 .attr('height', availableHeight)
1543 .attr('x', reverse ? x0 : 0)
1544 .on('mouseover', function(d,i) {
1545 dispatch.elementMouseover({
1547 label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
1548 pos: [x1(d), availableHeight/2]
1551 .on('mouseout', function(d,i) {
1552 dispatch.elementMouseout({
1554 label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
1558 d3.transition(range)
1559 .attr('x', reverse ? x1 : 0)
1561 .attr('height', availableHeight);
1564 // Update the measure rects.
1565 var measure = g.selectAll('rect.nv-measure')
1568 measure.enter().append('rect')
1569 .attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
1570 .style('fill', function(d,i) { return color(d,i ) })
1572 .attr('height', availableHeight / 3)
1573 .attr('x', reverse ? x0 : 0)
1574 .attr('y', availableHeight / 3)
1575 .on('mouseover', function(d) {
1576 dispatch.elementMouseover({
1578 label: 'Current', //TODO: make these labels a variable
1579 pos: [x1(d), availableHeight/2]
1582 .on('mouseout', function(d) {
1583 dispatch.elementMouseout({
1585 label: 'Current' //TODO: make these labels a variable
1589 d3.transition(measure)
1591 .attr('height', availableHeight / 3)
1592 .attr('x', reverse ? x1 : 0)
1593 .attr('y', availableHeight / 3);
1597 // Update the marker lines.
1598 var marker = g.selectAll('path.nv-markerTriangle')
1601 var h3 = availableHeight / 6;
1602 marker.enter().append('path')
1603 .attr('class', 'nv-markerTriangle')
1604 .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
1605 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1606 .on('mouseover', function(d,i) {
1607 dispatch.elementMouseover({
1610 pos: [x1(d), availableHeight/2]
1613 .on('mouseout', function(d,i) {
1614 dispatch.elementMouseout({
1620 d3.transition(marker)
1621 .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
1623 marker.exit().remove();
1628 // d3.timer.flush(); // Not needed?
1634 //============================================================
1635 // Expose Public Variables
1636 //------------------------------------------------------------
1638 chart.dispatch = dispatch;
1640 chart.options = nv.utils.optionsFunc.bind(chart);
1642 // left, right, top, bottom
1643 chart.orient = function(_) {
1644 if (!arguments.length) return orient;
1646 reverse = orient == 'right' || orient == 'bottom';
1650 // ranges (bad, satisfactory, good)
1651 chart.ranges = function(_) {
1652 if (!arguments.length) return ranges;
1657 // markers (previous, goal)
1658 chart.markers = function(_) {
1659 if (!arguments.length) return markers;
1664 // measures (actual, forecast)
1665 chart.measures = function(_) {
1666 if (!arguments.length) return measures;
1671 chart.forceX = function(_) {
1672 if (!arguments.length) return forceX;
1677 chart.width = function(_) {
1678 if (!arguments.length) return width;
1683 chart.height = function(_) {
1684 if (!arguments.length) return height;
1689 chart.margin = function(_) {
1690 if (!arguments.length) return margin;
1691 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1692 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1693 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1694 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1698 chart.tickFormat = function(_) {
1699 if (!arguments.length) return tickFormat;
1704 chart.color = function(_) {
1705 if (!arguments.length) return color;
1706 color = nv.utils.getColor(_);
1710 //============================================================
1718 // Chart design based on the recommendations of Stephen Few. Implementation
1719 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1720 // http://projects.instantcognition.com/protovis/bulletchart/
1721 nv.models.bulletChart = function() {
1723 //============================================================
1724 // Public Variables with Default Settings
1725 //------------------------------------------------------------
1727 var bullet = nv.models.bullet()
1730 var orient = 'left' // TODO top & bottom
1732 , margin = {top: 5, right: 40, bottom: 20, left: 120}
1733 , ranges = function(d) { return d.ranges }
1734 , markers = function(d) { return d.markers }
1735 , measures = function(d) { return d.measures }
1740 , tooltip = function(key, x, y, e, graph) {
1741 return '<h3>' + x + '</h3>' +
1744 , noData = 'No Data Available.'
1745 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
1748 //============================================================
1751 //============================================================
1752 // Private Variables
1753 //------------------------------------------------------------
1755 var showTooltip = function(e, offsetElement) {
1756 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
1757 top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
1758 content = tooltip(e.key, e.label, e.value, e, chart);
1760 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
1763 //============================================================
1766 function chart(selection) {
1767 selection.each(function(d, i) {
1768 var container = d3.select(this);
1770 var availableWidth = (width || parseInt(container.style('width')) || 960)
1771 - margin.left - margin.right,
1772 availableHeight = height - margin.top - margin.bottom,
1776 chart.update = function() { chart(selection) };
1777 chart.container = this;
1779 //------------------------------------------------------------
1780 // Display No Data message if there's nothing to show.
1782 if (!d || !ranges.call(this, d, i)) {
1783 var noDataText = container.selectAll('.nv-noData').data([noData]);
1785 noDataText.enter().append('text')
1786 .attr('class', 'nvd3 nv-noData')
1787 .attr('dy', '-.7em')
1788 .style('text-anchor', 'middle');
1791 .attr('x', margin.left + availableWidth / 2)
1792 .attr('y', 18 + margin.top + availableHeight / 2)
1793 .text(function(d) { return d });
1797 container.selectAll('.nv-noData').remove();
1800 //------------------------------------------------------------
1804 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1805 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1806 measurez = measures.call(this, d, i).slice().sort(d3.descending);
1809 //------------------------------------------------------------
1810 // Setup containers and skeleton of chart
1812 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
1813 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
1814 var gEnter = wrapEnter.append('g');
1815 var g = wrap.select('g');
1817 gEnter.append('g').attr('class', 'nv-bulletWrap');
1818 gEnter.append('g').attr('class', 'nv-titles');
1820 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1822 //------------------------------------------------------------
1825 // Compute the new x-scale.
1826 var x1 = d3.scale.linear()
1827 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
1828 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1830 // Retrieve the old x-scale, if this is an update.
1831 var x0 = this.__chart__ || d3.scale.linear()
1832 .domain([0, Infinity])
1835 // Stash the new scale.
1836 this.__chart__ = x1;
1839 // Derive width-scales from the x-scales.
1840 var w0 = bulletWidth(x0),
1841 w1 = bulletWidth(x1);
1843 function bulletWidth(x) {
1845 return function(d) {
1846 return Math.abs(x(d) - x(0));
1850 function bulletTranslate(x) {
1851 return function(d) {
1852 return 'translate(' + x(d) + ',0)';
1857 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1858 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1861 var title = gEnter.select('.nv-titles').append('g')
1862 .attr('text-anchor', 'end')
1863 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
1864 title.append('text')
1865 .attr('class', 'nv-title')
1866 .text(function(d) { return d.title; });
1868 title.append('text')
1869 .attr('class', 'nv-subtitle')
1871 .text(function(d) { return d.subtitle; });
1876 .width(availableWidth)
1877 .height(availableHeight)
1879 var bulletWrap = g.select('.nv-bulletWrap');
1881 d3.transition(bulletWrap).call(bullet);
1885 // Compute the tick format.
1886 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
1888 // Update the tick groups.
1889 var tick = g.selectAll('g.nv-tick')
1890 .data(x1.ticks( availableWidth / 50 ), function(d) {
1891 return this.textContent || format(d);
1894 // Initialize the ticks with the old scale, x0.
1895 var tickEnter = tick.enter().append('g')
1896 .attr('class', 'nv-tick')
1897 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
1898 .style('opacity', 1e-6);
1900 tickEnter.append('line')
1901 .attr('y1', availableHeight)
1902 .attr('y2', availableHeight * 7 / 6);
1904 tickEnter.append('text')
1905 .attr('text-anchor', 'middle')
1907 .attr('y', availableHeight * 7 / 6)
1911 // Transition the updating ticks to the new scale, x1.
1912 var tickUpdate = d3.transition(tick)
1913 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1914 .style('opacity', 1);
1916 tickUpdate.select('line')
1917 .attr('y1', availableHeight)
1918 .attr('y2', availableHeight * 7 / 6);
1920 tickUpdate.select('text')
1921 .attr('y', availableHeight * 7 / 6);
1923 // Transition the exiting ticks to the new scale, x1.
1924 d3.transition(tick.exit())
1925 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1926 .style('opacity', 1e-6)
1930 //============================================================
1931 // Event Handling/Dispatching (in chart's scope)
1932 //------------------------------------------------------------
1934 dispatch.on('tooltipShow', function(e) {
1936 if (tooltips) showTooltip(e, that.parentNode);
1939 //============================================================
1949 //============================================================
1950 // Event Handling/Dispatching (out of chart's scope)
1951 //------------------------------------------------------------
1953 bullet.dispatch.on('elementMouseover.tooltip', function(e) {
1954 dispatch.tooltipShow(e);
1957 bullet.dispatch.on('elementMouseout.tooltip', function(e) {
1958 dispatch.tooltipHide(e);
1961 dispatch.on('tooltipHide', function() {
1962 if (tooltips) nv.tooltip.cleanup();
1965 //============================================================
1968 //============================================================
1969 // Expose Public Variables
1970 //------------------------------------------------------------
1972 chart.dispatch = dispatch;
1973 chart.bullet = bullet;
1975 d3.rebind(chart, bullet, 'color');
1977 chart.options = nv.utils.optionsFunc.bind(chart);
1979 // left, right, top, bottom
1980 chart.orient = function(x) {
1981 if (!arguments.length) return orient;
1983 reverse = orient == 'right' || orient == 'bottom';
1987 // ranges (bad, satisfactory, good)
1988 chart.ranges = function(x) {
1989 if (!arguments.length) return ranges;
1994 // markers (previous, goal)
1995 chart.markers = function(x) {
1996 if (!arguments.length) return markers;
2001 // measures (actual, forecast)
2002 chart.measures = function(x) {
2003 if (!arguments.length) return measures;
2008 chart.width = function(x) {
2009 if (!arguments.length) return width;
2014 chart.height = function(x) {
2015 if (!arguments.length) return height;
2020 chart.margin = function(_) {
2021 if (!arguments.length) return margin;
2022 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2023 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2024 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2025 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2029 chart.tickFormat = function(x) {
2030 if (!arguments.length) return tickFormat;
2035 chart.tooltips = function(_) {
2036 if (!arguments.length) return tooltips;
2041 chart.tooltipContent = function(_) {
2042 if (!arguments.length) return tooltip;
2047 chart.noData = function(_) {
2048 if (!arguments.length) return noData;
2053 //============================================================
2061 nv.models.cumulativeLineChart = function() {
2063 //============================================================
2064 // Public Variables with Default Settings
2065 //------------------------------------------------------------
2067 var lines = nv.models.line()
2068 , xAxis = nv.models.axis()
2069 , yAxis = nv.models.axis()
2070 , legend = nv.models.legend()
2071 , controls = nv.models.legend()
2072 , interactiveLayer = nv.interactiveGuideline()
2075 var margin = {top: 30, right: 30, bottom: 50, left: 60}
2076 , color = nv.utils.defaultColor()
2082 , rightAlignYAxis = false
2084 , showControls = true
2085 , useInteractiveGuideline = false
2087 , tooltip = function(key, x, y, e, graph) {
2088 return '<h3>' + key + '</h3>' +
2089 '<p>' + y + ' at ' + x + '</p>'
2091 , x //can be accessed via chart.xScale()
2092 , y //can be accessed via chart.yScale()
2094 , state = { index: 0, rescaleY: rescaleY }
2095 , defaultState = null
2096 , noData = 'No Data Available.'
2097 , average = function(d) { return d.average }
2098 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
2099 , transitionDuration = 250
2107 .orient((rightAlignYAxis) ? 'right' : 'left')
2110 //============================================================
2111 controls.updateState(false);
2113 //============================================================
2114 // Private Variables
2115 //------------------------------------------------------------
2117 var dx = d3.scale.linear()
2118 , index = {i: 0, x: 0}
2121 var showTooltip = function(e, offsetElement) {
2122 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2123 top = e.pos[1] + ( offsetElement.offsetTop || 0),
2124 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2125 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2126 content = tooltip(e.series.key, x, y, e, chart);
2128 nv.tooltip.show([left, top], content, null, null, offsetElement);
2132 //Moved to see if we can get better behavior to fix issue #315
2133 var indexDrag = d3.behavior.drag()
2134 .on('dragstart', dragStart)
2135 .on('drag', dragMove)
2136 .on('dragend', dragEnd);
2138 function dragStart(d,i) {
2139 d3.select(chart.container)
2140 .style('cursor', 'ew-resize');
2143 function dragMove(d,i) {
2145 d.i = Math.round(dx.invert(d.x));
2147 d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
2151 function dragEnd(d,i) {
2152 d3.select(chart.container)
2153 .style('cursor', 'auto');
2158 //============================================================
2161 function chart(selection) {
2162 selection.each(function(data) {
2163 var container = d3.select(this).classed('nv-chart-' + id, true),
2166 var availableWidth = (width || parseInt(container.style('width')) || 960)
2167 - margin.left - margin.right,
2168 availableHeight = (height || parseInt(container.style('height')) || 400)
2169 - margin.top - margin.bottom;
2172 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
2173 chart.container = this;
2175 //set state.disabled
2176 state.disabled = data.map(function(d) { return !!d.disabled });
2178 if (!defaultState) {
2181 for (key in state) {
2182 if (state[key] instanceof Array)
2183 defaultState[key] = state[key].slice(0);
2185 defaultState[key] = state[key];
2189 var indexDrag = d3.behavior.drag()
2190 .on('dragstart', dragStart)
2191 .on('drag', dragMove)
2192 .on('dragend', dragEnd);
2195 function dragStart(d,i) {
2196 d3.select(chart.container)
2197 .style('cursor', 'ew-resize');
2200 function dragMove(d,i) {
2201 index.x = d3.event.x;
2202 index.i = Math.round(dx.invert(index.x));
2206 function dragEnd(d,i) {
2207 d3.select(chart.container)
2208 .style('cursor', 'auto');
2210 // update state and send stateChange with new index
2211 state.index = index.i;
2212 dispatch.stateChange(state);
2215 //------------------------------------------------------------
2216 // Display No Data message if there's nothing to show.
2218 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2219 var noDataText = container.selectAll('.nv-noData').data([noData]);
2221 noDataText.enter().append('text')
2222 .attr('class', 'nvd3 nv-noData')
2223 .attr('dy', '-.7em')
2224 .style('text-anchor', 'middle');
2227 .attr('x', margin.left + availableWidth / 2)
2228 .attr('y', margin.top + availableHeight / 2)
2229 .text(function(d) { return d });
2233 container.selectAll('.nv-noData').remove();
2236 //------------------------------------------------------------
2239 //------------------------------------------------------------
2247 var seriesDomains = data
2248 .filter(function(series) { return !series.disabled })
2249 .map(function(series,i) {
2250 var initialDomain = d3.extent(series.values, lines.y());
2252 //account for series being disabled when losing 95% or more
2253 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2256 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2257 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2261 var completeDomain = [
2262 d3.min(seriesDomains, function(d) { return d[0] }),
2263 d3.max(seriesDomains, function(d) { return d[1] })
2266 lines.yDomain(completeDomain);
2268 lines.yDomain(null);
2272 dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2273 .range([0, availableWidth])
2276 //------------------------------------------------------------
2279 var data = indexify(index.i, data);
2282 //------------------------------------------------------------
2283 // Setup containers and skeleton of chart
2284 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2285 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2286 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2287 var g = wrap.select('g');
2289 gEnter.append('g').attr('class', 'nv-interactive');
2290 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2291 gEnter.append('g').attr('class', 'nv-y nv-axis');
2292 gEnter.append('g').attr('class', 'nv-background');
2293 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2294 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2295 gEnter.append('g').attr('class', 'nv-legendWrap');
2296 gEnter.append('g').attr('class', 'nv-controlsWrap');
2299 //------------------------------------------------------------
2303 legend.width(availableWidth);
2305 g.select('.nv-legendWrap')
2309 if ( margin.top != legend.height()) {
2310 margin.top = legend.height();
2311 availableHeight = (height || parseInt(container.style('height')) || 400)
2312 - margin.top - margin.bottom;
2315 g.select('.nv-legendWrap')
2316 .attr('transform', 'translate(0,' + (-margin.top) +')')
2319 //------------------------------------------------------------
2322 //------------------------------------------------------------
2326 var controlsData = [
2327 { key: 'Re-scale y-axis', disabled: !rescaleY }
2330 controls.width(140).color(['#444', '#444', '#444']);
2331 g.select('.nv-controlsWrap')
2332 .datum(controlsData)
2333 .attr('transform', 'translate(0,' + (-margin.top) +')')
2337 //------------------------------------------------------------
2340 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2342 if (rightAlignYAxis) {
2343 g.select(".nv-y.nv-axis")
2344 .attr("transform", "translate(" + availableWidth + ",0)");
2347 // Show error if series goes below 100%
2348 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2350 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2351 if (tempDisabled.length) {
2352 wrap.append('text').attr('class', 'tempDisabled')
2353 .attr('x', availableWidth / 2)
2354 .attr('y', '-.71em')
2355 .style('text-anchor', 'end')
2356 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2359 //------------------------------------------------------------
2360 // Main Chart Component(s)
2362 //------------------------------------------------------------
2363 //Set up interactive layer
2364 if (useInteractiveGuideline) {
2366 .width(availableWidth)
2367 .height(availableHeight)
2368 .margin({left:margin.left,top:margin.top})
2369 .svgContainer(container)
2371 wrap.select(".nv-interactive").call(interactiveLayer);
2374 gEnter.select('.nv-background')
2377 g.select('.nv-background rect')
2378 .attr('width', availableWidth)
2379 .attr('height', availableHeight);
2382 //.x(function(d) { return d.x })
2383 .y(function(d) { return d.display.y })
2384 .width(availableWidth)
2385 .height(availableHeight)
2386 .color(data.map(function(d,i) {
2387 return d.color || color(d, i);
2388 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2392 var linesWrap = g.select('.nv-linesWrap')
2393 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
2395 //d3.transition(linesWrap).call(lines);
2396 linesWrap.call(lines);
2398 /*Handle average lines [AN-612] ----------------------------*/
2400 //Store a series index number in the data array.
2401 data.forEach(function(d,i) {
2405 var avgLineData = data.filter(function(d) {
2406 return !d.disabled && !!average(d);
2409 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2410 .data(avgLineData, function(d) { return d.key; });
2412 var getAvgLineY = function(d) {
2413 //If average lines go off the svg element, clamp them to the svg bounds.
2414 var yVal = y(average(d));
2415 if (yVal < 0) return 0;
2416 if (yVal > availableHeight) return availableHeight;
2422 .style('stroke-width',2)
2423 .style('stroke-dasharray','10,10')
2424 .style('stroke',function (d,i) {
2425 return lines.color()(d,d.seriesIndex);
2428 .attr('x2',availableWidth)
2429 .attr('y1', getAvgLineY)
2430 .attr('y2', getAvgLineY);
2433 .style('stroke-opacity',function(d){
2434 //If average lines go offscreen, make them transparent
2435 var yVal = y(average(d));
2436 if (yVal < 0 || yVal > availableHeight) return 0;
2440 .attr('x2',availableWidth)
2441 .attr('y1', getAvgLineY)
2442 .attr('y2', getAvgLineY);
2444 avgLines.exit().remove();
2446 //Create index line -----------------------------------------
2448 var indexLine = linesWrap.selectAll('.nv-indexLine')
2450 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2453 .attr('fill', 'red')
2454 .attr('fill-opacity', .5)
2455 .style("pointer-events","all")
2459 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2460 .attr('height', availableHeight)
2462 //------------------------------------------------------------
2465 //------------------------------------------------------------
2471 //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
2472 .ticks( Math.min(data[0].values.length,availableWidth/70) )
2473 .tickSize(-availableHeight, 0);
2475 g.select('.nv-x.nv-axis')
2476 .attr('transform', 'translate(0,' + y.range()[0] + ')');
2477 d3.transition(g.select('.nv-x.nv-axis'))
2485 .ticks( availableHeight / 36 )
2486 .tickSize( -availableWidth, 0);
2488 d3.transition(g.select('.nv-y.nv-axis'))
2491 //------------------------------------------------------------
2494 //============================================================
2495 // Event Handling/Dispatching (in chart's scope)
2496 //------------------------------------------------------------
2499 function updateZero() {
2503 //When dragging the index line, turn off line transitions.
2504 // Then turn them back on when done dragging.
2505 var oldDuration = chart.transitionDuration();
2506 chart.transitionDuration(0);
2508 chart.transitionDuration(oldDuration);
2511 g.select('.nv-background rect')
2512 .on('click', function() {
2513 index.x = d3.mouse(this)[0];
2514 index.i = Math.round(dx.invert(index.x));
2516 // update state and send stateChange with new index
2517 state.index = index.i;
2518 dispatch.stateChange(state);
2523 lines.dispatch.on('elementClick', function(e) {
2524 index.i = e.pointIndex;
2525 index.x = dx(index.i);
2527 // update state and send stateChange with new index
2528 state.index = index.i;
2529 dispatch.stateChange(state);
2534 controls.dispatch.on('legendClick', function(d,i) {
2535 d.disabled = !d.disabled;
2536 rescaleY = !d.disabled;
2538 state.rescaleY = rescaleY;
2539 dispatch.stateChange(state);
2544 legend.dispatch.on('stateChange', function(newState) {
2545 state.disabled = newState.disabled;
2546 dispatch.stateChange(state);
2550 interactiveLayer.dispatch.on('elementMousemove', function(e) {
2551 lines.clearHighlights();
2552 var singlePoint, pointIndex, pointXLocation, allData = [];
2554 .filter(function(series, i) {
2555 series.seriesIndex = i;
2556 return !series.disabled;
2558 .forEach(function(series,i) {
2559 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2560 lines.highlightPoint(i, pointIndex, true);
2561 var point = series.values[pointIndex];
2562 if (typeof point === 'undefined') return;
2563 if (typeof singlePoint === 'undefined') singlePoint = point;
2564 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2567 value: chart.y()(point, pointIndex),
2568 color: color(series,series.seriesIndex)
2572 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
2573 interactiveLayer.tooltip
2574 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2575 .chartContainer(that.parentNode)
2577 .valueFormatter(function(d,i) {
2578 return yAxis.tickFormat()(d);
2587 interactiveLayer.renderGuideLine(pointXLocation);
2591 interactiveLayer.dispatch.on("elementMouseout",function(e) {
2592 dispatch.tooltipHide();
2593 lines.clearHighlights();
2596 dispatch.on('tooltipShow', function(e) {
2597 if (tooltips) showTooltip(e, that.parentNode);
2601 // Update chart from a state object passed to event handler
2602 dispatch.on('changeState', function(e) {
2604 if (typeof e.disabled !== 'undefined') {
2605 data.forEach(function(series,i) {
2606 series.disabled = e.disabled[i];
2609 state.disabled = e.disabled;
2613 if (typeof e.index !== 'undefined') {
2615 index.x = dx(index.i);
2617 state.index = e.index;
2624 if (typeof e.rescaleY !== 'undefined') {
2625 rescaleY = e.rescaleY;
2631 //============================================================
2639 //============================================================
2640 // Event Handling/Dispatching (out of chart's scope)
2641 //------------------------------------------------------------
2643 lines.dispatch.on('elementMouseover.tooltip', function(e) {
2644 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
2645 dispatch.tooltipShow(e);
2648 lines.dispatch.on('elementMouseout.tooltip', function(e) {
2649 dispatch.tooltipHide(e);
2652 dispatch.on('tooltipHide', function() {
2653 if (tooltips) nv.tooltip.cleanup();
2656 //============================================================
2659 //============================================================
2660 // Expose Public Variables
2661 //------------------------------------------------------------
2663 // expose chart's sub-components
2664 chart.dispatch = dispatch;
2665 chart.lines = lines;
2666 chart.legend = legend;
2667 chart.xAxis = xAxis;
2668 chart.yAxis = yAxis;
2669 chart.interactiveLayer = interactiveLayer;
2671 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi', 'id');
2673 chart.options = nv.utils.optionsFunc.bind(chart);
2675 chart.margin = function(_) {
2676 if (!arguments.length) return margin;
2677 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2678 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2679 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2680 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2684 chart.width = function(_) {
2685 if (!arguments.length) return width;
2690 chart.height = function(_) {
2691 if (!arguments.length) return height;
2696 chart.color = function(_) {
2697 if (!arguments.length) return color;
2698 color = nv.utils.getColor(_);
2699 legend.color(color);
2703 chart.rescaleY = function(_) {
2704 if (!arguments.length) return rescaleY;
2709 chart.showControls = function(_) {
2710 if (!arguments.length) return showControls;
2715 chart.useInteractiveGuideline = function(_) {
2716 if(!arguments.length) return useInteractiveGuideline;
2717 useInteractiveGuideline = _;
2719 chart.interactive(false);
2720 chart.useVoronoi(false);
2725 chart.showLegend = function(_) {
2726 if (!arguments.length) return showLegend;
2731 chart.showXAxis = function(_) {
2732 if (!arguments.length) return showXAxis;
2737 chart.showYAxis = function(_) {
2738 if (!arguments.length) return showYAxis;
2743 chart.rightAlignYAxis = function(_) {
2744 if(!arguments.length) return rightAlignYAxis;
2745 rightAlignYAxis = _;
2746 yAxis.orient( (_) ? 'right' : 'left');
2750 chart.tooltips = function(_) {
2751 if (!arguments.length) return tooltips;
2756 chart.tooltipContent = function(_) {
2757 if (!arguments.length) return tooltip;
2762 chart.state = function(_) {
2763 if (!arguments.length) return state;
2768 chart.defaultState = function(_) {
2769 if (!arguments.length) return defaultState;
2774 chart.noData = function(_) {
2775 if (!arguments.length) return noData;
2780 chart.average = function(_) {
2781 if(!arguments.length) return average;
2786 chart.transitionDuration = function(_) {
2787 if (!arguments.length) return transitionDuration;
2788 transitionDuration = _;
2792 //============================================================
2795 //============================================================
2797 //------------------------------------------------------------
2799 /* Normalize the data according to an index point. */
2800 function indexify(idx, data) {
2801 return data.map(function(line, i) {
2805 var v = lines.y()(line.values[idx], idx);
2807 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2809 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2810 line.tempDisabled = true;
2814 line.tempDisabled = false;
2816 line.values = line.values.map(function(point, pointIndex) {
2817 point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
2825 //============================================================
2830 //TODO: consider deprecating by adding necessary features to multiBar model
2831 nv.models.discreteBar = function() {
2833 //============================================================
2834 // Public Variables with Default Settings
2835 //------------------------------------------------------------
2837 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2840 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2841 , x = d3.scale.ordinal()
2842 , y = d3.scale.linear()
2843 , getX = function(d) { return d.x }
2844 , getY = function(d) { return d.y }
2845 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2846 , color = nv.utils.defaultColor()
2847 , showValues = false
2848 , valueFormat = d3.format(',.2f')
2853 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2854 , rectClass = 'discreteBar'
2857 //============================================================
2860 //============================================================
2861 // Private Variables
2862 //------------------------------------------------------------
2866 //============================================================
2869 function chart(selection) {
2870 selection.each(function(data) {
2871 var availableWidth = width - margin.left - margin.right,
2872 availableHeight = height - margin.top - margin.bottom,
2873 container = d3.select(this);
2876 //add series index to each data point for reference
2877 data = data.map(function(series, i) {
2878 series.values = series.values.map(function(point) {
2886 //------------------------------------------------------------
2889 // remap and flatten the data for use in calculating the scales' domains
2890 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2891 data.map(function(d) {
2892 return d.values.map(function(d,i) {
2893 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2897 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2898 .rangeBands(xRange || [0, availableWidth], .1);
2900 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
2903 // If showValues, pad the Y axis range to account for label height
2904 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
2905 else y.range(yRange || [availableHeight, 0]);
2907 //store old scales if they exist
2909 y0 = y0 || y.copy().range([y(0),y(0)]);
2911 //------------------------------------------------------------
2914 //------------------------------------------------------------
2915 // Setup containers and skeleton of chart
2917 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
2918 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
2919 var gEnter = wrapEnter.append('g');
2920 var g = wrap.select('g');
2922 gEnter.append('g').attr('class', 'nv-groups');
2924 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2926 //------------------------------------------------------------
2930 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
2931 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
2932 .data(function(d) { return d }, function(d) { return d.key });
2933 groups.enter().append('g')
2934 .style('stroke-opacity', 1e-6)
2935 .style('fill-opacity', 1e-6);
2938 .style('stroke-opacity', 1e-6)
2939 .style('fill-opacity', 1e-6)
2942 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
2943 .classed('hover', function(d) { return d.hover });
2946 .style('stroke-opacity', 1)
2947 .style('fill-opacity', .75);
2950 var bars = groups.selectAll('g.nv-bar')
2951 .data(function(d) { return d.values });
2953 bars.exit().remove();
2956 var barsEnter = bars.enter().append('g')
2957 .attr('transform', function(d,i,j) {
2958 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
2960 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
2961 d3.select(this).classed('hover', true);
2962 dispatch.elementMouseover({
2965 series: data[d.series],
2966 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
2968 seriesIndex: d.series,
2972 .on('mouseout', function(d,i) {
2973 d3.select(this).classed('hover', false);
2974 dispatch.elementMouseout({
2977 series: data[d.series],
2979 seriesIndex: d.series,
2983 .on('click', function(d,i) {
2984 dispatch.elementClick({
2987 series: data[d.series],
2988 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
2990 seriesIndex: d.series,
2993 d3.event.stopPropagation();
2995 .on('dblclick', function(d,i) {
2996 dispatch.elementDblClick({
2999 series: data[d.series],
3000 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3002 seriesIndex: d.series,
3005 d3.event.stopPropagation();
3008 barsEnter.append('rect')
3010 .attr('width', x.rangeBand() * .9 / data.length )
3013 barsEnter.append('text')
3014 .attr('text-anchor', 'middle')
3018 .text(function(d,i) { return valueFormat(getY(d,i)) })
3020 .attr('x', x.rangeBand() * .9 / 2)
3021 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3025 bars.selectAll('text').remove();
3029 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3030 .style('fill', function(d,i) { return d.color || color(d,i) })
3031 .style('stroke', function(d,i) { return d.color || color(d,i) })
3033 .attr('class', rectClass)
3035 .attr('width', x.rangeBand() * .9 / data.length);
3037 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3038 .attr('transform', function(d,i) {
3039 var left = x(getX(d,i)) + x.rangeBand() * .05,
3040 top = getY(d,i) < 0 ?
3042 y(0) - y(getY(d,i)) < 1 ?
3043 y(0) - 1 : //make 1 px positive bars show up above y=0
3046 return 'translate(' + left + ', ' + top + ')'
3049 .attr('height', function(d,i) {
3050 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3054 //store old scales for use in transitions on update
3064 //============================================================
3065 // Expose Public Variables
3066 //------------------------------------------------------------
3068 chart.dispatch = dispatch;
3070 chart.options = nv.utils.optionsFunc.bind(chart);
3072 chart.x = function(_) {
3073 if (!arguments.length) return getX;
3078 chart.y = function(_) {
3079 if (!arguments.length) return getY;
3084 chart.margin = function(_) {
3085 if (!arguments.length) return margin;
3086 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3087 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3088 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3089 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3093 chart.width = function(_) {
3094 if (!arguments.length) return width;
3099 chart.height = function(_) {
3100 if (!arguments.length) return height;
3105 chart.xScale = function(_) {
3106 if (!arguments.length) return x;
3111 chart.yScale = function(_) {
3112 if (!arguments.length) return y;
3117 chart.xDomain = function(_) {
3118 if (!arguments.length) return xDomain;
3123 chart.yDomain = function(_) {
3124 if (!arguments.length) return yDomain;
3129 chart.xRange = function(_) {
3130 if (!arguments.length) return xRange;
3135 chart.yRange = function(_) {
3136 if (!arguments.length) return yRange;
3141 chart.forceY = function(_) {
3142 if (!arguments.length) return forceY;
3147 chart.color = function(_) {
3148 if (!arguments.length) return color;
3149 color = nv.utils.getColor(_);
3153 chart.id = function(_) {
3154 if (!arguments.length) return id;
3159 chart.showValues = function(_) {
3160 if (!arguments.length) return showValues;
3165 chart.valueFormat= function(_) {
3166 if (!arguments.length) return valueFormat;
3171 chart.rectClass= function(_) {
3172 if (!arguments.length) return rectClass;
3176 //============================================================
3182 nv.models.discreteBarChart = function() {
3184 //============================================================
3185 // Public Variables with Default Settings
3186 //------------------------------------------------------------
3188 var discretebar = nv.models.discreteBar()
3189 , xAxis = nv.models.axis()
3190 , yAxis = nv.models.axis()
3193 var margin = {top: 15, right: 10, bottom: 50, left: 60}
3196 , color = nv.utils.getColor()
3199 , rightAlignYAxis = false
3200 , staggerLabels = false
3202 , tooltip = function(key, x, y, e, graph) {
3203 return '<h3>' + x + '</h3>' +
3208 , noData = "No Data Available."
3209 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
3210 , transitionDuration = 250
3215 .highlightZero(false)
3217 .tickFormat(function(d) { return d })
3220 .orient((rightAlignYAxis) ? 'right' : 'left')
3221 .tickFormat(d3.format(',.1f'))
3224 //============================================================
3227 //============================================================
3228 // Private Variables
3229 //------------------------------------------------------------
3231 var showTooltip = function(e, offsetElement) {
3232 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3233 top = e.pos[1] + ( offsetElement.offsetTop || 0),
3234 x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3235 y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3236 content = tooltip(e.series.key, x, y, e, chart);
3238 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3241 //============================================================
3244 function chart(selection) {
3245 selection.each(function(data) {
3246 var container = d3.select(this),
3249 var availableWidth = (width || parseInt(container.style('width')) || 960)
3250 - margin.left - margin.right,
3251 availableHeight = (height || parseInt(container.style('height')) || 400)
3252 - margin.top - margin.bottom;
3255 chart.update = function() {
3256 dispatch.beforeUpdate();
3257 container.transition().duration(transitionDuration).call(chart);
3259 chart.container = this;
3262 //------------------------------------------------------------
3263 // Display No Data message if there's nothing to show.
3265 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3266 var noDataText = container.selectAll('.nv-noData').data([noData]);
3268 noDataText.enter().append('text')
3269 .attr('class', 'nvd3 nv-noData')
3270 .attr('dy', '-.7em')
3271 .style('text-anchor', 'middle');
3274 .attr('x', margin.left + availableWidth / 2)
3275 .attr('y', margin.top + availableHeight / 2)
3276 .text(function(d) { return d });
3280 container.selectAll('.nv-noData').remove();
3283 //------------------------------------------------------------
3286 //------------------------------------------------------------
3289 x = discretebar.xScale();
3290 y = discretebar.yScale().clamp(true);
3292 //------------------------------------------------------------
3295 //------------------------------------------------------------
3296 // Setup containers and skeleton of chart
3298 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3299 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3300 var defsEnter = gEnter.append('defs');
3301 var g = wrap.select('g');
3303 gEnter.append('g').attr('class', 'nv-x nv-axis');
3304 gEnter.append('g').attr('class', 'nv-y nv-axis');
3305 gEnter.append('g').attr('class', 'nv-barsWrap');
3307 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3309 if (rightAlignYAxis) {
3310 g.select(".nv-y.nv-axis")
3311 .attr("transform", "translate(" + availableWidth + ",0)");
3314 //------------------------------------------------------------
3317 //------------------------------------------------------------
3318 // Main Chart Component(s)
3321 .width(availableWidth)
3322 .height(availableHeight);
3325 var barsWrap = g.select('.nv-barsWrap')
3326 .datum(data.filter(function(d) { return !d.disabled }))
3328 barsWrap.transition().call(discretebar);
3330 //------------------------------------------------------------
3334 defsEnter.append('clipPath')
3335 .attr('id', 'nv-x-label-clip-' + discretebar.id())
3338 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3339 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3341 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3344 //------------------------------------------------------------
3350 .ticks( availableWidth / 100 )
3351 .tickSize(-availableHeight, 0);
3353 g.select('.nv-x.nv-axis')
3354 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3355 //d3.transition(g.select('.nv-x.nv-axis'))
3356 g.select('.nv-x.nv-axis').transition()
3360 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3362 if (staggerLabels) {
3365 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3372 .ticks( availableHeight / 36 )
3373 .tickSize( -availableWidth, 0);
3375 g.select('.nv-y.nv-axis').transition()
3379 //------------------------------------------------------------
3382 //============================================================
3383 // Event Handling/Dispatching (in chart's scope)
3384 //------------------------------------------------------------
3386 dispatch.on('tooltipShow', function(e) {
3387 if (tooltips) showTooltip(e, that.parentNode);
3390 //============================================================
3398 //============================================================
3399 // Event Handling/Dispatching (out of chart's scope)
3400 //------------------------------------------------------------
3402 discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3403 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3404 dispatch.tooltipShow(e);
3407 discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3408 dispatch.tooltipHide(e);
3411 dispatch.on('tooltipHide', function() {
3412 if (tooltips) nv.tooltip.cleanup();
3415 //============================================================
3418 //============================================================
3419 // Expose Public Variables
3420 //------------------------------------------------------------
3422 // expose chart's sub-components
3423 chart.dispatch = dispatch;
3424 chart.discretebar = discretebar;
3425 chart.xAxis = xAxis;
3426 chart.yAxis = yAxis;
3428 d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3430 chart.options = nv.utils.optionsFunc.bind(chart);
3432 chart.margin = function(_) {
3433 if (!arguments.length) return margin;
3434 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3435 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3436 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3437 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3441 chart.width = function(_) {
3442 if (!arguments.length) return width;
3447 chart.height = function(_) {
3448 if (!arguments.length) return height;
3453 chart.color = function(_) {
3454 if (!arguments.length) return color;
3455 color = nv.utils.getColor(_);
3456 discretebar.color(color);
3460 chart.showXAxis = function(_) {
3461 if (!arguments.length) return showXAxis;
3466 chart.showYAxis = function(_) {
3467 if (!arguments.length) return showYAxis;
3472 chart.rightAlignYAxis = function(_) {
3473 if(!arguments.length) return rightAlignYAxis;
3474 rightAlignYAxis = _;
3475 yAxis.orient( (_) ? 'right' : 'left');
3479 chart.staggerLabels = function(_) {
3480 if (!arguments.length) return staggerLabels;
3485 chart.tooltips = function(_) {
3486 if (!arguments.length) return tooltips;
3491 chart.tooltipContent = function(_) {
3492 if (!arguments.length) return tooltip;
3497 chart.noData = function(_) {
3498 if (!arguments.length) return noData;
3503 chart.transitionDuration = function(_) {
3504 if (!arguments.length) return transitionDuration;
3505 transitionDuration = _;
3509 //============================================================
3515 nv.models.distribution = function() {
3517 //============================================================
3518 // Public Variables with Default Settings
3519 //------------------------------------------------------------
3521 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3522 , width = 400 //technically width or height depending on x or y....
3524 , axis = 'x' // 'x' or 'y'... horizontal or vertical
3525 , getData = function(d) { return d[axis] } // defaults d.x or d.y
3526 , color = nv.utils.defaultColor()
3527 , scale = d3.scale.linear()
3531 //============================================================
3534 //============================================================
3535 // Private Variables
3536 //------------------------------------------------------------
3540 //============================================================
3543 function chart(selection) {
3544 selection.each(function(data) {
3545 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3546 naxis = axis == 'x' ? 'y' : 'x',
3547 container = d3.select(this);
3550 //------------------------------------------------------------
3553 scale0 = scale0 || scale;
3555 //------------------------------------------------------------
3558 //------------------------------------------------------------
3559 // Setup containers and skeleton of chart
3561 var wrap = container.selectAll('g.nv-distribution').data([data]);
3562 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3563 var gEnter = wrapEnter.append('g');
3564 var g = wrap.select('g');
3566 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
3568 //------------------------------------------------------------
3571 var distWrap = g.selectAll('g.nv-dist')
3572 .data(function(d) { return d }, function(d) { return d.key });
3574 distWrap.enter().append('g');
3576 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
3577 .style('stroke', function(d,i) { return color(d, i) });
3579 var dist = distWrap.selectAll('line.nv-dist' + axis)
3580 .data(function(d) { return d.values })
3581 dist.enter().append('line')
3582 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
3583 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
3584 distWrap.exit().selectAll('line.nv-dist' + axis)
3586 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3587 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3588 .style('stroke-opacity', 0)
3591 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
3592 .attr(naxis + '1', 0)
3593 .attr(naxis + '2', size);
3596 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3597 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3600 scale0 = scale.copy();
3608 //============================================================
3609 // Expose Public Variables
3610 //------------------------------------------------------------
3611 chart.options = nv.utils.optionsFunc.bind(chart);
3613 chart.margin = function(_) {
3614 if (!arguments.length) return margin;
3615 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3616 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3617 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3618 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3622 chart.width = function(_) {
3623 if (!arguments.length) return width;
3628 chart.axis = function(_) {
3629 if (!arguments.length) return axis;
3634 chart.size = function(_) {
3635 if (!arguments.length) return size;
3640 chart.getData = function(_) {
3641 if (!arguments.length) return getData;
3642 getData = d3.functor(_);
3646 chart.scale = function(_) {
3647 if (!arguments.length) return scale;
3652 chart.color = function(_) {
3653 if (!arguments.length) return color;
3654 color = nv.utils.getColor(_);
3657 //============================================================
3662 //TODO: consider deprecating and using multibar with single series for this
3663 nv.models.historicalBar = function() {
3665 //============================================================
3666 // Public Variables with Default Settings
3667 //------------------------------------------------------------
3669 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3672 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3673 , x = d3.scale.linear()
3674 , y = d3.scale.linear()
3675 , getX = function(d) { return d.x }
3676 , getY = function(d) { return d.y }
3681 , color = nv.utils.defaultColor()
3686 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
3687 , interactive = true
3690 //============================================================
3693 function chart(selection) {
3694 selection.each(function(data) {
3695 var availableWidth = width - margin.left - margin.right,
3696 availableHeight = height - margin.top - margin.bottom,
3697 container = d3.select(this);
3700 //------------------------------------------------------------
3703 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
3706 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
3708 x.range(xRange || [0, availableWidth]);
3710 y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
3711 .range(yRange || [availableHeight, 0]);
3713 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
3715 if (x.domain()[0] === x.domain()[1])
3717 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3720 if (y.domain()[0] === y.domain()[1])
3722 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3725 //------------------------------------------------------------
3728 //------------------------------------------------------------
3729 // Setup containers and skeleton of chart
3731 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
3732 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
3733 var defsEnter = wrapEnter.append('defs');
3734 var gEnter = wrapEnter.append('g');
3735 var g = wrap.select('g');
3737 gEnter.append('g').attr('class', 'nv-bars');
3739 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3741 //------------------------------------------------------------
3745 .on('click', function(d,i) {
3746 dispatch.chartClick({
3755 defsEnter.append('clipPath')
3756 .attr('id', 'nv-chart-clip-path-' + id)
3759 wrap.select('#nv-chart-clip-path-' + id + ' rect')
3760 .attr('width', availableWidth)
3761 .attr('height', availableHeight);
3763 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3767 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3768 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
3770 bars.exit().remove();
3773 var barsEnter = bars.enter().append('rect')
3774 //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3776 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
3777 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
3778 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3779 .on('mouseover', function(d,i) {
3780 if (!interactive) return;
3781 d3.select(this).classed('hover', true);
3782 dispatch.elementMouseover({
3785 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3792 .on('mouseout', function(d,i) {
3793 if (!interactive) return;
3794 d3.select(this).classed('hover', false);
3795 dispatch.elementMouseout({
3803 .on('click', function(d,i) {
3804 if (!interactive) return;
3805 dispatch.elementClick({
3810 pos: [x(getX(d,i)), y(getY(d,i))],
3814 d3.event.stopPropagation();
3816 .on('dblclick', function(d,i) {
3817 if (!interactive) return;
3818 dispatch.elementDblClick({
3823 pos: [x(getX(d,i)), y(getY(d,i))],
3827 d3.event.stopPropagation();
3831 .attr('fill', function(d,i) { return color(d, i); })
3832 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3834 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3835 //TODO: better width calculations that don't assume always uniform data spacing;w
3836 .attr('width', (availableWidth / data[0].values.length) * .9 );
3840 .attr('y', function(d,i) {
3841 var rval = getY(d,i) < 0 ?
3843 y(0) - y(getY(d,i)) < 1 ?
3846 return nv.utils.NaNtoZero(rval);
3848 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
3855 //Create methods to allow outside functions to highlight a specific bar.
3856 chart.highlightPoint = function(pointIndex, isHoverOver) {
3857 d3.select(".nv-historicalBar-" + id)
3858 .select(".nv-bars .nv-bar-0-" + pointIndex)
3859 .classed("hover", isHoverOver)
3863 chart.clearHighlights = function() {
3864 d3.select(".nv-historicalBar-" + id)
3865 .select(".nv-bars .nv-bar.hover")
3866 .classed("hover", false)
3869 //============================================================
3870 // Expose Public Variables
3871 //------------------------------------------------------------
3873 chart.dispatch = dispatch;
3875 chart.options = nv.utils.optionsFunc.bind(chart);
3877 chart.x = function(_) {
3878 if (!arguments.length) return getX;
3883 chart.y = function(_) {
3884 if (!arguments.length) return getY;
3889 chart.margin = function(_) {
3890 if (!arguments.length) return margin;
3891 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3892 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3893 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3894 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3898 chart.width = function(_) {
3899 if (!arguments.length) return width;
3904 chart.height = function(_) {
3905 if (!arguments.length) return height;
3910 chart.xScale = function(_) {
3911 if (!arguments.length) return x;
3916 chart.yScale = function(_) {
3917 if (!arguments.length) return y;
3922 chart.xDomain = function(_) {
3923 if (!arguments.length) return xDomain;
3928 chart.yDomain = function(_) {
3929 if (!arguments.length) return yDomain;
3934 chart.xRange = function(_) {
3935 if (!arguments.length) return xRange;
3940 chart.yRange = function(_) {
3941 if (!arguments.length) return yRange;
3946 chart.forceX = function(_) {
3947 if (!arguments.length) return forceX;
3952 chart.forceY = function(_) {
3953 if (!arguments.length) return forceY;
3958 chart.padData = function(_) {
3959 if (!arguments.length) return padData;
3964 chart.clipEdge = function(_) {
3965 if (!arguments.length) return clipEdge;
3970 chart.color = function(_) {
3971 if (!arguments.length) return color;
3972 color = nv.utils.getColor(_);
3976 chart.id = function(_) {
3977 if (!arguments.length) return id;
3982 chart.interactive = function(_) {
3983 if(!arguments.length) return interactive;
3984 interactive = false;
3988 //============================================================
3994 nv.models.historicalBarChart = function() {
3996 //============================================================
3997 // Public Variables with Default Settings
3998 //------------------------------------------------------------
4000 var bars = nv.models.historicalBar()
4001 , xAxis = nv.models.axis()
4002 , yAxis = nv.models.axis()
4003 , legend = nv.models.legend()
4007 var margin = {top: 30, right: 90, bottom: 50, left: 90}
4008 , color = nv.utils.defaultColor()
4011 , showLegend = false
4014 , rightAlignYAxis = false
4016 , tooltip = function(key, x, y, e, graph) {
4017 return '<h3>' + key + '</h3>' +
4018 '<p>' + y + ' at ' + x + '</p>'
4023 , defaultState = null
4024 , noData = 'No Data Available.'
4025 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4026 , transitionDuration = 250
4034 .orient( (rightAlignYAxis) ? 'right' : 'left')
4037 //============================================================
4040 //============================================================
4041 // Private Variables
4042 //------------------------------------------------------------
4044 var showTooltip = function(e, offsetElement) {
4046 // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4047 if (offsetElement) {
4048 var svg = d3.select(offsetElement).select('svg');
4049 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4051 viewBox = viewBox.split(' ');
4052 var ratio = parseInt(svg.style('width')) / viewBox[2];
4053 e.pos[0] = e.pos[0] * ratio;
4054 e.pos[1] = e.pos[1] * ratio;
4058 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4059 top = e.pos[1] + ( offsetElement.offsetTop || 0),
4060 x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4061 y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4062 content = tooltip(e.series.key, x, y, e, chart);
4064 nv.tooltip.show([left, top], content, null, null, offsetElement);
4067 //============================================================
4070 function chart(selection) {
4071 selection.each(function(data) {
4072 var container = d3.select(this),
4075 var availableWidth = (width || parseInt(container.style('width')) || 960)
4076 - margin.left - margin.right,
4077 availableHeight = (height || parseInt(container.style('height')) || 400)
4078 - margin.top - margin.bottom;
4081 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4082 chart.container = this;
4084 //set state.disabled
4085 state.disabled = data.map(function(d) { return !!d.disabled });
4087 if (!defaultState) {
4090 for (key in state) {
4091 if (state[key] instanceof Array)
4092 defaultState[key] = state[key].slice(0);
4094 defaultState[key] = state[key];
4098 //------------------------------------------------------------
4099 // Display noData message if there's nothing to show.
4101 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4102 var noDataText = container.selectAll('.nv-noData').data([noData]);
4104 noDataText.enter().append('text')
4105 .attr('class', 'nvd3 nv-noData')
4106 .attr('dy', '-.7em')
4107 .style('text-anchor', 'middle');
4110 .attr('x', margin.left + availableWidth / 2)
4111 .attr('y', margin.top + availableHeight / 2)
4112 .text(function(d) { return d });
4116 container.selectAll('.nv-noData').remove();
4119 //------------------------------------------------------------
4122 //------------------------------------------------------------
4128 //------------------------------------------------------------
4131 //------------------------------------------------------------
4132 // Setup containers and skeleton of chart
4134 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4135 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4136 var g = wrap.select('g');
4138 gEnter.append('g').attr('class', 'nv-x nv-axis');
4139 gEnter.append('g').attr('class', 'nv-y nv-axis');
4140 gEnter.append('g').attr('class', 'nv-barsWrap');
4141 gEnter.append('g').attr('class', 'nv-legendWrap');
4143 //------------------------------------------------------------
4146 //------------------------------------------------------------
4150 legend.width(availableWidth);
4152 g.select('.nv-legendWrap')
4156 if ( margin.top != legend.height()) {
4157 margin.top = legend.height();
4158 availableHeight = (height || parseInt(container.style('height')) || 400)
4159 - margin.top - margin.bottom;
4162 wrap.select('.nv-legendWrap')
4163 .attr('transform', 'translate(0,' + (-margin.top) +')')
4166 //------------------------------------------------------------
4168 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4170 if (rightAlignYAxis) {
4171 g.select(".nv-y.nv-axis")
4172 .attr("transform", "translate(" + availableWidth + ",0)");
4176 //------------------------------------------------------------
4177 // Main Chart Component(s)
4180 .width(availableWidth)
4181 .height(availableHeight)
4182 .color(data.map(function(d,i) {
4183 return d.color || color(d, i);
4184 }).filter(function(d,i) { return !data[i].disabled }));
4187 var barsWrap = g.select('.nv-barsWrap')
4188 .datum(data.filter(function(d) { return !d.disabled }))
4190 barsWrap.transition().call(bars);
4192 //------------------------------------------------------------
4195 //------------------------------------------------------------
4201 .tickSize(-availableHeight, 0);
4203 g.select('.nv-x.nv-axis')
4204 .attr('transform', 'translate(0,' + y.range()[0] + ')');
4205 g.select('.nv-x.nv-axis')
4213 .ticks( availableHeight / 36 )
4214 .tickSize( -availableWidth, 0);
4216 g.select('.nv-y.nv-axis')
4220 //------------------------------------------------------------
4223 //============================================================
4224 // Event Handling/Dispatching (in chart's scope)
4225 //------------------------------------------------------------
4227 legend.dispatch.on('legendClick', function(d,i) {
4228 d.disabled = !d.disabled;
4230 if (!data.filter(function(d) { return !d.disabled }).length) {
4231 data.map(function(d) {
4233 wrap.selectAll('.nv-series').classed('disabled', false);
4238 state.disabled = data.map(function(d) { return !!d.disabled });
4239 dispatch.stateChange(state);
4241 selection.transition().call(chart);
4244 legend.dispatch.on('legendDblclick', function(d) {
4245 //Double clicking should always enable current series, and disabled all others.
4246 data.forEach(function(d) {
4251 state.disabled = data.map(function(d) { return !!d.disabled });
4252 dispatch.stateChange(state);
4256 dispatch.on('tooltipShow', function(e) {
4257 if (tooltips) showTooltip(e, that.parentNode);
4261 dispatch.on('changeState', function(e) {
4263 if (typeof e.disabled !== 'undefined') {
4264 data.forEach(function(series,i) {
4265 series.disabled = e.disabled[i];
4268 state.disabled = e.disabled;
4271 selection.call(chart);
4274 //============================================================
4282 //============================================================
4283 // Event Handling/Dispatching (out of chart's scope)
4284 //------------------------------------------------------------
4286 bars.dispatch.on('elementMouseover.tooltip', function(e) {
4287 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
4288 dispatch.tooltipShow(e);
4291 bars.dispatch.on('elementMouseout.tooltip', function(e) {
4292 dispatch.tooltipHide(e);
4295 dispatch.on('tooltipHide', function() {
4296 if (tooltips) nv.tooltip.cleanup();
4299 //============================================================
4302 //============================================================
4303 // Expose Public Variables
4304 //------------------------------------------------------------
4306 // expose chart's sub-components
4307 chart.dispatch = dispatch;
4309 chart.legend = legend;
4310 chart.xAxis = xAxis;
4311 chart.yAxis = yAxis;
4313 d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale',
4314 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights', 'interactive');
4316 chart.options = nv.utils.optionsFunc.bind(chart);
4318 chart.margin = function(_) {
4319 if (!arguments.length) return margin;
4320 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4321 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4322 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4323 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4327 chart.width = function(_) {
4328 if (!arguments.length) return width;
4333 chart.height = function(_) {
4334 if (!arguments.length) return height;
4339 chart.color = function(_) {
4340 if (!arguments.length) return color;
4341 color = nv.utils.getColor(_);
4342 legend.color(color);
4346 chart.showLegend = function(_) {
4347 if (!arguments.length) return showLegend;
4352 chart.showXAxis = function(_) {
4353 if (!arguments.length) return showXAxis;
4358 chart.showYAxis = function(_) {
4359 if (!arguments.length) return showYAxis;
4364 chart.rightAlignYAxis = function(_) {
4365 if(!arguments.length) return rightAlignYAxis;
4366 rightAlignYAxis = _;
4367 yAxis.orient( (_) ? 'right' : 'left');
4371 chart.tooltips = function(_) {
4372 if (!arguments.length) return tooltips;
4377 chart.tooltipContent = function(_) {
4378 if (!arguments.length) return tooltip;
4383 chart.state = function(_) {
4384 if (!arguments.length) return state;
4389 chart.defaultState = function(_) {
4390 if (!arguments.length) return defaultState;
4395 chart.noData = function(_) {
4396 if (!arguments.length) return noData;
4401 chart.transitionDuration = function(_) {
4402 if (!arguments.length) return transitionDuration;
4403 transitionDuration = _;
4407 //============================================================
4412 nv.models.indentedTree = function() {
4414 //============================================================
4415 // Public Variables with Default Settings
4416 //------------------------------------------------------------
4418 var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
4421 , color = nv.utils.defaultColor()
4422 , id = Math.floor(Math.random() * 10000)
4424 , filterZero = false
4425 , noData = "No Data Available."
4427 , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
4429 , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
4430 , iconClose = 'images/grey-minus.png'
4431 , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
4432 , getUrl = function(d) { return d.url }
4435 //============================================================
4439 function chart(selection) {
4440 selection.each(function(data) {
4442 container = d3.select(this);
4444 var tree = d3.layout.tree()
4445 .children(function(d) { return d.values })
4446 .size([height, childIndent]); //Not sure if this is needed now that the result is HTML
4448 chart.update = function() { container.transition().duration(600).call(chart) };
4451 //------------------------------------------------------------
4452 // Display No Data message if there's nothing to show.
4453 if (!data[0]) data[0] = {key: noData};
4455 //------------------------------------------------------------
4458 var nodes = tree.nodes(data[0]);
4460 // nodes.map(function(d) {
4464 //------------------------------------------------------------
4465 // Setup containers and skeleton of chart
4467 var wrap = d3.select(this).selectAll('div').data([[nodes]]);
4468 var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
4469 var tableEnter = wrapEnter.append('table');
4470 var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
4472 //------------------------------------------------------------
4476 var thead = tableEnter.append('thead');
4478 var theadRow1 = thead.append('tr');
4480 columns.forEach(function(column) {
4483 .attr('width', column.width ? column.width : '10%')
4484 .style('text-align', column.type == 'numeric' ? 'right' : 'left')
4486 .text(column.label);
4491 var tbody = table.selectAll('tbody')
4492 .data(function(d) { return d });
4493 tbody.enter().append('tbody');
4497 //compute max generations
4498 depth = d3.max(nodes, function(node) { return node.depth });
4499 tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
4502 // Update the nodes…
4503 var node = tbody.selectAll('tr')
4504 // .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)});
4505 .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; } )}, function(d,i) { return d.id || (d.id || ++idx)});
4506 //.style('display', 'table-row'); //TODO: see if this does anything
4508 node.exit().remove();
4510 node.select('img.nv-treeicon')
4512 .classed('folded', folded);
4514 var nodeEnter = node.enter().append('tr');
4517 columns.forEach(function(column, index) {
4519 var nodeName = nodeEnter.append('td')
4520 .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here
4521 .style('text-align', column.type == 'numeric' ? 'right' : 'left');
4525 nodeName.append('img')
4526 .classed('nv-treeicon', true)
4527 .classed('nv-folded', folded)
4529 .style('width', '14px')
4530 .style('height', '14px')
4531 .style('padding', '0 1px')
4532 .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
4533 .on('click', click);
4537 nodeName.each(function(d) {
4538 if (!index && getUrl(d))
4541 .attr('href',getUrl)
4542 .attr('class', d3.functor(column.classes))
4548 d3.select(this).select('span')
4549 .attr('class', d3.functor(column.classes) )
4550 .text(function(d) { return column.format ? column.format(d) :
4551 (d[column.key] || '-') });
4554 if (column.showCount) {
4555 nodeName.append('span')
4556 .attr('class', 'nv-childrenCount');
4558 node.selectAll('span.nv-childrenCount').text(function(d) {
4559 return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent
4560 '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter
4561 || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values...
4562 || 0) + ')' //This is the catch-all in case there are no children after a filter
4563 : '' //If this is not a parent, just give an empty string
4567 // if (column.click)
4568 // nodeName.select('span').on('click', column.click);
4574 .on('click', function(d) {
4575 dispatch.elementClick({
4576 row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
4581 .on('dblclick', function(d) {
4582 dispatch.elementDblclick({
4588 .on('mouseover', function(d) {
4589 dispatch.elementMouseover({
4595 .on('mouseout', function(d) {
4596 dispatch.elementMouseout({
4606 // Toggle children on click.
4607 function click(d, _, unshift) {
4608 d3.event.stopPropagation();
4610 if(d3.event.shiftKey && !unshift) {
4611 //If you shift-click, it'll toggle fold all the children, instead of itself
4612 d3.event.shiftKey = false;
4613 d.values && d.values.forEach(function(node){
4614 if (node.values || node._values) {
4615 click(node, 0, true);
4620 if(!hasChildren(d)) {
4622 //window.location.href = d.url;
4626 d._values = d.values;
4629 d.values = d._values;
4637 return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
4640 function folded(d) {
4641 return (d._values && d._values.length);
4644 function hasChildren(d) {
4645 var values = d.values || d._values;
4647 return (values && values.length);
4657 //============================================================
4658 // Expose Public Variables
4659 //------------------------------------------------------------
4660 chart.options = nv.utils.optionsFunc.bind(chart);
4662 chart.margin = function(_) {
4663 if (!arguments.length) return margin;
4664 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4665 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4666 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4667 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4671 chart.width = function(_) {
4672 if (!arguments.length) return width;
4677 chart.height = function(_) {
4678 if (!arguments.length) return height;
4683 chart.color = function(_) {
4684 if (!arguments.length) return color;
4685 color = nv.utils.getColor(_);
4686 scatter.color(color);
4690 chart.id = function(_) {
4691 if (!arguments.length) return id;
4696 chart.header = function(_) {
4697 if (!arguments.length) return header;
4702 chart.noData = function(_) {
4703 if (!arguments.length) return noData;
4708 chart.filterZero = function(_) {
4709 if (!arguments.length) return filterZero;
4714 chart.columns = function(_) {
4715 if (!arguments.length) return columns;
4720 chart.tableClass = function(_) {
4721 if (!arguments.length) return tableClass;
4726 chart.iconOpen = function(_){
4727 if (!arguments.length) return iconOpen;
4732 chart.iconClose = function(_){
4733 if (!arguments.length) return iconClose;
4738 chart.getUrl = function(_){
4739 if (!arguments.length) return getUrl;
4744 //============================================================
4748 };nv.models.legend = function() {
4750 //============================================================
4751 // Public Variables with Default Settings
4752 //------------------------------------------------------------
4754 var margin = {top: 5, right: 0, bottom: 5, left: 0}
4757 , getKey = function(d) { return d.key }
4758 , color = nv.utils.defaultColor()
4761 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4762 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4763 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4766 //============================================================
4769 function chart(selection) {
4770 selection.each(function(data) {
4771 var availableWidth = width - margin.left - margin.right,
4772 container = d3.select(this);
4775 //------------------------------------------------------------
4776 // Setup containers and skeleton of chart
4778 var wrap = container.selectAll('g.nv-legend').data([data]);
4779 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4780 var g = wrap.select('g');
4782 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4784 //------------------------------------------------------------
4787 var series = g.selectAll('.nv-series')
4788 .data(function(d) { return d });
4789 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4790 .on('mouseover', function(d,i) {
4791 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4793 .on('mouseout', function(d,i) {
4794 dispatch.legendMouseout(d,i);
4796 .on('click', function(d,i) {
4797 dispatch.legendClick(d,i);
4799 if (radioButtonMode) {
4800 //Radio button mode: set every series to disabled,
4801 // and enable the clicked series.
4802 data.forEach(function(series) { series.disabled = true});
4806 d.disabled = !d.disabled;
4807 if (data.every(function(series) { return series.disabled})) {
4808 //the default behavior of NVD3 legends is, if every single series
4809 // is disabled, turn all series' back on.
4810 data.forEach(function(series) { series.disabled = false});
4813 dispatch.stateChange({
4814 disabled: data.map(function(d) { return !!d.disabled })
4818 .on('dblclick', function(d,i) {
4819 dispatch.legendDblclick(d,i);
4821 //the default behavior of NVD3 legends, when double clicking one,
4822 // is to set all other series' to false, and make the double clicked series enabled.
4823 data.forEach(function(series) {
4824 series.disabled = true;
4827 dispatch.stateChange({
4828 disabled: data.map(function(d) { return !!d.disabled })
4832 seriesEnter.append('circle')
4833 .style('stroke-width', 2)
4834 .attr('class','nv-legend-symbol')
4836 seriesEnter.append('text')
4837 .attr('text-anchor', 'start')
4838 .attr('class','nv-legend-text')
4839 .attr('dy', '.32em')
4841 series.classed('disabled', function(d) { return d.disabled });
4842 series.exit().remove();
4843 series.select('circle')
4844 .style('fill', function(d,i) { return d.color || color(d,i)})
4845 .style('stroke', function(d,i) { return d.color || color(d, i) });
4846 series.select('text').text(getKey);
4849 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4851 // NEW ALIGNING CODE, TODO: clean up
4854 var seriesWidths = [];
4855 series.each(function(d,i) {
4856 var legendText = d3.select(this).select('text');
4859 nodeTextLength = legendText.node().getComputedTextLength();
4862 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4865 seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4868 var seriesPerRow = 0;
4869 var legendWidth = 0;
4870 var columnWidths = [];
4872 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4873 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4874 legendWidth += seriesWidths[seriesPerRow++];
4876 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4879 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4883 for (var k = 0; k < seriesWidths.length; k++) {
4884 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4885 columnWidths[k % seriesPerRow] = seriesWidths[k];
4888 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4893 var xPositions = [];
4894 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4895 xPositions[i] = curX;
4896 curX += columnWidths[i];
4900 .attr('transform', function(d, i) {
4901 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4904 //position legend as far right as possible within the total width
4906 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4909 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4912 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4921 .attr('transform', function(d, i) {
4922 var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4925 if (width < margin.left + margin.right + xpos + length) {
4931 if (newxpos > maxwidth) maxwidth = newxpos;
4933 return 'translate(' + xpos + ',' + ypos + ')';
4936 //position legend as far right as possible within the total width
4937 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4939 height = margin.top + margin.bottom + ypos + 15;
4949 //============================================================
4950 // Expose Public Variables
4951 //------------------------------------------------------------
4953 chart.dispatch = dispatch;
4954 chart.options = nv.utils.optionsFunc.bind(chart);
4956 chart.margin = function(_) {
4957 if (!arguments.length) return margin;
4958 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4959 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4960 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4961 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4965 chart.width = function(_) {
4966 if (!arguments.length) return width;
4971 chart.height = function(_) {
4972 if (!arguments.length) return height;
4977 chart.key = function(_) {
4978 if (!arguments.length) return getKey;
4983 chart.color = function(_) {
4984 if (!arguments.length) return color;
4985 color = nv.utils.getColor(_);
4989 chart.align = function(_) {
4990 if (!arguments.length) return align;
4995 chart.rightAlign = function(_) {
4996 if (!arguments.length) return rightAlign;
5001 chart.updateState = function(_) {
5002 if (!arguments.length) return updateState;
5007 chart.radioButtonMode = function(_) {
5008 if (!arguments.length) return radioButtonMode;
5009 radioButtonMode = _;
5013 //============================================================
5019 nv.models.line = function() {
5021 //============================================================
5022 // Public Variables with Default Settings
5023 //------------------------------------------------------------
5025 var scatter = nv.models.scatter()
5028 var margin = {top: 0, right: 0, bottom: 0, left: 0}
5031 , color = nv.utils.defaultColor() // a function that returns a color
5032 , getX = function(d) { return d.x } // accessor to get the x value from a data point
5033 , getY = function(d) { return d.y } // accessor to get the y value from a data point
5034 , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
5035 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5036 , clipEdge = false // if true, masks lines within x and y scale
5037 , x //can be accessed via chart.xScale()
5038 , y //can be accessed via chart.yScale()
5039 , interpolate = "linear" // controls the line interpolation
5043 .size(16) // default size
5044 .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5047 //============================================================
5050 //============================================================
5051 // Private Variables
5052 //------------------------------------------------------------
5054 var x0, y0 //used to store previous scales
5057 //============================================================
5060 function chart(selection) {
5061 selection.each(function(data) {
5062 var availableWidth = width - margin.left - margin.right,
5063 availableHeight = height - margin.top - margin.bottom,
5064 container = d3.select(this);
5066 //------------------------------------------------------------
5069 x = scatter.xScale();
5070 y = scatter.yScale();
5075 //------------------------------------------------------------
5078 //------------------------------------------------------------
5079 // Setup containers and skeleton of chart
5081 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5082 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5083 var defsEnter = wrapEnter.append('defs');
5084 var gEnter = wrapEnter.append('g');
5085 var g = wrap.select('g')
5087 gEnter.append('g').attr('class', 'nv-groups');
5088 gEnter.append('g').attr('class', 'nv-scatterWrap');
5090 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5092 //------------------------------------------------------------
5098 .width(availableWidth)
5099 .height(availableHeight)
5101 var scatterWrap = wrap.select('.nv-scatterWrap');
5102 //.datum(data); // Data automatically trickles down from the wrap
5104 scatterWrap.transition().call(scatter);
5108 defsEnter.append('clipPath')
5109 .attr('id', 'nv-edge-clip-' + scatter.id())
5112 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5113 .attr('width', availableWidth)
5114 .attr('height', availableHeight);
5116 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5118 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5123 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5124 .data(function(d) { return d }, function(d) { return d.key });
5125 groups.enter().append('g')
5126 .style('stroke-opacity', 1e-6)
5127 .style('fill-opacity', 1e-6);
5130 .style('stroke-opacity', 1e-6)
5131 .style('fill-opacity', 1e-6)
5134 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5135 .classed('hover', function(d) { return d.hover })
5136 .style('fill', function(d,i){ return color(d, i) })
5137 .style('stroke', function(d,i){ return color(d, i)});
5140 .style('stroke-opacity', 1)
5141 .style('fill-opacity', .5);
5145 var areaPaths = groups.selectAll('path.nv-area')
5146 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5147 areaPaths.enter().append('path')
5148 .attr('class', 'nv-area')
5149 .attr('d', function(d) {
5150 return d3.svg.area()
5151 .interpolate(interpolate)
5153 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5154 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5155 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5156 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5157 .apply(this, [d.values])
5159 groups.exit().selectAll('path.nv-area')
5164 .attr('d', function(d) {
5165 return d3.svg.area()
5166 .interpolate(interpolate)
5168 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5169 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5170 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5171 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5172 .apply(this, [d.values])
5177 var linePaths = groups.selectAll('path.nv-line')
5178 .data(function(d) { return [d.values] });
5179 linePaths.enter().append('path')
5180 .attr('class', 'nv-line')
5183 .interpolate(interpolate)
5185 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5186 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5188 groups.exit().selectAll('path.nv-line')
5192 .interpolate(interpolate)
5194 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5195 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5201 .interpolate(interpolate)
5203 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5204 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5209 //store old scales for use in transitions on update
5219 //============================================================
5220 // Expose Public Variables
5221 //------------------------------------------------------------
5223 chart.dispatch = scatter.dispatch;
5224 chart.scatter = scatter;
5226 d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
5227 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5229 chart.options = nv.utils.optionsFunc.bind(chart);
5231 chart.margin = function(_) {
5232 if (!arguments.length) return margin;
5233 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5234 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5235 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5236 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5240 chart.width = function(_) {
5241 if (!arguments.length) return width;
5246 chart.height = function(_) {
5247 if (!arguments.length) return height;
5252 chart.x = function(_) {
5253 if (!arguments.length) return getX;
5259 chart.y = function(_) {
5260 if (!arguments.length) return getY;
5266 chart.clipEdge = function(_) {
5267 if (!arguments.length) return clipEdge;
5272 chart.color = function(_) {
5273 if (!arguments.length) return color;
5274 color = nv.utils.getColor(_);
5275 scatter.color(color);
5279 chart.interpolate = function(_) {
5280 if (!arguments.length) return interpolate;
5285 chart.defined = function(_) {
5286 if (!arguments.length) return defined;
5291 chart.isArea = function(_) {
5292 if (!arguments.length) return isArea;
5293 isArea = d3.functor(_);
5297 //============================================================
5303 nv.models.lineChart = function() {
5305 //============================================================
5306 // Public Variables with Default Settings
5307 //------------------------------------------------------------
5309 var lines = nv.models.line()
5310 , xAxis = nv.models.axis()
5311 , yAxis = nv.models.axis()
5312 , legend = nv.models.legend()
5313 , interactiveLayer = nv.interactiveGuideline()
5316 var margin = {top: 30, right: 20, bottom: 50, left: 60}
5317 , color = nv.utils.defaultColor()
5323 , rightAlignYAxis = false
5324 , useInteractiveGuideline = false
5326 , tooltip = function(key, x, y, e, graph) {
5327 return '<h3>' + key + '</h3>' +
5328 '<p>' + y + ' at ' + x + '</p>'
5333 , defaultState = null
5334 , noData = 'No Data Available.'
5335 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5336 , transitionDuration = 250
5344 .orient((rightAlignYAxis) ? 'right' : 'left')
5347 //============================================================
5350 //============================================================
5351 // Private Variables
5352 //------------------------------------------------------------
5354 var showTooltip = function(e, offsetElement) {
5355 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5356 top = e.pos[1] + ( offsetElement.offsetTop || 0),
5357 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5358 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5359 content = tooltip(e.series.key, x, y, e, chart);
5361 nv.tooltip.show([left, top], content, null, null, offsetElement);
5364 //============================================================
5367 function chart(selection) {
5368 selection.each(function(data) {
5369 var container = d3.select(this),
5372 var availableWidth = (width || parseInt(container.style('width')) || 960)
5373 - margin.left - margin.right,
5374 availableHeight = (height || parseInt(container.style('height')) || 400)
5375 - margin.top - margin.bottom;
5378 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5379 chart.container = this;
5381 //set state.disabled
5382 state.disabled = data.map(function(d) { return !!d.disabled });
5385 if (!defaultState) {
5388 for (key in state) {
5389 if (state[key] instanceof Array)
5390 defaultState[key] = state[key].slice(0);
5392 defaultState[key] = state[key];
5396 //------------------------------------------------------------
5397 // Display noData message if there's nothing to show.
5399 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5400 var noDataText = container.selectAll('.nv-noData').data([noData]);
5402 noDataText.enter().append('text')
5403 .attr('class', 'nvd3 nv-noData')
5404 .attr('dy', '-.7em')
5405 .style('text-anchor', 'middle');
5408 .attr('x', margin.left + availableWidth / 2)
5409 .attr('y', margin.top + availableHeight / 2)
5410 .text(function(d) { return d });
5414 container.selectAll('.nv-noData').remove();
5417 //------------------------------------------------------------
5420 //------------------------------------------------------------
5426 //------------------------------------------------------------
5429 //------------------------------------------------------------
5430 // Setup containers and skeleton of chart
5432 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5433 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5434 var g = wrap.select('g');
5436 gEnter.append("rect").style("opacity",0);
5437 gEnter.append('g').attr('class', 'nv-x nv-axis');
5438 gEnter.append('g').attr('class', 'nv-y nv-axis');
5439 gEnter.append('g').attr('class', 'nv-linesWrap');
5440 gEnter.append('g').attr('class', 'nv-legendWrap');
5441 gEnter.append('g').attr('class', 'nv-interactive');
5443 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
5444 //------------------------------------------------------------
5448 legend.width(availableWidth);
5450 g.select('.nv-legendWrap')
5454 if ( margin.top != legend.height()) {
5455 margin.top = legend.height();
5456 availableHeight = (height || parseInt(container.style('height')) || 400)
5457 - margin.top - margin.bottom;
5460 wrap.select('.nv-legendWrap')
5461 .attr('transform', 'translate(0,' + (-margin.top) +')')
5464 //------------------------------------------------------------
5466 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5468 if (rightAlignYAxis) {
5469 g.select(".nv-y.nv-axis")
5470 .attr("transform", "translate(" + availableWidth + ",0)");
5473 //------------------------------------------------------------
5474 // Main Chart Component(s)
5477 //------------------------------------------------------------
5478 //Set up interactive layer
5479 if (useInteractiveGuideline) {
5481 .width(availableWidth)
5482 .height(availableHeight)
5483 .margin({left:margin.left, top:margin.top})
5484 .svgContainer(container)
5486 wrap.select(".nv-interactive").call(interactiveLayer);
5491 .width(availableWidth)
5492 .height(availableHeight)
5493 .color(data.map(function(d,i) {
5494 return d.color || color(d, i);
5495 }).filter(function(d,i) { return !data[i].disabled }));
5498 var linesWrap = g.select('.nv-linesWrap')
5499 .datum(data.filter(function(d) { return !d.disabled }))
5501 linesWrap.transition().call(lines);
5503 //------------------------------------------------------------
5506 //------------------------------------------------------------
5512 .ticks( availableWidth / 100 )
5513 .tickSize(-availableHeight, 0);
5515 g.select('.nv-x.nv-axis')
5516 .attr('transform', 'translate(0,' + y.range()[0] + ')');
5517 g.select('.nv-x.nv-axis')
5525 .ticks( availableHeight / 36 )
5526 .tickSize( -availableWidth, 0);
5528 g.select('.nv-y.nv-axis')
5532 //------------------------------------------------------------
5535 //============================================================
5536 // Event Handling/Dispatching (in chart's scope)
5537 //------------------------------------------------------------
5539 legend.dispatch.on('stateChange', function(newState) {
5541 dispatch.stateChange(state);
5545 interactiveLayer.dispatch.on('elementMousemove', function(e) {
5546 lines.clearHighlights();
5547 var singlePoint, pointIndex, pointXLocation, allData = [];
5549 .filter(function(series, i) {
5550 series.seriesIndex = i;
5551 return !series.disabled;
5553 .forEach(function(series,i) {
5554 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5555 lines.highlightPoint(i, pointIndex, true);
5556 var point = series.values[pointIndex];
5557 if (typeof point === 'undefined') return;
5558 if (typeof singlePoint === 'undefined') singlePoint = point;
5559 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5562 value: chart.y()(point, pointIndex),
5563 color: color(series,series.seriesIndex)
5567 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5568 interactiveLayer.tooltip
5569 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5570 .chartContainer(that.parentNode)
5572 .valueFormatter(function(d,i) {
5573 return yAxis.tickFormat()(d);
5582 interactiveLayer.renderGuideLine(pointXLocation);
5586 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5587 dispatch.tooltipHide();
5588 lines.clearHighlights();
5591 dispatch.on('tooltipShow', function(e) {
5592 if (tooltips) showTooltip(e, that.parentNode);
5596 dispatch.on('changeState', function(e) {
5598 if (typeof e.disabled !== 'undefined') {
5599 data.forEach(function(series,i) {
5600 series.disabled = e.disabled[i];
5603 state.disabled = e.disabled;
5609 //============================================================
5617 //============================================================
5618 // Event Handling/Dispatching (out of chart's scope)
5619 //------------------------------------------------------------
5621 lines.dispatch.on('elementMouseover.tooltip', function(e) {
5622 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5623 dispatch.tooltipShow(e);
5626 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5627 dispatch.tooltipHide(e);
5630 dispatch.on('tooltipHide', function() {
5631 if (tooltips) nv.tooltip.cleanup();
5634 //============================================================
5637 //============================================================
5638 // Expose Public Variables
5639 //------------------------------------------------------------
5641 // expose chart's sub-components
5642 chart.dispatch = dispatch;
5643 chart.lines = lines;
5644 chart.legend = legend;
5645 chart.xAxis = xAxis;
5646 chart.yAxis = yAxis;
5647 chart.interactiveLayer = interactiveLayer;
5649 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5650 , 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5652 chart.options = nv.utils.optionsFunc.bind(chart);
5654 chart.margin = function(_) {
5655 if (!arguments.length) return margin;
5656 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5657 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5658 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5659 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5663 chart.width = function(_) {
5664 if (!arguments.length) return width;
5669 chart.height = function(_) {
5670 if (!arguments.length) return height;
5675 chart.color = function(_) {
5676 if (!arguments.length) return color;
5677 color = nv.utils.getColor(_);
5678 legend.color(color);
5682 chart.showLegend = function(_) {
5683 if (!arguments.length) return showLegend;
5688 chart.showXAxis = function(_) {
5689 if (!arguments.length) return showXAxis;
5694 chart.showYAxis = function(_) {
5695 if (!arguments.length) return showYAxis;
5700 chart.rightAlignYAxis = function(_) {
5701 if(!arguments.length) return rightAlignYAxis;
5702 rightAlignYAxis = _;
5703 yAxis.orient( (_) ? 'right' : 'left');
5707 chart.useInteractiveGuideline = function(_) {
5708 if(!arguments.length) return useInteractiveGuideline;
5709 useInteractiveGuideline = _;
5711 chart.interactive(false);
5712 chart.useVoronoi(false);
5717 chart.tooltips = function(_) {
5718 if (!arguments.length) return tooltips;
5723 chart.tooltipContent = function(_) {
5724 if (!arguments.length) return tooltip;
5729 chart.state = function(_) {
5730 if (!arguments.length) return state;
5735 chart.defaultState = function(_) {
5736 if (!arguments.length) return defaultState;
5741 chart.noData = function(_) {
5742 if (!arguments.length) return noData;
5747 chart.transitionDuration = function(_) {
5748 if (!arguments.length) return transitionDuration;
5749 transitionDuration = _;
5753 //============================================================
5759 nv.models.linePlusBarChart = function() {
5761 //============================================================
5762 // Public Variables with Default Settings
5763 //------------------------------------------------------------
5765 var lines = nv.models.line()
5766 , bars = nv.models.historicalBar()
5767 , xAxis = nv.models.axis()
5768 , y1Axis = nv.models.axis()
5769 , y2Axis = nv.models.axis()
5770 , legend = nv.models.legend()
5773 var margin = {top: 30, right: 60, bottom: 50, left: 60}
5776 , getX = function(d) { return d.x }
5777 , getY = function(d) { return d.y }
5778 , color = nv.utils.defaultColor()
5781 , tooltip = function(key, x, y, e, graph) {
5782 return '<h3>' + key + '</h3>' +
5783 '<p>' + y + ' at ' + x + '</p>';
5789 , defaultState = null
5790 , noData = "No Data Available."
5791 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5804 .highlightZero(false)
5813 //============================================================
5816 //============================================================
5817 // Private Variables
5818 //------------------------------------------------------------
5820 var showTooltip = function(e, offsetElement) {
5821 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5822 top = e.pos[1] + ( offsetElement.offsetTop || 0),
5823 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5824 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5825 content = tooltip(e.series.key, x, y, e, chart);
5827 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5831 //------------------------------------------------------------
5835 function chart(selection) {
5836 selection.each(function(data) {
5837 var container = d3.select(this),
5840 var availableWidth = (width || parseInt(container.style('width')) || 960)
5841 - margin.left - margin.right,
5842 availableHeight = (height || parseInt(container.style('height')) || 400)
5843 - margin.top - margin.bottom;
5845 chart.update = function() { container.transition().call(chart); };
5846 // chart.container = this;
5848 //set state.disabled
5849 state.disabled = data.map(function(d) { return !!d.disabled });
5851 if (!defaultState) {
5854 for (key in state) {
5855 if (state[key] instanceof Array)
5856 defaultState[key] = state[key].slice(0);
5858 defaultState[key] = state[key];
5862 //------------------------------------------------------------
5863 // Display No Data message if there's nothing to show.
5865 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5866 var noDataText = container.selectAll('.nv-noData').data([noData]);
5868 noDataText.enter().append('text')
5869 .attr('class', 'nvd3 nv-noData')
5870 .attr('dy', '-.7em')
5871 .style('text-anchor', 'middle');
5874 .attr('x', margin.left + availableWidth / 2)
5875 .attr('y', margin.top + availableHeight / 2)
5876 .text(function(d) { return d });
5880 container.selectAll('.nv-noData').remove();
5883 //------------------------------------------------------------
5886 //------------------------------------------------------------
5889 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5890 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5892 //x = xAxis.scale();
5893 x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
5894 //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
5896 y2 = lines.yScale();
5898 //------------------------------------------------------------
5900 //------------------------------------------------------------
5901 // Setup containers and skeleton of chart
5903 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5904 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5905 var g = wrap.select('g');
5907 gEnter.append('g').attr('class', 'nv-x nv-axis');
5908 gEnter.append('g').attr('class', 'nv-y1 nv-axis');
5909 gEnter.append('g').attr('class', 'nv-y2 nv-axis');
5910 gEnter.append('g').attr('class', 'nv-barsWrap');
5911 gEnter.append('g').attr('class', 'nv-linesWrap');
5912 gEnter.append('g').attr('class', 'nv-legendWrap');
5914 //------------------------------------------------------------
5917 //------------------------------------------------------------
5921 legend.width( availableWidth / 2 );
5923 g.select('.nv-legendWrap')
5924 .datum(data.map(function(series) {
5925 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5926 series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5931 if ( margin.top != legend.height()) {
5932 margin.top = legend.height();
5933 availableHeight = (height || parseInt(container.style('height')) || 400)
5934 - margin.top - margin.bottom;
5937 g.select('.nv-legendWrap')
5938 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5941 //------------------------------------------------------------
5944 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5947 //------------------------------------------------------------
5948 // Main Chart Component(s)
5952 .width(availableWidth)
5953 .height(availableHeight)
5954 .color(data.map(function(d,i) {
5955 return d.color || color(d, i);
5956 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
5959 .width(availableWidth)
5960 .height(availableHeight)
5961 .color(data.map(function(d,i) {
5962 return d.color || color(d, i);
5963 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
5967 var barsWrap = g.select('.nv-barsWrap')
5968 .datum(dataBars.length ? dataBars : [{values:[]}])
5970 var linesWrap = g.select('.nv-linesWrap')
5971 .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
5972 //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
5974 d3.transition(barsWrap).call(bars);
5975 d3.transition(linesWrap).call(lines);
5977 //------------------------------------------------------------
5980 //------------------------------------------------------------
5985 .ticks( availableWidth / 100 )
5986 .tickSize(-availableHeight, 0);
5988 g.select('.nv-x.nv-axis')
5989 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5990 d3.transition(g.select('.nv-x.nv-axis'))
5996 .ticks( availableHeight / 36 )
5997 .tickSize(-availableWidth, 0);
5999 d3.transition(g.select('.nv-y1.nv-axis'))
6000 .style('opacity', dataBars.length ? 1 : 0)
6006 .ticks( availableHeight / 36 )
6007 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6009 g.select('.nv-y2.nv-axis')
6010 .style('opacity', dataLines.length ? 1 : 0)
6011 .attr('transform', 'translate(' + availableWidth + ',0)');
6012 //.attr('transform', 'translate(' + x.range()[1] + ',0)');
6014 d3.transition(g.select('.nv-y2.nv-axis'))
6017 //------------------------------------------------------------
6020 //============================================================
6021 // Event Handling/Dispatching (in chart's scope)
6022 //------------------------------------------------------------
6024 legend.dispatch.on('stateChange', function(newState) {
6026 dispatch.stateChange(state);
6030 dispatch.on('tooltipShow', function(e) {
6031 if (tooltips) showTooltip(e, that.parentNode);
6035 // Update chart from a state object passed to event handler
6036 dispatch.on('changeState', function(e) {
6038 if (typeof e.disabled !== 'undefined') {
6039 data.forEach(function(series,i) {
6040 series.disabled = e.disabled[i];
6043 state.disabled = e.disabled;
6049 //============================================================
6058 //============================================================
6059 // Event Handling/Dispatching (out of chart's scope)
6060 //------------------------------------------------------------
6062 lines.dispatch.on('elementMouseover.tooltip', function(e) {
6063 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6064 dispatch.tooltipShow(e);
6067 lines.dispatch.on('elementMouseout.tooltip', function(e) {
6068 dispatch.tooltipHide(e);
6071 bars.dispatch.on('elementMouseover.tooltip', function(e) {
6072 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6073 dispatch.tooltipShow(e);
6076 bars.dispatch.on('elementMouseout.tooltip', function(e) {
6077 dispatch.tooltipHide(e);
6080 dispatch.on('tooltipHide', function() {
6081 if (tooltips) nv.tooltip.cleanup();
6084 //============================================================
6087 //============================================================
6088 // Expose Public Variables
6089 //------------------------------------------------------------
6091 // expose chart's sub-components
6092 chart.dispatch = dispatch;
6093 chart.legend = legend;
6094 chart.lines = lines;
6096 chart.xAxis = xAxis;
6097 chart.y1Axis = y1Axis;
6098 chart.y2Axis = y2Axis;
6100 d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6101 //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6102 //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6104 chart.options = nv.utils.optionsFunc.bind(chart);
6106 chart.x = function(_) {
6107 if (!arguments.length) return getX;
6114 chart.y = function(_) {
6115 if (!arguments.length) return getY;
6122 chart.margin = function(_) {
6123 if (!arguments.length) return margin;
6124 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6125 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6126 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6127 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6131 chart.width = function(_) {
6132 if (!arguments.length) return width;
6137 chart.height = function(_) {
6138 if (!arguments.length) return height;
6143 chart.color = function(_) {
6144 if (!arguments.length) return color;
6145 color = nv.utils.getColor(_);
6146 legend.color(color);
6150 chart.showLegend = function(_) {
6151 if (!arguments.length) return showLegend;
6156 chart.tooltips = function(_) {
6157 if (!arguments.length) return tooltips;
6162 chart.tooltipContent = function(_) {
6163 if (!arguments.length) return tooltip;
6168 chart.state = function(_) {
6169 if (!arguments.length) return state;
6174 chart.defaultState = function(_) {
6175 if (!arguments.length) return defaultState;
6180 chart.noData = function(_) {
6181 if (!arguments.length) return noData;
6186 //============================================================
6191 nv.models.lineWithFocusChart = function() {
6193 //============================================================
6194 // Public Variables with Default Settings
6195 //------------------------------------------------------------
6197 var lines = nv.models.line()
6198 , lines2 = nv.models.line()
6199 , xAxis = nv.models.axis()
6200 , yAxis = nv.models.axis()
6201 , x2Axis = nv.models.axis()
6202 , y2Axis = nv.models.axis()
6203 , legend = nv.models.legend()
6204 , brush = d3.svg.brush()
6207 var margin = {top: 30, right: 30, bottom: 30, left: 60}
6208 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6209 , color = nv.utils.defaultColor()
6218 , brushExtent = null
6220 , tooltip = function(key, x, y, e, graph) {
6221 return '<h3>' + key + '</h3>' +
6222 '<p>' + y + ' at ' + x + '</p>'
6224 , noData = "No Data Available."
6225 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6226 , transitionDuration = 250
6249 //============================================================
6252 //============================================================
6253 // Private Variables
6254 //------------------------------------------------------------
6256 var showTooltip = function(e, offsetElement) {
6257 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6258 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6259 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6260 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
6261 content = tooltip(e.series.key, x, y, e, chart);
6263 nv.tooltip.show([left, top], content, null, null, offsetElement);
6266 //============================================================
6269 function chart(selection) {
6270 selection.each(function(data) {
6271 var container = d3.select(this),
6274 var availableWidth = (width || parseInt(container.style('width')) || 960)
6275 - margin.left - margin.right,
6276 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6277 - margin.top - margin.bottom - height2,
6278 availableHeight2 = height2 - margin2.top - margin2.bottom;
6280 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
6281 chart.container = this;
6284 //------------------------------------------------------------
6285 // Display No Data message if there's nothing to show.
6287 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6288 var noDataText = container.selectAll('.nv-noData').data([noData]);
6290 noDataText.enter().append('text')
6291 .attr('class', 'nvd3 nv-noData')
6292 .attr('dy', '-.7em')
6293 .style('text-anchor', 'middle');
6296 .attr('x', margin.left + availableWidth / 2)
6297 .attr('y', margin.top + availableHeight1 / 2)
6298 .text(function(d) { return d });
6302 container.selectAll('.nv-noData').remove();
6305 //------------------------------------------------------------
6308 //------------------------------------------------------------
6313 x2 = lines2.xScale();
6314 y2 = lines2.yScale();
6316 //------------------------------------------------------------
6319 //------------------------------------------------------------
6320 // Setup containers and skeleton of chart
6322 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
6323 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
6324 var g = wrap.select('g');
6326 gEnter.append('g').attr('class', 'nv-legendWrap');
6328 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6329 focusEnter.append('g').attr('class', 'nv-x nv-axis');
6330 focusEnter.append('g').attr('class', 'nv-y nv-axis');
6331 focusEnter.append('g').attr('class', 'nv-linesWrap');
6333 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6334 contextEnter.append('g').attr('class', 'nv-x nv-axis');
6335 contextEnter.append('g').attr('class', 'nv-y nv-axis');
6336 contextEnter.append('g').attr('class', 'nv-linesWrap');
6337 contextEnter.append('g').attr('class', 'nv-brushBackground');
6338 contextEnter.append('g').attr('class', 'nv-x nv-brush');
6340 //------------------------------------------------------------
6343 //------------------------------------------------------------
6347 legend.width(availableWidth);
6349 g.select('.nv-legendWrap')
6353 if ( margin.top != legend.height()) {
6354 margin.top = legend.height();
6355 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6356 - margin.top - margin.bottom - height2;
6359 g.select('.nv-legendWrap')
6360 .attr('transform', 'translate(0,' + (-margin.top) +')')
6363 //------------------------------------------------------------
6366 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6369 //------------------------------------------------------------
6370 // Main Chart Component(s)
6373 .width(availableWidth)
6374 .height(availableHeight1)
6377 .map(function(d,i) {
6378 return d.color || color(d, i);
6380 .filter(function(d,i) {
6381 return !data[i].disabled;
6386 .defined(lines.defined())
6387 .width(availableWidth)
6388 .height(availableHeight2)
6391 .map(function(d,i) {
6392 return d.color || color(d, i);
6394 .filter(function(d,i) {
6395 return !data[i].disabled;
6399 g.select('.nv-context')
6400 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6402 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
6403 .datum(data.filter(function(d) { return !d.disabled }))
6405 d3.transition(contextLinesWrap).call(lines2);
6407 //------------------------------------------------------------
6411 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6412 .datum(data.filter(function(d) { return !d.disabled }))
6414 d3.transition(focusLinesWrap).call(lines);
6418 //------------------------------------------------------------
6419 // Setup Main (Focus) Axes
6423 .ticks( availableWidth / 100 )
6424 .tickSize(-availableHeight1, 0);
6428 .ticks( availableHeight1 / 36 )
6429 .tickSize( -availableWidth, 0);
6431 g.select('.nv-focus .nv-x.nv-axis')
6432 .attr('transform', 'translate(0,' + availableHeight1 + ')');
6434 //------------------------------------------------------------
6437 //------------------------------------------------------------
6442 .on('brush', function() {
6443 //When brushing, turn off transitions because chart needs to change immediately.
6444 var oldTransition = chart.transitionDuration();
6445 chart.transitionDuration(0);
6447 chart.transitionDuration(oldTransition);
6450 if (brushExtent) brush.extent(brushExtent);
6452 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6453 .data([brushExtent || brush.extent()])
6455 var brushBGenter = brushBG.enter()
6458 brushBGenter.append('rect')
6459 .attr('class', 'left')
6462 .attr('height', availableHeight2);
6464 brushBGenter.append('rect')
6465 .attr('class', 'right')
6468 .attr('height', availableHeight2);
6470 var gBrush = g.select('.nv-x.nv-brush')
6472 gBrush.selectAll('rect')
6474 .attr('height', availableHeight2);
6475 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6479 //------------------------------------------------------------
6482 //------------------------------------------------------------
6483 // Setup Secondary (Context) Axes
6487 .ticks( availableWidth / 100 )
6488 .tickSize(-availableHeight2, 0);
6490 g.select('.nv-context .nv-x.nv-axis')
6491 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6492 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6498 .ticks( availableHeight2 / 36 )
6499 .tickSize( -availableWidth, 0);
6501 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6504 g.select('.nv-context .nv-x.nv-axis')
6505 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6507 //------------------------------------------------------------
6510 //============================================================
6511 // Event Handling/Dispatching (in chart's scope)
6512 //------------------------------------------------------------
6514 legend.dispatch.on('stateChange', function(newState) {
6518 dispatch.on('tooltipShow', function(e) {
6519 if (tooltips) showTooltip(e, that.parentNode);
6522 //============================================================
6525 //============================================================
6527 //------------------------------------------------------------
6529 // Taken from crossfilter (http://square.github.com/crossfilter/)
6530 function resizePath(d) {
6531 var e = +(d == 'e'),
6533 y = availableHeight2 / 3;
6534 return 'M' + (.5 * x) + ',' + y
6535 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6537 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6539 + 'M' + (2.5 * x) + ',' + (y + 8)
6541 + 'M' + (4.5 * x) + ',' + (y + 8)
6542 + 'V' + (2 * y - 8);
6546 function updateBrushBG() {
6547 if (!brush.empty()) brush.extent(brushExtent);
6549 .data([brush.empty() ? x2.domain() : brushExtent])
6550 .each(function(d,i) {
6551 var leftWidth = x2(d[0]) - x.range()[0],
6552 rightWidth = x.range()[1] - x2(d[1]);
6553 d3.select(this).select('.left')
6554 .attr('width', leftWidth < 0 ? 0 : leftWidth);
6556 d3.select(this).select('.right')
6557 .attr('x', x2(d[1]))
6558 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6563 function onBrush() {
6564 brushExtent = brush.empty() ? null : brush.extent();
6565 var extent = brush.empty() ? x2.domain() : brush.extent();
6567 //The brush extent cannot be less than one. If it is, don't update the line chart.
6568 if (Math.abs(extent[0] - extent[1]) <= 1) {
6572 dispatch.brush({extent: extent, brush: brush});
6577 // Update Main (Focus)
6578 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6581 .filter(function(d) { return !d.disabled })
6582 .map(function(d,i) {
6585 values: d.values.filter(function(d,i) {
6586 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6591 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6594 // Update Main (Focus) Axes
6595 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6597 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6601 //============================================================
6610 //============================================================
6611 // Event Handling/Dispatching (out of chart's scope)
6612 //------------------------------------------------------------
6614 lines.dispatch.on('elementMouseover.tooltip', function(e) {
6615 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6616 dispatch.tooltipShow(e);
6619 lines.dispatch.on('elementMouseout.tooltip', function(e) {
6620 dispatch.tooltipHide(e);
6623 dispatch.on('tooltipHide', function() {
6624 if (tooltips) nv.tooltip.cleanup();
6627 //============================================================
6630 //============================================================
6631 // Expose Public Variables
6632 //------------------------------------------------------------
6634 // expose chart's sub-components
6635 chart.dispatch = dispatch;
6636 chart.legend = legend;
6637 chart.lines = lines;
6638 chart.lines2 = lines2;
6639 chart.xAxis = xAxis;
6640 chart.yAxis = yAxis;
6641 chart.x2Axis = x2Axis;
6642 chart.y2Axis = y2Axis;
6644 d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6646 chart.options = nv.utils.optionsFunc.bind(chart);
6648 chart.x = function(_) {
6649 if (!arguments.length) return lines.x;
6655 chart.y = function(_) {
6656 if (!arguments.length) return lines.y;
6662 chart.margin = function(_) {
6663 if (!arguments.length) return margin;
6664 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6665 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6666 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6667 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6671 chart.margin2 = function(_) {
6672 if (!arguments.length) return margin2;
6677 chart.width = function(_) {
6678 if (!arguments.length) return width;
6683 chart.height = function(_) {
6684 if (!arguments.length) return height;
6689 chart.height2 = function(_) {
6690 if (!arguments.length) return height2;
6695 chart.color = function(_) {
6696 if (!arguments.length) return color;
6697 color =nv.utils.getColor(_);
6698 legend.color(color);
6702 chart.showLegend = function(_) {
6703 if (!arguments.length) return showLegend;
6708 chart.tooltips = function(_) {
6709 if (!arguments.length) return tooltips;
6714 chart.tooltipContent = function(_) {
6715 if (!arguments.length) return tooltip;
6720 chart.interpolate = function(_) {
6721 if (!arguments.length) return lines.interpolate();
6722 lines.interpolate(_);
6723 lines2.interpolate(_);
6727 chart.noData = function(_) {
6728 if (!arguments.length) return noData;
6733 // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
6734 chart.xTickFormat = function(_) {
6735 if (!arguments.length) return xAxis.tickFormat();
6736 xAxis.tickFormat(_);
6737 x2Axis.tickFormat(_);
6741 chart.yTickFormat = function(_) {
6742 if (!arguments.length) return yAxis.tickFormat();
6743 yAxis.tickFormat(_);
6744 y2Axis.tickFormat(_);
6748 chart.brushExtent = function(_) {
6749 if (!arguments.length) return brushExtent;
6754 chart.transitionDuration = function(_) {
6755 if (!arguments.length) return transitionDuration;
6756 transitionDuration = _;
6760 //============================================================
6766 nv.models.linePlusBarWithFocusChart = function() {
6768 //============================================================
6769 // Public Variables with Default Settings
6770 //------------------------------------------------------------
6772 var lines = nv.models.line()
6773 , lines2 = nv.models.line()
6774 , bars = nv.models.historicalBar()
6775 , bars2 = nv.models.historicalBar()
6776 , xAxis = nv.models.axis()
6777 , x2Axis = nv.models.axis()
6778 , y1Axis = nv.models.axis()
6779 , y2Axis = nv.models.axis()
6780 , y3Axis = nv.models.axis()
6781 , y4Axis = nv.models.axis()
6782 , legend = nv.models.legend()
6783 , brush = d3.svg.brush()
6786 var margin = {top: 30, right: 30, bottom: 30, left: 60}
6787 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6791 , getX = function(d) { return d.x }
6792 , getY = function(d) { return d.y }
6793 , color = nv.utils.defaultColor()
6796 , brushExtent = null
6798 , tooltip = function(key, x, y, e, graph) {
6799 return '<h3>' + key + '</h3>' +
6800 '<p>' + y + ' at ' + x + '</p>';
6808 , noData = "No Data Available."
6809 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6810 , transitionDuration = 0
6840 //============================================================
6843 //============================================================
6844 // Private Variables
6845 //------------------------------------------------------------
6847 var showTooltip = function(e, offsetElement) {
6849 e.pointIndex += Math.ceil(extent[0]);
6851 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6852 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6853 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6854 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6855 content = tooltip(e.series.key, x, y, e, chart);
6857 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6860 //------------------------------------------------------------
6864 function chart(selection) {
6865 selection.each(function(data) {
6866 var container = d3.select(this),
6869 var availableWidth = (width || parseInt(container.style('width')) || 960)
6870 - margin.left - margin.right,
6871 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6872 - margin.top - margin.bottom - height2,
6873 availableHeight2 = height2 - margin2.top - margin2.bottom;
6875 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6876 chart.container = this;
6879 //------------------------------------------------------------
6880 // Display No Data message if there's nothing to show.
6882 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6883 var noDataText = container.selectAll('.nv-noData').data([noData]);
6885 noDataText.enter().append('text')
6886 .attr('class', 'nvd3 nv-noData')
6887 .attr('dy', '-.7em')
6888 .style('text-anchor', 'middle');
6891 .attr('x', margin.left + availableWidth / 2)
6892 .attr('y', margin.top + availableHeight1 / 2)
6893 .text(function(d) { return d });
6897 container.selectAll('.nv-noData').remove();
6900 //------------------------------------------------------------
6903 //------------------------------------------------------------
6906 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6907 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6910 x2 = x2Axis.scale();
6912 y2 = lines.yScale();
6913 y3 = bars2.yScale();
6914 y4 = lines2.yScale();
6917 .filter(function(d) { return !d.disabled && d.bar })
6919 return d.values.map(function(d,i) {
6920 return { x: getX(d,i), y: getY(d,i) }
6925 .filter(function(d) { return !d.disabled && !d.bar })
6927 return d.values.map(function(d,i) {
6928 return { x: getX(d,i), y: getY(d,i) }
6932 x .range([0, availableWidth]);
6934 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6935 .range([0, availableWidth]);
6938 //------------------------------------------------------------
6941 //------------------------------------------------------------
6942 // Setup containers and skeleton of chart
6944 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6945 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6946 var g = wrap.select('g');
6948 gEnter.append('g').attr('class', 'nv-legendWrap');
6950 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6951 focusEnter.append('g').attr('class', 'nv-x nv-axis');
6952 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6953 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6954 focusEnter.append('g').attr('class', 'nv-barsWrap');
6955 focusEnter.append('g').attr('class', 'nv-linesWrap');
6957 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6958 contextEnter.append('g').attr('class', 'nv-x nv-axis');
6959 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6960 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6961 contextEnter.append('g').attr('class', 'nv-barsWrap');
6962 contextEnter.append('g').attr('class', 'nv-linesWrap');
6963 contextEnter.append('g').attr('class', 'nv-brushBackground');
6964 contextEnter.append('g').attr('class', 'nv-x nv-brush');
6967 //------------------------------------------------------------
6970 //------------------------------------------------------------
6974 legend.width( availableWidth / 2 );
6976 g.select('.nv-legendWrap')
6977 .datum(data.map(function(series) {
6978 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6979 series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6984 if ( margin.top != legend.height()) {
6985 margin.top = legend.height();
6986 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6987 - margin.top - margin.bottom - height2;
6990 g.select('.nv-legendWrap')
6991 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6994 //------------------------------------------------------------
6997 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7000 //------------------------------------------------------------
7001 // Context Components
7004 .width(availableWidth)
7005 .height(availableHeight2)
7006 .color(data.map(function(d,i) {
7007 return d.color || color(d, i);
7008 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7011 .width(availableWidth)
7012 .height(availableHeight2)
7013 .color(data.map(function(d,i) {
7014 return d.color || color(d, i);
7015 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7017 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
7018 .datum(dataBars.length ? dataBars : [{values:[]}]);
7020 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
7021 .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
7023 g.select('.nv-context')
7024 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7026 bars2Wrap.transition().call(bars2);
7027 lines2Wrap.transition().call(lines2);
7029 //------------------------------------------------------------
7033 //------------------------------------------------------------
7038 .on('brush', onBrush);
7040 if (brushExtent) brush.extent(brushExtent);
7042 var brushBG = g.select('.nv-brushBackground').selectAll('g')
7043 .data([brushExtent || brush.extent()])
7045 var brushBGenter = brushBG.enter()
7048 brushBGenter.append('rect')
7049 .attr('class', 'left')
7052 .attr('height', availableHeight2);
7054 brushBGenter.append('rect')
7055 .attr('class', 'right')
7058 .attr('height', availableHeight2);
7060 var gBrush = g.select('.nv-x.nv-brush')
7062 gBrush.selectAll('rect')
7064 .attr('height', availableHeight2);
7065 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7067 //------------------------------------------------------------
7069 //------------------------------------------------------------
7070 // Setup Secondary (Context) Axes
7073 .ticks( availableWidth / 100 )
7074 .tickSize(-availableHeight2, 0);
7076 g.select('.nv-context .nv-x.nv-axis')
7077 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
7078 g.select('.nv-context .nv-x.nv-axis').transition()
7084 .ticks( availableHeight2 / 36 )
7085 .tickSize( -availableWidth, 0);
7087 g.select('.nv-context .nv-y1.nv-axis')
7088 .style('opacity', dataBars.length ? 1 : 0)
7089 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
7091 g.select('.nv-context .nv-y1.nv-axis').transition()
7097 .ticks( availableHeight2 / 36 )
7098 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7100 g.select('.nv-context .nv-y2.nv-axis')
7101 .style('opacity', dataLines.length ? 1 : 0)
7102 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
7104 g.select('.nv-context .nv-y2.nv-axis').transition()
7107 //------------------------------------------------------------
7109 //============================================================
7110 // Event Handling/Dispatching (in chart's scope)
7111 //------------------------------------------------------------
7113 legend.dispatch.on('stateChange', function(newState) {
7117 dispatch.on('tooltipShow', function(e) {
7118 if (tooltips) showTooltip(e, that.parentNode);
7121 //============================================================
7124 //============================================================
7126 //------------------------------------------------------------
7128 // Taken from crossfilter (http://square.github.com/crossfilter/)
7129 function resizePath(d) {
7130 var e = +(d == 'e'),
7132 y = availableHeight2 / 3;
7133 return 'M' + (.5 * x) + ',' + y
7134 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7136 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7138 + 'M' + (2.5 * x) + ',' + (y + 8)
7140 + 'M' + (4.5 * x) + ',' + (y + 8)
7141 + 'V' + (2 * y - 8);
7145 function updateBrushBG() {
7146 if (!brush.empty()) brush.extent(brushExtent);
7148 .data([brush.empty() ? x2.domain() : brushExtent])
7149 .each(function(d,i) {
7150 var leftWidth = x2(d[0]) - x2.range()[0],
7151 rightWidth = x2.range()[1] - x2(d[1]);
7152 d3.select(this).select('.left')
7153 .attr('width', leftWidth < 0 ? 0 : leftWidth);
7155 d3.select(this).select('.right')
7156 .attr('x', x2(d[1]))
7157 .attr('width', rightWidth < 0 ? 0 : rightWidth);
7162 function onBrush() {
7163 brushExtent = brush.empty() ? null : brush.extent();
7164 extent = brush.empty() ? x2.domain() : brush.extent();
7167 dispatch.brush({extent: extent, brush: brush});
7172 //------------------------------------------------------------
7173 // Prepare Main (Focus) Bars and Lines
7176 .width(availableWidth)
7177 .height(availableHeight1)
7178 .color(data.map(function(d,i) {
7179 return d.color || color(d, i);
7180 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7184 .width(availableWidth)
7185 .height(availableHeight1)
7186 .color(data.map(function(d,i) {
7187 return d.color || color(d, i);
7188 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7190 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7191 .datum(!dataBars.length ? [{values:[]}] :
7193 .map(function(d,i) {
7196 values: d.values.filter(function(d,i) {
7197 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7203 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7204 .datum(dataLines[0].disabled ? [{values:[]}] :
7206 .map(function(d,i) {
7209 values: d.values.filter(function(d,i) {
7210 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7216 //------------------------------------------------------------
7219 //------------------------------------------------------------
7220 // Update Main (Focus) X Axis
7222 if (dataBars.length) {
7230 .ticks( availableWidth / 100 )
7231 .tickSize(-availableHeight1, 0);
7233 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7235 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7237 //------------------------------------------------------------
7240 //------------------------------------------------------------
7241 // Update Main (Focus) Bars and Lines
7243 focusBarsWrap.transition().duration(transitionDuration).call(bars);
7244 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7246 //------------------------------------------------------------
7249 //------------------------------------------------------------
7250 // Setup and Update Main (Focus) Y Axes
7252 g.select('.nv-focus .nv-x.nv-axis')
7253 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
7258 .ticks( availableHeight1 / 36 )
7259 .tickSize(-availableWidth, 0);
7261 g.select('.nv-focus .nv-y1.nv-axis')
7262 .style('opacity', dataBars.length ? 1 : 0);
7267 .ticks( availableHeight1 / 36 )
7268 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7270 g.select('.nv-focus .nv-y2.nv-axis')
7271 .style('opacity', dataLines.length ? 1 : 0)
7272 .attr('transform', 'translate(' + x.range()[1] + ',0)');
7274 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7276 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7280 //============================================================
7290 //============================================================
7291 // Event Handling/Dispatching (out of chart's scope)
7292 //------------------------------------------------------------
7294 lines.dispatch.on('elementMouseover.tooltip', function(e) {
7295 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7296 dispatch.tooltipShow(e);
7299 lines.dispatch.on('elementMouseout.tooltip', function(e) {
7300 dispatch.tooltipHide(e);
7303 bars.dispatch.on('elementMouseover.tooltip', function(e) {
7304 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7305 dispatch.tooltipShow(e);
7308 bars.dispatch.on('elementMouseout.tooltip', function(e) {
7309 dispatch.tooltipHide(e);
7312 dispatch.on('tooltipHide', function() {
7313 if (tooltips) nv.tooltip.cleanup();
7316 //============================================================
7319 //============================================================
7320 // Expose Public Variables
7321 //------------------------------------------------------------
7323 // expose chart's sub-components
7324 chart.dispatch = dispatch;
7325 chart.legend = legend;
7326 chart.lines = lines;
7327 chart.lines2 = lines2;
7329 chart.bars2 = bars2;
7330 chart.xAxis = xAxis;
7331 chart.x2Axis = x2Axis;
7332 chart.y1Axis = y1Axis;
7333 chart.y2Axis = y2Axis;
7334 chart.y3Axis = y3Axis;
7335 chart.y4Axis = y4Axis;
7337 d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7338 //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7339 //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7341 chart.options = nv.utils.optionsFunc.bind(chart);
7343 chart.x = function(_) {
7344 if (!arguments.length) return getX;
7351 chart.y = function(_) {
7352 if (!arguments.length) return getY;
7359 chart.margin = function(_) {
7360 if (!arguments.length) return margin;
7361 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7362 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7363 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7364 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7368 chart.width = function(_) {
7369 if (!arguments.length) return width;
7374 chart.height = function(_) {
7375 if (!arguments.length) return height;
7380 chart.color = function(_) {
7381 if (!arguments.length) return color;
7382 color = nv.utils.getColor(_);
7383 legend.color(color);
7387 chart.showLegend = function(_) {
7388 if (!arguments.length) return showLegend;
7393 chart.tooltips = function(_) {
7394 if (!arguments.length) return tooltips;
7399 chart.tooltipContent = function(_) {
7400 if (!arguments.length) return tooltip;
7405 chart.noData = function(_) {
7406 if (!arguments.length) return noData;
7411 chart.brushExtent = function(_) {
7412 if (!arguments.length) return brushExtent;
7418 //============================================================
7424 nv.models.multiBar = function() {
7426 //============================================================
7427 // Public Variables with Default Settings
7428 //------------------------------------------------------------
7430 var margin = {top: 0, right: 0, bottom: 0, left: 0}
7433 , x = d3.scale.ordinal()
7434 , y = d3.scale.linear()
7435 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7436 , getX = function(d) { return d.x }
7437 , getY = function(d) { return d.y }
7438 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7441 , color = nv.utils.defaultColor()
7443 , barColor = null // adding the ability to set the color for each rather than the whole group
7444 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7450 , groupSpacing = 0.1
7451 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7454 //============================================================
7457 //============================================================
7458 // Private Variables
7459 //------------------------------------------------------------
7461 var x0, y0 //used to store previous scales
7464 //============================================================
7467 function chart(selection) {
7468 selection.each(function(data) {
7469 var availableWidth = width - margin.left - margin.right,
7470 availableHeight = height - margin.top - margin.bottom,
7471 container = d3.select(this);
7473 if(hideable && data.length) hideable = [{
7474 values: data[0].values.map(function(d) {
7484 data = d3.layout.stack()
7486 .values(function(d){ return d.values })
7488 (!data.length && hideable ? hideable : data);
7491 //add series index to each data point for reference
7492 data = data.map(function(series, i) {
7493 series.values = series.values.map(function(point) {
7501 //------------------------------------------------------------
7502 // HACK for negative value stacking
7504 data[0].values.map(function(d,i) {
7505 var posBase = 0, negBase = 0;
7506 data.map(function(d) {
7508 f.size = Math.abs(f.y);
7511 negBase = negBase - f.size;
7514 f.y1 = f.size + posBase;
7515 posBase = posBase + f.size;
7520 //------------------------------------------------------------
7523 // remap and flatten the data for use in calculating the scales' domains
7524 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7525 data.map(function(d) {
7526 return d.values.map(function(d,i) {
7527 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7531 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7532 .rangeBands(xRange || [0, availableWidth], groupSpacing);
7534 //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
7535 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
7536 .range(yRange || [availableHeight, 0]);
7538 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7539 if (x.domain()[0] === x.domain()[1])
7541 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7544 if (y.domain()[0] === y.domain()[1])
7546 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7553 //------------------------------------------------------------
7556 //------------------------------------------------------------
7557 // Setup containers and skeleton of chart
7559 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7560 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7561 var defsEnter = wrapEnter.append('defs');
7562 var gEnter = wrapEnter.append('g');
7563 var g = wrap.select('g')
7565 gEnter.append('g').attr('class', 'nv-groups');
7567 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7569 //------------------------------------------------------------
7573 defsEnter.append('clipPath')
7574 .attr('id', 'nv-edge-clip-' + id)
7576 wrap.select('#nv-edge-clip-' + id + ' rect')
7577 .attr('width', availableWidth)
7578 .attr('height', availableHeight);
7580 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7584 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7585 .data(function(d) { return d }, function(d,i) { return i });
7586 groups.enter().append('g')
7587 .style('stroke-opacity', 1e-6)
7588 .style('fill-opacity', 1e-6);
7591 .selectAll('rect.nv-bar')
7592 .delay(function(d,i) {
7593 return i * delay/ data[0].values.length;
7595 .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
7599 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7600 .classed('hover', function(d) { return d.hover })
7601 .style('fill', function(d,i){ return color(d, i) })
7602 .style('stroke', function(d,i){ return color(d, i) });
7605 .style('stroke-opacity', 1)
7606 .style('fill-opacity', .75);
7609 var bars = groups.selectAll('rect.nv-bar')
7610 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7612 bars.exit().remove();
7615 var barsEnter = bars.enter().append('rect')
7616 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7617 .attr('x', function(d,i,j) {
7618 return stacked ? 0 : (j * x.rangeBand() / data.length )
7620 .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
7622 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
7623 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7626 .style('fill', function(d,i,j){ return color(d, j, i); })
7627 .style('stroke', function(d,i,j){ return color(d, j, i); })
7628 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7629 d3.select(this).classed('hover', true);
7630 dispatch.elementMouseover({
7633 series: data[d.series],
7634 pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7636 seriesIndex: d.series,
7640 .on('mouseout', function(d,i) {
7641 d3.select(this).classed('hover', false);
7642 dispatch.elementMouseout({
7645 series: data[d.series],
7647 seriesIndex: d.series,
7651 .on('click', function(d,i) {
7652 dispatch.elementClick({
7655 series: data[d.series],
7656 pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7658 seriesIndex: d.series,
7661 d3.event.stopPropagation();
7663 .on('dblclick', function(d,i) {
7664 dispatch.elementDblClick({
7667 series: data[d.series],
7668 pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7670 seriesIndex: d.series,
7673 d3.event.stopPropagation();
7676 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7678 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7681 if (!disabled) disabled = data.map(function() { return true });
7683 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
7684 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
7690 .delay(function(d,i) {
7692 return i * delay / data[0].values.length;
7694 .attr('y', function(d,i) {
7696 return y((stacked ? d.y1 : 0));
7698 .attr('height', function(d,i) {
7699 return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
7701 .attr('x', function(d,i) {
7702 return stacked ? 0 : (d.series * x.rangeBand() / data.length )
7704 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
7707 .delay(function(d,i) {
7708 return i * delay/ data[0].values.length;
7710 .attr('x', function(d,i) {
7711 return d.series * x.rangeBand() / data.length
7713 .attr('width', x.rangeBand() / data.length)
7714 .attr('y', function(d,i) {
7715 return getY(d,i) < 0 ?
7717 y(0) - y(getY(d,i)) < 1 ?
7721 .attr('height', function(d,i) {
7722 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7727 //store old scales for use in transitions on update
7737 //============================================================
7738 // Expose Public Variables
7739 //------------------------------------------------------------
7741 chart.dispatch = dispatch;
7743 chart.options = nv.utils.optionsFunc.bind(chart);
7745 chart.x = function(_) {
7746 if (!arguments.length) return getX;
7751 chart.y = function(_) {
7752 if (!arguments.length) return getY;
7757 chart.margin = function(_) {
7758 if (!arguments.length) return margin;
7759 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7760 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7761 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7762 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7766 chart.width = function(_) {
7767 if (!arguments.length) return width;
7772 chart.height = function(_) {
7773 if (!arguments.length) return height;
7778 chart.xScale = function(_) {
7779 if (!arguments.length) return x;
7784 chart.yScale = function(_) {
7785 if (!arguments.length) return y;
7790 chart.xDomain = function(_) {
7791 if (!arguments.length) return xDomain;
7796 chart.yDomain = function(_) {
7797 if (!arguments.length) return yDomain;
7802 chart.xRange = function(_) {
7803 if (!arguments.length) return xRange;
7808 chart.yRange = function(_) {
7809 if (!arguments.length) return yRange;
7814 chart.forceY = function(_) {
7815 if (!arguments.length) return forceY;
7820 chart.stacked = function(_) {
7821 if (!arguments.length) return stacked;
7826 chart.clipEdge = function(_) {
7827 if (!arguments.length) return clipEdge;
7832 chart.color = function(_) {
7833 if (!arguments.length) return color;
7834 color = nv.utils.getColor(_);
7838 chart.barColor = function(_) {
7839 if (!arguments.length) return barColor;
7840 barColor = nv.utils.getColor(_);
7844 chart.disabled = function(_) {
7845 if (!arguments.length) return disabled;
7850 chart.id = function(_) {
7851 if (!arguments.length) return id;
7856 chart.hideable = function(_) {
7857 if (!arguments.length) return hideable;
7862 chart.delay = function(_) {
7863 if (!arguments.length) return delay;
7868 chart.groupSpacing = function(_) {
7869 if (!arguments.length) return groupSpacing;
7874 //============================================================
7880 nv.models.multiBarChart = function() {
7882 //============================================================
7883 // Public Variables with Default Settings
7884 //------------------------------------------------------------
7886 var multibar = nv.models.multiBar()
7887 , xAxis = nv.models.axis()
7888 , yAxis = nv.models.axis()
7889 , legend = nv.models.legend()
7890 , controls = nv.models.legend()
7893 var margin = {top: 30, right: 20, bottom: 50, left: 60}
7896 , color = nv.utils.defaultColor()
7897 , showControls = true
7901 , rightAlignYAxis = false
7902 , reduceXTicks = true // if false a tick will show for every data point
7903 , staggerLabels = false
7906 , tooltip = function(key, x, y, e, graph) {
7907 return '<h3>' + key + '</h3>' +
7908 '<p>' + y + ' on ' + x + '</p>'
7910 , x //can be accessed via chart.xScale()
7911 , y //can be accessed via chart.yScale()
7912 , state = { stacked: false }
7913 , defaultState = null
7914 , noData = "No Data Available."
7915 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
7916 , controlWidth = function() { return showControls ? 180 : 0 }
7917 , transitionDuration = 250
7926 .highlightZero(true)
7928 .tickFormat(function(d) { return d })
7931 .orient((rightAlignYAxis) ? 'right' : 'left')
7932 .tickFormat(d3.format(',.1f'))
7935 controls.updateState(false);
7936 //============================================================
7939 //============================================================
7940 // Private Variables
7941 //------------------------------------------------------------
7943 var showTooltip = function(e, offsetElement) {
7944 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7945 top = e.pos[1] + ( offsetElement.offsetTop || 0),
7946 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7947 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7948 content = tooltip(e.series.key, x, y, e, chart);
7950 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
7953 //============================================================
7956 function chart(selection) {
7957 selection.each(function(data) {
7958 var container = d3.select(this),
7961 var availableWidth = (width || parseInt(container.style('width')) || 960)
7962 - margin.left - margin.right,
7963 availableHeight = (height || parseInt(container.style('height')) || 400)
7964 - margin.top - margin.bottom;
7966 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7967 chart.container = this;
7969 //set state.disabled
7970 state.disabled = data.map(function(d) { return !!d.disabled });
7972 if (!defaultState) {
7975 for (key in state) {
7976 if (state[key] instanceof Array)
7977 defaultState[key] = state[key].slice(0);
7979 defaultState[key] = state[key];
7982 //------------------------------------------------------------
7983 // Display noData message if there's nothing to show.
7985 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7986 var noDataText = container.selectAll('.nv-noData').data([noData]);
7988 noDataText.enter().append('text')
7989 .attr('class', 'nvd3 nv-noData')
7990 .attr('dy', '-.7em')
7991 .style('text-anchor', 'middle');
7994 .attr('x', margin.left + availableWidth / 2)
7995 .attr('y', margin.top + availableHeight / 2)
7996 .text(function(d) { return d });
8000 container.selectAll('.nv-noData').remove();
8003 //------------------------------------------------------------
8006 //------------------------------------------------------------
8009 x = multibar.xScale();
8010 y = multibar.yScale();
8012 //------------------------------------------------------------
8015 //------------------------------------------------------------
8016 // Setup containers and skeleton of chart
8018 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8019 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8020 var g = wrap.select('g');
8022 gEnter.append('g').attr('class', 'nv-x nv-axis');
8023 gEnter.append('g').attr('class', 'nv-y nv-axis');
8024 gEnter.append('g').attr('class', 'nv-barsWrap');
8025 gEnter.append('g').attr('class', 'nv-legendWrap');
8026 gEnter.append('g').attr('class', 'nv-controlsWrap');
8028 //------------------------------------------------------------
8031 //------------------------------------------------------------
8035 legend.width(availableWidth - controlWidth());
8037 if (multibar.barColor())
8038 data.forEach(function(series,i) {
8039 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8042 g.select('.nv-legendWrap')
8046 if ( margin.top != legend.height()) {
8047 margin.top = legend.height();
8048 availableHeight = (height || parseInt(container.style('height')) || 400)
8049 - margin.top - margin.bottom;
8052 g.select('.nv-legendWrap')
8053 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8056 //------------------------------------------------------------
8059 //------------------------------------------------------------
8063 var controlsData = [
8064 { key: 'Grouped', disabled: multibar.stacked() },
8065 { key: 'Stacked', disabled: !multibar.stacked() }
8068 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8069 g.select('.nv-controlsWrap')
8070 .datum(controlsData)
8071 .attr('transform', 'translate(0,' + (-margin.top) +')')
8075 //------------------------------------------------------------
8078 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8080 if (rightAlignYAxis) {
8081 g.select(".nv-y.nv-axis")
8082 .attr("transform", "translate(" + availableWidth + ",0)");
8085 //------------------------------------------------------------
8086 // Main Chart Component(s)
8089 .disabled(data.map(function(series) { return series.disabled }))
8090 .width(availableWidth)
8091 .height(availableHeight)
8092 .color(data.map(function(d,i) {
8093 return d.color || color(d, i);
8094 }).filter(function(d,i) { return !data[i].disabled }))
8097 var barsWrap = g.select('.nv-barsWrap')
8098 .datum(data.filter(function(d) { return !d.disabled }))
8100 barsWrap.transition().call(multibar);
8102 //------------------------------------------------------------
8105 //------------------------------------------------------------
8111 .ticks( availableWidth / 100 )
8112 .tickSize(-availableHeight, 0);
8114 g.select('.nv-x.nv-axis')
8115 .attr('transform', 'translate(0,' + y.range()[0] + ')');
8116 g.select('.nv-x.nv-axis').transition()
8119 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8122 .selectAll('line, text')
8123 .style('opacity', 1)
8125 if (staggerLabels) {
8126 var getTranslate = function(x,y) {
8127 return "translate(" + x + "," + y + ")";
8130 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8134 .attr('transform', function(d,i,j) {
8135 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8138 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8139 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8140 .attr("transform", function(d,i) {
8141 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8147 .filter(function(d,i) {
8148 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8150 .selectAll('text, line')
8151 .style('opacity', 0);
8155 .selectAll('.tick text')
8156 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8157 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8159 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8160 .style('opacity', 1);
8167 .ticks( availableHeight / 36 )
8168 .tickSize( -availableWidth, 0);
8170 g.select('.nv-y.nv-axis').transition()
8175 //------------------------------------------------------------
8179 //============================================================
8180 // Event Handling/Dispatching (in chart's scope)
8181 //------------------------------------------------------------
8183 legend.dispatch.on('stateChange', function(newState) {
8185 dispatch.stateChange(state);
8189 controls.dispatch.on('legendClick', function(d,i) {
8190 if (!d.disabled) return;
8191 controlsData = controlsData.map(function(s) {
8199 multibar.stacked(false);
8202 multibar.stacked(true);
8206 state.stacked = multibar.stacked();
8207 dispatch.stateChange(state);
8212 dispatch.on('tooltipShow', function(e) {
8213 if (tooltips) showTooltip(e, that.parentNode)
8216 // Update chart from a state object passed to event handler
8217 dispatch.on('changeState', function(e) {
8219 if (typeof e.disabled !== 'undefined') {
8220 data.forEach(function(series,i) {
8221 series.disabled = e.disabled[i];
8224 state.disabled = e.disabled;
8227 if (typeof e.stacked !== 'undefined') {
8228 multibar.stacked(e.stacked);
8229 state.stacked = e.stacked;
8235 //============================================================
8244 //============================================================
8245 // Event Handling/Dispatching (out of chart's scope)
8246 //------------------------------------------------------------
8248 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8249 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8250 dispatch.tooltipShow(e);
8253 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8254 dispatch.tooltipHide(e);
8256 dispatch.on('tooltipHide', function() {
8257 if (tooltips) nv.tooltip.cleanup();
8260 //============================================================
8263 //============================================================
8264 // Expose Public Variables
8265 //------------------------------------------------------------
8267 // expose chart's sub-components
8268 chart.dispatch = dispatch;
8269 chart.multibar = multibar;
8270 chart.legend = legend;
8271 chart.xAxis = xAxis;
8272 chart.yAxis = yAxis;
8274 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8275 'id', 'stacked', 'delay', 'barColor','groupSpacing');
8277 chart.options = nv.utils.optionsFunc.bind(chart);
8279 chart.margin = function(_) {
8280 if (!arguments.length) return margin;
8281 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8282 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8283 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8284 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8288 chart.width = function(_) {
8289 if (!arguments.length) return width;
8294 chart.height = function(_) {
8295 if (!arguments.length) return height;
8300 chart.color = function(_) {
8301 if (!arguments.length) return color;
8302 color = nv.utils.getColor(_);
8303 legend.color(color);
8307 chart.showControls = function(_) {
8308 if (!arguments.length) return showControls;
8313 chart.showLegend = function(_) {
8314 if (!arguments.length) return showLegend;
8319 chart.showXAxis = function(_) {
8320 if (!arguments.length) return showXAxis;
8325 chart.showYAxis = function(_) {
8326 if (!arguments.length) return showYAxis;
8331 chart.rightAlignYAxis = function(_) {
8332 if(!arguments.length) return rightAlignYAxis;
8333 rightAlignYAxis = _;
8334 yAxis.orient( (_) ? 'right' : 'left');
8338 chart.reduceXTicks= function(_) {
8339 if (!arguments.length) return reduceXTicks;
8344 chart.rotateLabels = function(_) {
8345 if (!arguments.length) return rotateLabels;
8350 chart.staggerLabels = function(_) {
8351 if (!arguments.length) return staggerLabels;
8356 chart.tooltip = function(_) {
8357 if (!arguments.length) return tooltip;
8362 chart.tooltips = function(_) {
8363 if (!arguments.length) return tooltips;
8368 chart.tooltipContent = function(_) {
8369 if (!arguments.length) return tooltip;
8374 chart.state = function(_) {
8375 if (!arguments.length) return state;
8380 chart.defaultState = function(_) {
8381 if (!arguments.length) return defaultState;
8386 chart.noData = function(_) {
8387 if (!arguments.length) return noData;
8392 chart.transitionDuration = function(_) {
8393 if (!arguments.length) return transitionDuration;
8394 transitionDuration = _;
8398 //============================================================
8404 nv.models.multiBarHorizontal = function() {
8406 //============================================================
8407 // Public Variables with Default Settings
8408 //------------------------------------------------------------
8410 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8413 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8414 , x = d3.scale.ordinal()
8415 , y = d3.scale.linear()
8416 , getX = function(d) { return d.x }
8417 , getY = function(d) { return d.y }
8418 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8419 , color = nv.utils.defaultColor()
8420 , barColor = null // adding the ability to set the color for each rather than the whole group
8421 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8423 , showValues = false
8425 , valueFormat = d3.format(',.2f')
8431 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8434 //============================================================
8437 //============================================================
8438 // Private Variables
8439 //------------------------------------------------------------
8441 var x0, y0 //used to store previous scales
8444 //============================================================
8447 function chart(selection) {
8448 selection.each(function(data) {
8449 var availableWidth = width - margin.left - margin.right,
8450 availableHeight = height - margin.top - margin.bottom,
8451 container = d3.select(this);
8455 data = d3.layout.stack()
8457 .values(function(d){ return d.values })
8462 //add series index to each data point for reference
8463 data = data.map(function(series, i) {
8464 series.values = series.values.map(function(point) {
8473 //------------------------------------------------------------
8474 // HACK for negative value stacking
8476 data[0].values.map(function(d,i) {
8477 var posBase = 0, negBase = 0;
8478 data.map(function(d) {
8480 f.size = Math.abs(f.y);
8482 f.y1 = negBase - f.size;
8483 negBase = negBase - f.size;
8487 posBase = posBase + f.size;
8494 //------------------------------------------------------------
8497 // remap and flatten the data for use in calculating the scales' domains
8498 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8499 data.map(function(d) {
8500 return d.values.map(function(d,i) {
8501 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8505 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8506 .rangeBands(xRange || [0, availableHeight], .1);
8508 //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
8509 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))
8511 if (showValues && !stacked)
8512 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8514 y.range(yRange || [0, availableWidth]);
8517 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8519 //------------------------------------------------------------
8522 //------------------------------------------------------------
8523 // Setup containers and skeleton of chart
8525 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8526 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8527 var defsEnter = wrapEnter.append('defs');
8528 var gEnter = wrapEnter.append('g');
8529 var g = wrap.select('g');
8531 gEnter.append('g').attr('class', 'nv-groups');
8533 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8535 //------------------------------------------------------------
8539 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8540 .data(function(d) { return d }, function(d,i) { return i });
8541 groups.enter().append('g')
8542 .style('stroke-opacity', 1e-6)
8543 .style('fill-opacity', 1e-6);
8544 groups.exit().transition()
8545 .style('stroke-opacity', 1e-6)
8546 .style('fill-opacity', 1e-6)
8549 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8550 .classed('hover', function(d) { return d.hover })
8551 .style('fill', function(d,i){ return color(d, i) })
8552 .style('stroke', function(d,i){ return color(d, i) });
8554 .style('stroke-opacity', 1)
8555 .style('fill-opacity', .75);
8558 var bars = groups.selectAll('g.nv-bar')
8559 .data(function(d) { return d.values });
8561 bars.exit().remove();
8564 var barsEnter = bars.enter().append('g')
8565 .attr('transform', function(d,i,j) {
8566 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8569 barsEnter.append('rect')
8571 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8574 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8575 d3.select(this).classed('hover', true);
8576 dispatch.elementMouseover({
8579 series: data[d.series],
8580 pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
8582 seriesIndex: d.series,
8586 .on('mouseout', function(d,i) {
8587 d3.select(this).classed('hover', false);
8588 dispatch.elementMouseout({
8591 series: data[d.series],
8593 seriesIndex: d.series,
8597 .on('click', function(d,i) {
8598 dispatch.elementClick({
8601 series: data[d.series],
8602 pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
8604 seriesIndex: d.series,
8607 d3.event.stopPropagation();
8609 .on('dblclick', function(d,i) {
8610 dispatch.elementDblClick({
8613 series: data[d.series],
8614 pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
8616 seriesIndex: d.series,
8619 d3.event.stopPropagation();
8623 barsEnter.append('text');
8625 if (showValues && !stacked) {
8627 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8628 .attr('y', x.rangeBand() / (data.length * 2))
8629 .attr('dy', '.32em')
8630 .text(function(d,i) { return valueFormat(getY(d,i)) })
8633 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8635 bars.selectAll('text').text('');
8639 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8642 if (!disabled) disabled = data.map(function() { return true });
8644 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
8645 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
8650 .attr('transform', function(d,i) {
8651 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8654 .attr('width', function(d,i) {
8655 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8657 .attr('height', x.rangeBand() );
8660 .attr('transform', function(d,i) {
8661 //TODO: stacked must be all positive or all negative, not both?
8662 return 'translate(' +
8663 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8665 (d.series * x.rangeBand() / data.length
8671 .attr('height', x.rangeBand() / data.length )
8672 .attr('width', function(d,i) {
8673 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8677 //store old scales for use in transitions on update
8687 //============================================================
8688 // Expose Public Variables
8689 //------------------------------------------------------------
8691 chart.dispatch = dispatch;
8693 chart.options = nv.utils.optionsFunc.bind(chart);
8695 chart.x = function(_) {
8696 if (!arguments.length) return getX;
8701 chart.y = function(_) {
8702 if (!arguments.length) return getY;
8707 chart.margin = function(_) {
8708 if (!arguments.length) return margin;
8709 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8710 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8711 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8712 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8716 chart.width = function(_) {
8717 if (!arguments.length) return width;
8722 chart.height = function(_) {
8723 if (!arguments.length) return height;
8728 chart.xScale = function(_) {
8729 if (!arguments.length) return x;
8734 chart.yScale = function(_) {
8735 if (!arguments.length) return y;
8740 chart.xDomain = function(_) {
8741 if (!arguments.length) return xDomain;
8746 chart.yDomain = function(_) {
8747 if (!arguments.length) return yDomain;
8752 chart.xRange = function(_) {
8753 if (!arguments.length) return xRange;
8758 chart.yRange = function(_) {
8759 if (!arguments.length) return yRange;
8764 chart.forceY = function(_) {
8765 if (!arguments.length) return forceY;
8770 chart.stacked = function(_) {
8771 if (!arguments.length) return stacked;
8776 chart.color = function(_) {
8777 if (!arguments.length) return color;
8778 color = nv.utils.getColor(_);
8782 chart.barColor = function(_) {
8783 if (!arguments.length) return barColor;
8784 barColor = nv.utils.getColor(_);
8788 chart.disabled = function(_) {
8789 if (!arguments.length) return disabled;
8794 chart.id = function(_) {
8795 if (!arguments.length) return id;
8800 chart.delay = function(_) {
8801 if (!arguments.length) return delay;
8806 chart.showValues = function(_) {
8807 if (!arguments.length) return showValues;
8812 chart.valueFormat= function(_) {
8813 if (!arguments.length) return valueFormat;
8818 chart.valuePadding = function(_) {
8819 if (!arguments.length) return valuePadding;
8824 //============================================================
8830 nv.models.multiBarHorizontalChart = function() {
8832 //============================================================
8833 // Public Variables with Default Settings
8834 //------------------------------------------------------------
8836 var multibar = nv.models.multiBarHorizontal()
8837 , xAxis = nv.models.axis()
8838 , yAxis = nv.models.axis()
8839 , legend = nv.models.legend().height(30)
8840 , controls = nv.models.legend().height(30)
8843 var margin = {top: 30, right: 20, bottom: 50, left: 60}
8846 , color = nv.utils.defaultColor()
8847 , showControls = true
8851 , tooltip = function(key, x, y, e, graph) {
8852 return '<h3>' + key + ' - ' + x + '</h3>' +
8855 , x //can be accessed via chart.xScale()
8856 , y //can be accessed via chart.yScale()
8857 , state = { stacked: stacked }
8858 , defaultState = null
8859 , noData = 'No Data Available.'
8860 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
8861 , controlWidth = function() { return showControls ? 180 : 0 }
8862 , transitionDuration = 250
8871 .highlightZero(false)
8873 .tickFormat(function(d) { return d })
8877 .tickFormat(d3.format(',.1f'))
8880 controls.updateState(false);
8881 //============================================================
8884 //============================================================
8885 // Private Variables
8886 //------------------------------------------------------------
8888 var showTooltip = function(e, offsetElement) {
8889 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8890 top = e.pos[1] + ( offsetElement.offsetTop || 0),
8891 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8892 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8893 content = tooltip(e.series.key, x, y, e, chart);
8895 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
8898 //============================================================
8901 function chart(selection) {
8902 selection.each(function(data) {
8903 var container = d3.select(this),
8906 var availableWidth = (width || parseInt(container.style('width')) || 960)
8907 - margin.left - margin.right,
8908 availableHeight = (height || parseInt(container.style('height')) || 400)
8909 - margin.top - margin.bottom;
8911 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
8912 chart.container = this;
8914 //set state.disabled
8915 state.disabled = data.map(function(d) { return !!d.disabled });
8917 if (!defaultState) {
8920 for (key in state) {
8921 if (state[key] instanceof Array)
8922 defaultState[key] = state[key].slice(0);
8924 defaultState[key] = state[key];
8928 //------------------------------------------------------------
8929 // Display No Data message if there's nothing to show.
8931 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8932 var noDataText = container.selectAll('.nv-noData').data([noData]);
8934 noDataText.enter().append('text')
8935 .attr('class', 'nvd3 nv-noData')
8936 .attr('dy', '-.7em')
8937 .style('text-anchor', 'middle');
8940 .attr('x', margin.left + availableWidth / 2)
8941 .attr('y', margin.top + availableHeight / 2)
8942 .text(function(d) { return d });
8946 container.selectAll('.nv-noData').remove();
8949 //------------------------------------------------------------
8952 //------------------------------------------------------------
8955 x = multibar.xScale();
8956 y = multibar.yScale();
8958 //------------------------------------------------------------
8961 //------------------------------------------------------------
8962 // Setup containers and skeleton of chart
8964 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8965 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8966 var g = wrap.select('g');
8968 gEnter.append('g').attr('class', 'nv-x nv-axis');
8969 gEnter.append('g').attr('class', 'nv-y nv-axis');
8970 gEnter.append('g').attr('class', 'nv-barsWrap');
8971 gEnter.append('g').attr('class', 'nv-legendWrap');
8972 gEnter.append('g').attr('class', 'nv-controlsWrap');
8974 //------------------------------------------------------------
8977 //------------------------------------------------------------
8981 legend.width(availableWidth - controlWidth());
8983 if (multibar.barColor())
8984 data.forEach(function(series,i) {
8985 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8988 g.select('.nv-legendWrap')
8992 if ( margin.top != legend.height()) {
8993 margin.top = legend.height();
8994 availableHeight = (height || parseInt(container.style('height')) || 400)
8995 - margin.top - margin.bottom;
8998 g.select('.nv-legendWrap')
8999 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9002 //------------------------------------------------------------
9005 //------------------------------------------------------------
9009 var controlsData = [
9010 { key: 'Grouped', disabled: multibar.stacked() },
9011 { key: 'Stacked', disabled: !multibar.stacked() }
9014 controls.width(controlWidth()).color(['#444', '#444', '#444']);
9015 g.select('.nv-controlsWrap')
9016 .datum(controlsData)
9017 .attr('transform', 'translate(0,' + (-margin.top) +')')
9021 //------------------------------------------------------------
9024 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9027 //------------------------------------------------------------
9028 // Main Chart Component(s)
9031 .disabled(data.map(function(series) { return series.disabled }))
9032 .width(availableWidth)
9033 .height(availableHeight)
9034 .color(data.map(function(d,i) {
9035 return d.color || color(d, i);
9036 }).filter(function(d,i) { return !data[i].disabled }))
9039 var barsWrap = g.select('.nv-barsWrap')
9040 .datum(data.filter(function(d) { return !d.disabled }))
9042 barsWrap.transition().call(multibar);
9044 //------------------------------------------------------------
9047 //------------------------------------------------------------
9052 .ticks( availableHeight / 24 )
9053 .tickSize(-availableWidth, 0);
9055 g.select('.nv-x.nv-axis').transition()
9058 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9061 .selectAll('line, text')
9062 .style('opacity', 1)
9067 .ticks( availableWidth / 100 )
9068 .tickSize( -availableHeight, 0);
9070 g.select('.nv-y.nv-axis')
9071 .attr('transform', 'translate(0,' + availableHeight + ')');
9072 g.select('.nv-y.nv-axis').transition()
9075 //------------------------------------------------------------
9079 //============================================================
9080 // Event Handling/Dispatching (in chart's scope)
9081 //------------------------------------------------------------
9083 legend.dispatch.on('stateChange', function(newState) {
9085 dispatch.stateChange(state);
9089 controls.dispatch.on('legendClick', function(d,i) {
9090 if (!d.disabled) return;
9091 controlsData = controlsData.map(function(s) {
9099 multibar.stacked(false);
9102 multibar.stacked(true);
9106 state.stacked = multibar.stacked();
9107 dispatch.stateChange(state);
9112 dispatch.on('tooltipShow', function(e) {
9113 if (tooltips) showTooltip(e, that.parentNode);
9116 // Update chart from a state object passed to event handler
9117 dispatch.on('changeState', function(e) {
9119 if (typeof e.disabled !== 'undefined') {
9120 data.forEach(function(series,i) {
9121 series.disabled = e.disabled[i];
9124 state.disabled = e.disabled;
9127 if (typeof e.stacked !== 'undefined') {
9128 multibar.stacked(e.stacked);
9129 state.stacked = e.stacked;
9132 selection.call(chart);
9134 //============================================================
9143 //============================================================
9144 // Event Handling/Dispatching (out of chart's scope)
9145 //------------------------------------------------------------
9147 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9148 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9149 dispatch.tooltipShow(e);
9152 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9153 dispatch.tooltipHide(e);
9155 dispatch.on('tooltipHide', function() {
9156 if (tooltips) nv.tooltip.cleanup();
9159 //============================================================
9162 //============================================================
9163 // Expose Public Variables
9164 //------------------------------------------------------------
9166 // expose chart's sub-components
9167 chart.dispatch = dispatch;
9168 chart.multibar = multibar;
9169 chart.legend = legend;
9170 chart.xAxis = xAxis;
9171 chart.yAxis = yAxis;
9173 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor');
9175 chart.options = nv.utils.optionsFunc.bind(chart);
9177 chart.margin = function(_) {
9178 if (!arguments.length) return margin;
9179 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
9180 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
9181 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9182 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
9186 chart.width = function(_) {
9187 if (!arguments.length) return width;
9192 chart.height = function(_) {
9193 if (!arguments.length) return height;
9198 chart.color = function(_) {
9199 if (!arguments.length) return color;
9200 color = nv.utils.getColor(_);
9201 legend.color(color);
9205 chart.showControls = function(_) {
9206 if (!arguments.length) return showControls;
9211 chart.showLegend = function(_) {
9212 if (!arguments.length) return showLegend;
9217 chart.tooltip = function(_) {
9218 if (!arguments.length) return tooltip;
9223 chart.tooltips = function(_) {
9224 if (!arguments.length) return tooltips;
9229 chart.tooltipContent = function(_) {
9230 if (!arguments.length) return tooltip;
9235 chart.state = function(_) {
9236 if (!arguments.length) return state;
9241 chart.defaultState = function(_) {
9242 if (!arguments.length) return defaultState;
9247 chart.noData = function(_) {
9248 if (!arguments.length) return noData;
9253 chart.transitionDuration = function(_) {
9254 if (!arguments.length) return transitionDuration;
9255 transitionDuration = _;
9258 //============================================================
9263 nv.models.multiChart = function() {
9265 //============================================================
9266 // Public Variables with Default Settings
9267 //------------------------------------------------------------
9269 var margin = {top: 30, right: 20, bottom: 50, left: 60},
9270 color = d3.scale.category20().range(),
9275 tooltip = function(key, x, y, e, graph) {
9276 return '<h3>' + key + '</h3>' +
9277 '<p>' + y + ' at ' + x + '</p>'
9283 ; //can be accessed via chart.lines.[x/y]Scale()
9285 //============================================================
9286 // Private Variables
9287 //------------------------------------------------------------
9289 var x = d3.scale.linear(),
9290 yScale1 = d3.scale.linear(),
9291 yScale2 = d3.scale.linear(),
9293 lines1 = nv.models.line().yScale(yScale1),
9294 lines2 = nv.models.line().yScale(yScale2),
9296 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9297 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9299 stack1 = nv.models.stackedArea().yScale(yScale1),
9300 stack2 = nv.models.stackedArea().yScale(yScale2),
9302 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9303 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9304 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9306 legend = nv.models.legend().height(30),
9307 dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
9309 var showTooltip = function(e, offsetElement) {
9310 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9311 top = e.pos[1] + ( offsetElement.offsetTop || 0),
9312 x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
9313 y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
9314 content = tooltip(e.series.key, x, y, e, chart);
9316 nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
9319 function chart(selection) {
9320 selection.each(function(data) {
9321 var container = d3.select(this),
9324 chart.update = function() { container.transition().call(chart); };
9325 chart.container = this;
9327 var availableWidth = (width || parseInt(container.style('width')) || 960)
9328 - margin.left - margin.right,
9329 availableHeight = (height || parseInt(container.style('height')) || 400)
9330 - margin.top - margin.bottom;
9332 var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
9333 var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
9334 var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
9335 var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
9336 var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
9337 var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
9339 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9341 return d.values.map(function(d,i) {
9342 return { x: d.x, y: d.y }
9346 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9348 return d.values.map(function(d,i) {
9349 return { x: d.x, y: d.y }
9353 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9354 .range([0, availableWidth]);
9356 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9357 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9359 gEnter.append('g').attr('class', 'x axis');
9360 gEnter.append('g').attr('class', 'y1 axis');
9361 gEnter.append('g').attr('class', 'y2 axis');
9362 gEnter.append('g').attr('class', 'lines1Wrap');
9363 gEnter.append('g').attr('class', 'lines2Wrap');
9364 gEnter.append('g').attr('class', 'bars1Wrap');
9365 gEnter.append('g').attr('class', 'bars2Wrap');
9366 gEnter.append('g').attr('class', 'stack1Wrap');
9367 gEnter.append('g').attr('class', 'stack2Wrap');
9368 gEnter.append('g').attr('class', 'legendWrap');
9370 var g = wrap.select('g');
9373 legend.width( availableWidth / 2 );
9375 g.select('.legendWrap')
9376 .datum(data.map(function(series) {
9377 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9378 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9383 if ( margin.top != legend.height()) {
9384 margin.top = legend.height();
9385 availableHeight = (height || parseInt(container.style('height')) || 400)
9386 - margin.top - margin.bottom;
9389 g.select('.legendWrap')
9390 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
9395 .width(availableWidth)
9396 .height(availableHeight)
9397 .interpolate("monotone")
9398 .color(data.map(function(d,i) {
9399 return d.color || color[i % color.length];
9400 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9403 .width(availableWidth)
9404 .height(availableHeight)
9405 .interpolate("monotone")
9406 .color(data.map(function(d,i) {
9407 return d.color || color[i % color.length];
9408 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9411 .width(availableWidth)
9412 .height(availableHeight)
9413 .color(data.map(function(d,i) {
9414 return d.color || color[i % color.length];
9415 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9418 .width(availableWidth)
9419 .height(availableHeight)
9420 .color(data.map(function(d,i) {
9421 return d.color || color[i % color.length];
9422 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9425 .width(availableWidth)
9426 .height(availableHeight)
9427 .color(data.map(function(d,i) {
9428 return d.color || color[i % color.length];
9429 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9432 .width(availableWidth)
9433 .height(availableHeight)
9434 .color(data.map(function(d,i) {
9435 return d.color || color[i % color.length];
9436 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9438 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9441 var lines1Wrap = g.select('.lines1Wrap')
9443 var bars1Wrap = g.select('.bars1Wrap')
9445 var stack1Wrap = g.select('.stack1Wrap')
9448 var lines2Wrap = g.select('.lines2Wrap')
9450 var bars2Wrap = g.select('.bars2Wrap')
9452 var stack2Wrap = g.select('.stack2Wrap')
9455 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9456 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9457 }).concat([{x:0, y:0}]) : []
9458 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9459 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9460 }).concat([{x:0, y:0}]) : []
9462 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9463 .range([0, availableHeight])
9465 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9466 .range([0, availableHeight])
9468 lines1.yDomain(yScale1.domain())
9469 bars1.yDomain(yScale1.domain())
9470 stack1.yDomain(yScale1.domain())
9472 lines2.yDomain(yScale2.domain())
9473 bars2.yDomain(yScale2.domain())
9474 stack2.yDomain(yScale2.domain())
9476 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9477 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9479 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9480 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9482 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9483 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9488 .ticks( availableWidth / 100 )
9489 .tickSize(-availableHeight, 0);
9492 .attr('transform', 'translate(0,' + availableHeight + ')');
9493 d3.transition(g.select('.x.axis'))
9497 .ticks( availableHeight / 36 )
9498 .tickSize( -availableWidth, 0);
9501 d3.transition(g.select('.y1.axis'))
9505 .ticks( availableHeight / 36 )
9506 .tickSize( -availableWidth, 0);
9508 d3.transition(g.select('.y2.axis'))
9511 g.select('.y2.axis')
9512 .style('opacity', series2.length ? 1 : 0)
9513 .attr('transform', 'translate(' + x.range()[1] + ',0)');
9515 legend.dispatch.on('stateChange', function(newState) {
9519 dispatch.on('tooltipShow', function(e) {
9520 if (tooltips) showTooltip(e, that.parentNode);
9529 //============================================================
9530 // Event Handling/Dispatching (out of chart's scope)
9531 //------------------------------------------------------------
9533 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9534 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9535 dispatch.tooltipShow(e);
9538 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9539 dispatch.tooltipHide(e);
9542 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9543 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9544 dispatch.tooltipShow(e);
9547 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9548 dispatch.tooltipHide(e);
9551 bars1.dispatch.on('elementMouseover.tooltip', function(e) {
9552 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9553 dispatch.tooltipShow(e);
9556 bars1.dispatch.on('elementMouseout.tooltip', function(e) {
9557 dispatch.tooltipHide(e);
9560 bars2.dispatch.on('elementMouseover.tooltip', function(e) {
9561 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9562 dispatch.tooltipShow(e);
9565 bars2.dispatch.on('elementMouseout.tooltip', function(e) {
9566 dispatch.tooltipHide(e);
9569 stack1.dispatch.on('tooltipShow', function(e) {
9570 //disable tooltips when value ~= 0
9571 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
9572 if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9573 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9577 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9578 dispatch.tooltipShow(e);
9581 stack1.dispatch.on('tooltipHide', function(e) {
9582 dispatch.tooltipHide(e);
9585 stack2.dispatch.on('tooltipShow', function(e) {
9586 //disable tooltips when value ~= 0
9587 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
9588 if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9589 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9593 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9594 dispatch.tooltipShow(e);
9597 stack2.dispatch.on('tooltipHide', function(e) {
9598 dispatch.tooltipHide(e);
9601 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9602 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9603 dispatch.tooltipShow(e);
9606 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9607 dispatch.tooltipHide(e);
9610 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9611 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9612 dispatch.tooltipShow(e);
9615 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9616 dispatch.tooltipHide(e);
9619 dispatch.on('tooltipHide', function() {
9620 if (tooltips) nv.tooltip.cleanup();
9625 //============================================================
9626 // Global getters and setters
9627 //------------------------------------------------------------
9629 chart.dispatch = dispatch;
9630 chart.lines1 = lines1;
9631 chart.lines2 = lines2;
9632 chart.bars1 = bars1;
9633 chart.bars2 = bars2;
9634 chart.stack1 = stack1;
9635 chart.stack2 = stack2;
9636 chart.xAxis = xAxis;
9637 chart.yAxis1 = yAxis1;
9638 chart.yAxis2 = yAxis2;
9639 chart.options = nv.utils.optionsFunc.bind(chart);
9641 chart.x = function(_) {
9642 if (!arguments.length) return getX;
9649 chart.y = function(_) {
9650 if (!arguments.length) return getY;
9657 chart.yDomain1 = function(_) {
9658 if (!arguments.length) return yDomain1;
9663 chart.yDomain2 = function(_) {
9664 if (!arguments.length) return yDomain2;
9669 chart.margin = function(_) {
9670 if (!arguments.length) return margin;
9675 chart.width = function(_) {
9676 if (!arguments.length) return width;
9681 chart.height = function(_) {
9682 if (!arguments.length) return height;
9687 chart.color = function(_) {
9688 if (!arguments.length) return color;
9694 chart.showLegend = function(_) {
9695 if (!arguments.length) return showLegend;
9700 chart.tooltips = function(_) {
9701 if (!arguments.length) return tooltips;
9706 chart.tooltipContent = function(_) {
9707 if (!arguments.length) return tooltip;
9716 nv.models.ohlcBar = function() {
9718 //============================================================
9719 // Public Variables with Default Settings
9720 //------------------------------------------------------------
9722 var margin = {top: 0, right: 0, bottom: 0, left: 0}
9725 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9726 , x = d3.scale.linear()
9727 , y = d3.scale.linear()
9728 , getX = function(d) { return d.x }
9729 , getY = function(d) { return d.y }
9730 , getOpen = function(d) { return d.open }
9731 , getClose = function(d) { return d.close }
9732 , getHigh = function(d) { return d.high }
9733 , getLow = function(d) { return d.low }
9736 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9738 , color = nv.utils.defaultColor()
9743 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9746 //============================================================
9748 //============================================================
9749 // Private Variables
9750 //------------------------------------------------------------
9752 //TODO: store old scales for transitions
9754 //============================================================
9757 function chart(selection) {
9758 selection.each(function(data) {
9759 var availableWidth = width - margin.left - margin.right,
9760 availableHeight = height - margin.top - margin.bottom,
9761 container = d3.select(this);
9764 //------------------------------------------------------------
9767 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9770 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9772 x.range(xRange || [0, availableWidth]);
9774 y .domain(yDomain || [
9775 d3.min(data[0].values.map(getLow).concat(forceY)),
9776 d3.max(data[0].values.map(getHigh).concat(forceY))
9778 .range(yRange || [availableHeight, 0]);
9780 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9781 if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
9782 if (x.domain()[0] === x.domain()[1])
9784 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9787 if (y.domain()[0] === y.domain()[1])
9789 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9792 //------------------------------------------------------------
9795 //------------------------------------------------------------
9796 // Setup containers and skeleton of chart
9798 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9799 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9800 var defsEnter = wrapEnter.append('defs');
9801 var gEnter = wrapEnter.append('g');
9802 var g = wrap.select('g');
9804 gEnter.append('g').attr('class', 'nv-ticks');
9806 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9808 //------------------------------------------------------------
9812 .on('click', function(d,i) {
9813 dispatch.chartClick({
9822 defsEnter.append('clipPath')
9823 .attr('id', 'nv-chart-clip-path-' + id)
9826 wrap.select('#nv-chart-clip-path-' + id + ' rect')
9827 .attr('width', availableWidth)
9828 .attr('height', availableHeight);
9830 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9834 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9835 .data(function(d) { return d });
9837 ticks.exit().remove();
9840 var ticksEnter = ticks.enter().append('path')
9841 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9842 .attr('d', function(d,i) {
9843 var w = (availableWidth / data[0].values.length) * .9;
9852 + (y(getLow(d,i)) - y(getOpen(d,i)))
9862 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9863 //.attr('fill', function(d,i) { return color[0]; })
9864 //.attr('stroke', function(d,i) { return color[0]; })
9866 //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
9867 //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
9868 .on('mouseover', function(d,i) {
9869 d3.select(this).classed('hover', true);
9870 dispatch.elementMouseover({
9873 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
9880 .on('mouseout', function(d,i) {
9881 d3.select(this).classed('hover', false);
9882 dispatch.elementMouseout({
9890 .on('click', function(d,i) {
9891 dispatch.elementClick({
9896 pos: [x(getX(d,i)), y(getY(d,i))],
9900 d3.event.stopPropagation();
9902 .on('dblclick', function(d,i) {
9903 dispatch.elementDblClick({
9908 pos: [x(getX(d,i)), y(getY(d,i))],
9912 d3.event.stopPropagation();
9916 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9917 d3.transition(ticks)
9918 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9919 .attr('d', function(d,i) {
9920 var w = (availableWidth / data[0].values.length) * .9;
9940 //.attr('width', (availableWidth / data[0].values.length) * .9 )
9943 //d3.transition(ticks)
9944 //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
9945 //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9946 //.order(); // not sure if this makes any sense for this model
9954 //============================================================
9955 // Expose Public Variables
9956 //------------------------------------------------------------
9958 chart.dispatch = dispatch;
9960 chart.options = nv.utils.optionsFunc.bind(chart);
9962 chart.x = function(_) {
9963 if (!arguments.length) return getX;
9968 chart.y = function(_) {
9969 if (!arguments.length) return getY;
9974 chart.open = function(_) {
9975 if (!arguments.length) return getOpen;
9980 chart.close = function(_) {
9981 if (!arguments.length) return getClose;
9986 chart.high = function(_) {
9987 if (!arguments.length) return getHigh;
9992 chart.low = function(_) {
9993 if (!arguments.length) return getLow;
9998 chart.margin = function(_) {
9999 if (!arguments.length) return margin;
10000 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10001 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10002 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10003 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10007 chart.width = function(_) {
10008 if (!arguments.length) return width;
10013 chart.height = function(_) {
10014 if (!arguments.length) return height;
10019 chart.xScale = function(_) {
10020 if (!arguments.length) return x;
10025 chart.yScale = function(_) {
10026 if (!arguments.length) return y;
10031 chart.xDomain = function(_) {
10032 if (!arguments.length) return xDomain;
10037 chart.yDomain = function(_) {
10038 if (!arguments.length) return yDomain;
10043 chart.xRange = function(_) {
10044 if (!arguments.length) return xRange;
10049 chart.yRange = function(_) {
10050 if (!arguments.length) return yRange;
10055 chart.forceX = function(_) {
10056 if (!arguments.length) return forceX;
10061 chart.forceY = function(_) {
10062 if (!arguments.length) return forceY;
10067 chart.padData = function(_) {
10068 if (!arguments.length) return padData;
10073 chart.clipEdge = function(_) {
10074 if (!arguments.length) return clipEdge;
10079 chart.color = function(_) {
10080 if (!arguments.length) return color;
10081 color = nv.utils.getColor(_);
10085 chart.id = function(_) {
10086 if (!arguments.length) return id;
10091 //============================================================
10096 nv.models.pie = function() {
10098 //============================================================
10099 // Public Variables with Default Settings
10100 //------------------------------------------------------------
10102 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10105 , getX = function(d) { return d.x }
10106 , getY = function(d) { return d.y }
10107 , getDescription = function(d) { return d.description }
10108 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10109 , color = nv.utils.defaultColor()
10110 , valueFormat = d3.format(',.2f')
10111 , showLabels = true
10112 , pieLabelsOutside = true
10113 , donutLabelsOutside = false
10114 , labelType = "key"
10115 , labelThreshold = .02 //if slice percentage is under this, don't show label
10117 , labelSunbeamLayout = false
10118 , startAngle = false
10121 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10124 //============================================================
10127 function chart(selection) {
10128 selection.each(function(data) {
10129 var availableWidth = width - margin.left - margin.right,
10130 availableHeight = height - margin.top - margin.bottom,
10131 radius = Math.min(availableWidth, availableHeight) / 2,
10132 arcRadius = radius-(radius / 5),
10133 container = d3.select(this);
10136 //------------------------------------------------------------
10137 // Setup containers and skeleton of chart
10139 //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
10140 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10141 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10142 var gEnter = wrapEnter.append('g');
10143 var g = wrap.select('g');
10145 gEnter.append('g').attr('class', 'nv-pie');
10147 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10148 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10150 //------------------------------------------------------------
10154 .on('click', function(d,i) {
10155 dispatch.chartClick({
10164 var arc = d3.svg.arc()
10165 .outerRadius(arcRadius);
10167 if (startAngle) arc.startAngle(startAngle)
10168 if (endAngle) arc.endAngle(endAngle);
10169 if (donut) arc.innerRadius(radius * donutRatio);
10171 // Setup the Pie chart and choose the data element
10172 var pie = d3.layout.pie()
10174 .value(function(d) { return d.disabled ? 0 : getY(d) });
10176 var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
10179 slices.exit().remove();
10181 var ae = slices.enter().append('g')
10182 .attr('class', 'nv-slice')
10183 .on('mouseover', function(d,i){
10184 d3.select(this).classed('hover', true);
10185 dispatch.elementMouseover({
10186 label: getX(d.data),
10187 value: getY(d.data),
10190 pos: [d3.event.pageX, d3.event.pageY],
10194 .on('mouseout', function(d,i){
10195 d3.select(this).classed('hover', false);
10196 dispatch.elementMouseout({
10197 label: getX(d.data),
10198 value: getY(d.data),
10204 .on('click', function(d,i) {
10205 dispatch.elementClick({
10206 label: getX(d.data),
10207 value: getY(d.data),
10213 d3.event.stopPropagation();
10215 .on('dblclick', function(d,i) {
10216 dispatch.elementDblClick({
10217 label: getX(d.data),
10218 value: getY(d.data),
10224 d3.event.stopPropagation();
10228 .attr('fill', function(d,i) { return color(d, i); })
10229 .attr('stroke', function(d,i) { return color(d, i); });
10231 var paths = ae.append('path')
10232 .each(function(d) { this._current = d; });
10235 d3.transition(slices.select('path'))
10237 .attrTween('d', arcTween);
10240 // This does the normal label
10241 var labelsArc = d3.svg.arc().innerRadius(0);
10243 if (pieLabelsOutside){ labelsArc = arc; }
10245 if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
10247 ae.append("g").classed("nv-label", true)
10248 .each(function(d, i) {
10249 var group = d3.select(this);
10252 .attr('transform', function(d) {
10253 if (labelSunbeamLayout) {
10254 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10255 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10256 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10257 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10262 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10264 d.outerRadius = radius + 10; // Set Outer Coordinate
10265 d.innerRadius = radius + 15; // Set Inner Coordinate
10266 return 'translate(' + labelsArc.centroid(d) + ')'
10270 group.append('rect')
10271 .style('stroke', '#fff')
10272 .style('fill', '#fff')
10276 group.append('text')
10277 .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
10278 .style('fill', '#000')
10283 slices.select(".nv-label").transition()
10284 .attr('transform', function(d) {
10285 if (labelSunbeamLayout) {
10286 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10287 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10288 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10289 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10294 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10296 d.outerRadius = radius + 10; // Set Outer Coordinate
10297 d.innerRadius = radius + 15; // Set Inner Coordinate
10298 return 'translate(' + labelsArc.centroid(d) + ')'
10302 slices.each(function(d, i) {
10303 var slice = d3.select(this);
10306 .select(".nv-label text")
10307 .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
10308 .text(function(d, i) {
10309 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10311 "key" : getX(d.data),
10312 "value": getY(d.data),
10313 "percent": d3.format('%')(percent)
10315 return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
10318 var textBox = slice.select('text').node().getBBox();
10319 slice.select(".nv-label rect")
10320 .attr("width", textBox.width + 10)
10321 .attr("height", textBox.height + 10)
10322 .attr("transform", function() {
10323 return "translate(" + [textBox.x - 5, textBox.y - 5] + ")";
10329 // Computes the angle of an arc, converting from radians to degrees.
10330 function angle(d) {
10331 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10332 return a > 90 ? a - 180 : a;
10335 function arcTween(a) {
10336 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10337 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10338 if (!donut) a.innerRadius = 0;
10339 var i = d3.interpolate(this._current, a);
10340 this._current = i(0);
10341 return function(t) {
10346 function tweenPie(b) {
10348 var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
10349 return function(t) {
10360 //============================================================
10361 // Expose Public Variables
10362 //------------------------------------------------------------
10364 chart.dispatch = dispatch;
10365 chart.options = nv.utils.optionsFunc.bind(chart);
10367 chart.margin = function(_) {
10368 if (!arguments.length) return margin;
10369 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10370 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10371 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10372 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10376 chart.width = function(_) {
10377 if (!arguments.length) return width;
10382 chart.height = function(_) {
10383 if (!arguments.length) return height;
10388 chart.values = function(_) {
10389 nv.log("pie.values() is no longer supported.");
10393 chart.x = function(_) {
10394 if (!arguments.length) return getX;
10399 chart.y = function(_) {
10400 if (!arguments.length) return getY;
10401 getY = d3.functor(_);
10405 chart.description = function(_) {
10406 if (!arguments.length) return getDescription;
10407 getDescription = _;
10411 chart.showLabels = function(_) {
10412 if (!arguments.length) return showLabels;
10417 chart.labelSunbeamLayout = function(_) {
10418 if (!arguments.length) return labelSunbeamLayout;
10419 labelSunbeamLayout = _;
10423 chart.donutLabelsOutside = function(_) {
10424 if (!arguments.length) return donutLabelsOutside;
10425 donutLabelsOutside = _;
10429 chart.pieLabelsOutside = function(_) {
10430 if (!arguments.length) return pieLabelsOutside;
10431 pieLabelsOutside = _;
10435 chart.labelType = function(_) {
10436 if (!arguments.length) return labelType;
10438 labelType = labelType || "key";
10442 chart.donut = function(_) {
10443 if (!arguments.length) return donut;
10448 chart.donutRatio = function(_) {
10449 if (!arguments.length) return donutRatio;
10454 chart.startAngle = function(_) {
10455 if (!arguments.length) return startAngle;
10460 chart.endAngle = function(_) {
10461 if (!arguments.length) return endAngle;
10466 chart.id = function(_) {
10467 if (!arguments.length) return id;
10472 chart.color = function(_) {
10473 if (!arguments.length) return color;
10474 color = nv.utils.getColor(_);
10478 chart.valueFormat = function(_) {
10479 if (!arguments.length) return valueFormat;
10484 chart.labelThreshold = function(_) {
10485 if (!arguments.length) return labelThreshold;
10486 labelThreshold = _;
10489 //============================================================
10494 nv.models.pieChart = function() {
10496 //============================================================
10497 // Public Variables with Default Settings
10498 //------------------------------------------------------------
10500 var pie = nv.models.pie()
10501 , legend = nv.models.legend()
10504 var margin = {top: 30, right: 20, bottom: 20, left: 20}
10507 , showLegend = true
10508 , color = nv.utils.defaultColor()
10510 , tooltip = function(key, y, e, graph) {
10511 return '<h3>' + key + '</h3>' +
10515 , defaultState = null
10516 , noData = "No Data Available."
10517 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10520 //============================================================
10523 //============================================================
10524 // Private Variables
10525 //------------------------------------------------------------
10527 var showTooltip = function(e, offsetElement) {
10528 var tooltipLabel = pie.description()(e.point) || pie.x()(e.point)
10529 var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
10530 top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
10531 y = pie.valueFormat()(pie.y()(e.point)),
10532 content = tooltip(tooltipLabel, y, e, chart);
10534 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10537 //============================================================
10540 function chart(selection) {
10541 selection.each(function(data) {
10542 var container = d3.select(this),
10545 var availableWidth = (width || parseInt(container.style('width')) || 960)
10546 - margin.left - margin.right,
10547 availableHeight = (height || parseInt(container.style('height')) || 400)
10548 - margin.top - margin.bottom;
10550 chart.update = function() { container.transition().call(chart); };
10551 chart.container = this;
10553 //set state.disabled
10554 state.disabled = data.map(function(d) { return !!d.disabled });
10556 if (!defaultState) {
10559 for (key in state) {
10560 if (state[key] instanceof Array)
10561 defaultState[key] = state[key].slice(0);
10563 defaultState[key] = state[key];
10567 //------------------------------------------------------------
10568 // Display No Data message if there's nothing to show.
10570 if (!data || !data.length) {
10571 var noDataText = container.selectAll('.nv-noData').data([noData]);
10573 noDataText.enter().append('text')
10574 .attr('class', 'nvd3 nv-noData')
10575 .attr('dy', '-.7em')
10576 .style('text-anchor', 'middle');
10579 .attr('x', margin.left + availableWidth / 2)
10580 .attr('y', margin.top + availableHeight / 2)
10581 .text(function(d) { return d });
10585 container.selectAll('.nv-noData').remove();
10588 //------------------------------------------------------------
10591 //------------------------------------------------------------
10592 // Setup containers and skeleton of chart
10594 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10595 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10596 var g = wrap.select('g');
10598 gEnter.append('g').attr('class', 'nv-pieWrap');
10599 gEnter.append('g').attr('class', 'nv-legendWrap');
10601 //------------------------------------------------------------
10604 //------------------------------------------------------------
10609 .width( availableWidth )
10612 wrap.select('.nv-legendWrap')
10616 if ( margin.top != legend.height()) {
10617 margin.top = legend.height();
10618 availableHeight = (height || parseInt(container.style('height')) || 400)
10619 - margin.top - margin.bottom;
10622 wrap.select('.nv-legendWrap')
10623 .attr('transform', 'translate(0,' + (-margin.top) +')');
10626 //------------------------------------------------------------
10629 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10632 //------------------------------------------------------------
10633 // Main Chart Component(s)
10636 .width(availableWidth)
10637 .height(availableHeight);
10640 var pieWrap = g.select('.nv-pieWrap')
10643 d3.transition(pieWrap).call(pie);
10645 //------------------------------------------------------------
10648 //============================================================
10649 // Event Handling/Dispatching (in chart's scope)
10650 //------------------------------------------------------------
10652 legend.dispatch.on('stateChange', function(newState) {
10654 dispatch.stateChange(state);
10658 pie.dispatch.on('elementMouseout.tooltip', function(e) {
10659 dispatch.tooltipHide(e);
10662 // Update chart from a state object passed to event handler
10663 dispatch.on('changeState', function(e) {
10665 if (typeof e.disabled !== 'undefined') {
10666 data.forEach(function(series,i) {
10667 series.disabled = e.disabled[i];
10670 state.disabled = e.disabled;
10676 //============================================================
10684 //============================================================
10685 // Event Handling/Dispatching (out of chart's scope)
10686 //------------------------------------------------------------
10688 pie.dispatch.on('elementMouseover.tooltip', function(e) {
10689 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10690 dispatch.tooltipShow(e);
10693 dispatch.on('tooltipShow', function(e) {
10694 if (tooltips) showTooltip(e);
10697 dispatch.on('tooltipHide', function() {
10698 if (tooltips) nv.tooltip.cleanup();
10701 //============================================================
10704 //============================================================
10705 // Expose Public Variables
10706 //------------------------------------------------------------
10708 // expose chart's sub-components
10709 chart.legend = legend;
10710 chart.dispatch = dispatch;
10713 d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'labelType', 'donut', 'donutRatio', 'labelThreshold');
10714 chart.options = nv.utils.optionsFunc.bind(chart);
10716 chart.margin = function(_) {
10717 if (!arguments.length) return margin;
10718 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10719 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10720 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10721 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10725 chart.width = function(_) {
10726 if (!arguments.length) return width;
10731 chart.height = function(_) {
10732 if (!arguments.length) return height;
10737 chart.color = function(_) {
10738 if (!arguments.length) return color;
10739 color = nv.utils.getColor(_);
10740 legend.color(color);
10745 chart.showLegend = function(_) {
10746 if (!arguments.length) return showLegend;
10751 chart.tooltips = function(_) {
10752 if (!arguments.length) return tooltips;
10757 chart.tooltipContent = function(_) {
10758 if (!arguments.length) return tooltip;
10763 chart.state = function(_) {
10764 if (!arguments.length) return state;
10769 chart.defaultState = function(_) {
10770 if (!arguments.length) return defaultState;
10775 chart.noData = function(_) {
10776 if (!arguments.length) return noData;
10781 //============================================================
10787 nv.models.scatter = function() {
10789 //============================================================
10790 // Public Variables with Default Settings
10791 //------------------------------------------------------------
10793 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10796 , color = nv.utils.defaultColor() // chooses color
10797 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10798 , x = d3.scale.linear()
10799 , y = d3.scale.linear()
10800 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10801 , getX = function(d) { return d.x } // accessor to get the x value
10802 , getY = function(d) { return d.y } // accessor to get the y value
10803 , getSize = function(d) { return d.size || 1} // accessor to get the point size
10804 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
10805 , onlyCircles = true // Set to false to use shapes
10806 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10807 , forceY = [] // List of numbers to Force into the Y scale
10808 , forceSize = [] // List of numbers to Force into the Size scale
10809 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
10811 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
10812 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10813 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10814 , clipEdge = false // if true, masks points within x and y scale
10815 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
10816 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
10817 , xDomain = null // Override x domain (skips the calculation from data)
10818 , yDomain = null // Override y domain
10819 , xRange = null // Override x range
10820 , yRange = null // Override y range
10821 , sizeDomain = null // Override point size domain
10823 , singlePoint = false
10824 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
10825 , useVoronoi = true
10828 //============================================================
10831 //============================================================
10832 // Private Variables
10833 //------------------------------------------------------------
10835 var x0, y0, z0 // used to store previous scales
10837 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
10840 //============================================================
10843 function chart(selection) {
10844 selection.each(function(data) {
10845 var availableWidth = width - margin.left - margin.right,
10846 availableHeight = height - margin.top - margin.bottom,
10847 container = d3.select(this);
10849 //add series index to each data point for reference
10850 data = data.map(function(series, i) {
10851 series.values = series.values.map(function(point) {
10858 //------------------------------------------------------------
10861 // remap and flatten the data for use in calculating the scales' domains
10862 var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
10864 data.map(function(d) {
10865 return d.values.map(function(d,i) {
10866 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
10871 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10873 if (padData && data[0])
10874 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
10875 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
10877 x.range(xRange || [0, availableWidth]);
10879 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10880 .range(yRange || [availableHeight, 0]);
10882 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10883 .range(sizeRange || [16, 256]);
10885 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10886 if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
10887 if (x.domain()[0] === x.domain()[1])
10889 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10890 : x.domain([-1,1]);
10892 if (y.domain()[0] === y.domain()[1])
10894 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
10895 : y.domain([-1,1]);
10897 if ( isNaN(x.domain()[0])) {
10901 if ( isNaN(y.domain()[0])) {
10910 //------------------------------------------------------------
10913 //------------------------------------------------------------
10914 // Setup containers and skeleton of chart
10916 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10917 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
10918 var defsEnter = wrapEnter.append('defs');
10919 var gEnter = wrapEnter.append('g');
10920 var g = wrap.select('g');
10922 gEnter.append('g').attr('class', 'nv-groups');
10923 gEnter.append('g').attr('class', 'nv-point-paths');
10925 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10927 //------------------------------------------------------------
10930 defsEnter.append('clipPath')
10931 .attr('id', 'nv-edge-clip-' + id)
10934 wrap.select('#nv-edge-clip-' + id + ' rect')
10935 .attr('width', availableWidth)
10936 .attr('height', availableHeight);
10938 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10941 function updateInteractiveLayer() {
10943 if (!interactive) return false;
10947 var vertices = d3.merge(data.map(function(group, groupIndex) {
10948 return group.values
10949 .map(function(point, pointIndex) {
10950 // *Adding noise to make duplicates very unlikely
10951 // *Injecting series and point index for reference
10952 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10954 var pX = getX(point,pointIndex);
10955 var pY = getY(point,pointIndex);
10957 return [x(pX)+ Math.random() * 1e-7,
10958 y(pY)+ Math.random() * 1e-7,
10960 pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
10962 .filter(function(pointArray, pointIndex) {
10963 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10970 //inject series and point index for reference into voronoi
10971 if (useVoronoi === true) {
10974 var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
10978 pointClipsEnter.append('clipPath')
10979 .attr('class', 'nv-point-clips')
10980 .attr('id', 'nv-points-clip-' + id);
10982 var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
10984 pointClips.enter().append('circle')
10985 .attr('r', clipRadius);
10986 pointClips.exit().remove();
10988 .attr('cx', function(d) { return d[0] })
10989 .attr('cy', function(d) { return d[1] });
10991 wrap.select('.nv-point-paths')
10992 .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
10996 if(vertices.length) {
10997 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
10998 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
10999 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11000 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11001 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11004 var bounds = d3.geom.polygon([
11007 [width + 10,height + 10],
11011 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11013 'data': bounds.clip(d),
11014 'series': vertices[i][2],
11015 'point': vertices[i][3]
11020 var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11022 pointPaths.enter().append('path')
11023 .attr('class', function(d,i) { return 'nv-path-'+i; });
11024 pointPaths.exit().remove();
11026 .attr('d', function(d) {
11027 if (d.data.length === 0)
11030 return 'M' + d.data.join('L') + 'Z';
11033 var mouseEventCallback = function(d,mDispatch) {
11034 if (needsUpdate) return 0;
11035 var series = data[d.series];
11036 if (typeof series === 'undefined') return;
11038 var point = series.values[d.point];
11043 pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11044 seriesIndex: d.series,
11045 pointIndex: d.point
11050 .on('click', function(d) {
11051 mouseEventCallback(d, dispatch.elementClick);
11053 .on('mouseover', function(d) {
11054 mouseEventCallback(d, dispatch.elementMouseover);
11056 .on('mouseout', function(d, i) {
11057 mouseEventCallback(d, dispatch.elementMouseout);
11063 // bring data in form needed for click handlers
11064 var dataWithPoints = vertices.map(function(d, i) {
11067 'series': vertices[i][2],
11068 'point': vertices[i][3]
11073 // add event handlers to points instead voronoi paths
11074 wrap.select('.nv-groups').selectAll('.nv-group')
11075 .selectAll('.nv-point')
11076 //.data(dataWithPoints)
11077 //.style('pointer-events', 'auto') // recativate events, disabled by css
11078 .on('click', function(d,i) {
11079 //nv.log('test', d, i);
11080 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11081 var series = data[d.series],
11082 point = series.values[i];
11084 dispatch.elementClick({
11087 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11088 seriesIndex: d.series,
11092 .on('mouseover', function(d,i) {
11093 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11094 var series = data[d.series],
11095 point = series.values[i];
11097 dispatch.elementMouseover({
11100 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11101 seriesIndex: d.series,
11105 .on('mouseout', function(d,i) {
11106 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11107 var series = data[d.series],
11108 point = series.values[i];
11110 dispatch.elementMouseout({
11113 seriesIndex: d.series,
11119 needsUpdate = false;
11122 needsUpdate = true;
11124 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11125 .data(function(d) { return d }, function(d) { return d.key });
11126 groups.enter().append('g')
11127 .style('stroke-opacity', 1e-6)
11128 .style('fill-opacity', 1e-6);
11132 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11133 .classed('hover', function(d) { return d.hover });
11136 .style('fill', function(d,i) { return color(d, i) })
11137 .style('stroke', function(d,i) { return color(d, i) })
11138 .style('stroke-opacity', 1)
11139 .style('fill-opacity', .5);
11144 var points = groups.selectAll('circle.nv-point')
11145 .data(function(d) { return d.values }, pointKey);
11146 points.enter().append('circle')
11147 .style('fill', function (d,i) { return d.color })
11148 .style('stroke', function (d,i) { return d.color })
11149 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11150 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11151 .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11152 points.exit().remove();
11153 groups.exit().selectAll('path.nv-point').transition()
11154 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11155 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11157 points.each(function(d,i) {
11159 .classed('nv-point', true)
11160 .classed('nv-point-' + i, true)
11161 .classed('hover',false)
11164 points.transition()
11165 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11166 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11167 .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11171 var points = groups.selectAll('path.nv-point')
11172 .data(function(d) { return d.values });
11173 points.enter().append('path')
11174 .style('fill', function (d,i) { return d.color })
11175 .style('stroke', function (d,i) { return d.color })
11176 .attr('transform', function(d,i) {
11177 return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11182 .size(function(d,i) { return z(getSize(d,i)) })
11184 points.exit().remove();
11185 groups.exit().selectAll('path.nv-point')
11187 .attr('transform', function(d,i) {
11188 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11191 points.each(function(d,i) {
11193 .classed('nv-point', true)
11194 .classed('nv-point-' + i, true)
11195 .classed('hover',false)
11198 points.transition()
11199 .attr('transform', function(d,i) {
11200 //nv.log(d,i,getX(d,i), x(getX(d,i)));
11201 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11206 .size(function(d,i) { return z(getSize(d,i)) })
11211 // Delay updating the invisible interactive layer for smoother animation
11212 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11213 timeoutID = setTimeout(updateInteractiveLayer, 300);
11214 //updateInteractiveLayer();
11216 //store old scales for use in transitions on update
11227 //============================================================
11228 // Event Handling/Dispatching (out of chart's scope)
11229 //------------------------------------------------------------
11230 chart.clearHighlights = function() {
11231 //Remove the 'hover' class from all highlighted points.
11232 d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11235 chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11236 d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11237 .classed("hover",isHoverOver);
11241 dispatch.on('elementMouseover.point', function(d) {
11242 if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11245 dispatch.on('elementMouseout.point', function(d) {
11246 if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11249 //============================================================
11252 //============================================================
11253 // Expose Public Variables
11254 //------------------------------------------------------------
11256 chart.dispatch = dispatch;
11257 chart.options = nv.utils.optionsFunc.bind(chart);
11259 chart.x = function(_) {
11260 if (!arguments.length) return getX;
11261 getX = d3.functor(_);
11265 chart.y = function(_) {
11266 if (!arguments.length) return getY;
11267 getY = d3.functor(_);
11271 chart.size = function(_) {
11272 if (!arguments.length) return getSize;
11273 getSize = d3.functor(_);
11277 chart.margin = function(_) {
11278 if (!arguments.length) return margin;
11279 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11280 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11281 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11282 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11286 chart.width = function(_) {
11287 if (!arguments.length) return width;
11292 chart.height = function(_) {
11293 if (!arguments.length) return height;
11298 chart.xScale = function(_) {
11299 if (!arguments.length) return x;
11304 chart.yScale = function(_) {
11305 if (!arguments.length) return y;
11310 chart.zScale = function(_) {
11311 if (!arguments.length) return z;
11316 chart.xDomain = function(_) {
11317 if (!arguments.length) return xDomain;
11322 chart.yDomain = function(_) {
11323 if (!arguments.length) return yDomain;
11328 chart.sizeDomain = function(_) {
11329 if (!arguments.length) return sizeDomain;
11334 chart.xRange = function(_) {
11335 if (!arguments.length) return xRange;
11340 chart.yRange = function(_) {
11341 if (!arguments.length) return yRange;
11346 chart.sizeRange = function(_) {
11347 if (!arguments.length) return sizeRange;
11352 chart.forceX = function(_) {
11353 if (!arguments.length) return forceX;
11358 chart.forceY = function(_) {
11359 if (!arguments.length) return forceY;
11364 chart.forceSize = function(_) {
11365 if (!arguments.length) return forceSize;
11370 chart.interactive = function(_) {
11371 if (!arguments.length) return interactive;
11376 chart.pointKey = function(_) {
11377 if (!arguments.length) return pointKey;
11382 chart.pointActive = function(_) {
11383 if (!arguments.length) return pointActive;
11388 chart.padData = function(_) {
11389 if (!arguments.length) return padData;
11394 chart.padDataOuter = function(_) {
11395 if (!arguments.length) return padDataOuter;
11400 chart.clipEdge = function(_) {
11401 if (!arguments.length) return clipEdge;
11406 chart.clipVoronoi= function(_) {
11407 if (!arguments.length) return clipVoronoi;
11412 chart.useVoronoi= function(_) {
11413 if (!arguments.length) return useVoronoi;
11415 if (useVoronoi === false) {
11416 clipVoronoi = false;
11421 chart.clipRadius = function(_) {
11422 if (!arguments.length) return clipRadius;
11427 chart.color = function(_) {
11428 if (!arguments.length) return color;
11429 color = nv.utils.getColor(_);
11433 chart.shape = function(_) {
11434 if (!arguments.length) return getShape;
11439 chart.onlyCircles = function(_) {
11440 if (!arguments.length) return onlyCircles;
11445 chart.id = function(_) {
11446 if (!arguments.length) return id;
11451 chart.singlePoint = function(_) {
11452 if (!arguments.length) return singlePoint;
11457 //============================================================
11462 nv.models.scatterChart = function() {
11464 //============================================================
11465 // Public Variables with Default Settings
11466 //------------------------------------------------------------
11468 var scatter = nv.models.scatter()
11469 , xAxis = nv.models.axis()
11470 , yAxis = nv.models.axis()
11471 , legend = nv.models.legend()
11472 , controls = nv.models.legend()
11473 , distX = nv.models.distribution()
11474 , distY = nv.models.distribution()
11477 var margin = {top: 30, right: 20, bottom: 50, left: 75}
11480 , color = nv.utils.defaultColor()
11481 , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
11482 , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
11485 , showDistX = false
11486 , showDistY = false
11487 , showLegend = true
11490 , rightAlignYAxis = false
11491 , showControls = !!d3.fisheye
11493 , pauseFisheye = false
11495 , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
11496 , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
11499 , defaultState = null
11500 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11501 , noData = "No Data Available."
11502 , transitionDuration = 250
11514 .orient((rightAlignYAxis) ? 'right' : 'left')
11524 controls.updateState(false);
11526 //============================================================
11529 //============================================================
11530 // Private Variables
11531 //------------------------------------------------------------
11535 var showTooltip = function(e, offsetElement) {
11536 //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
11538 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11539 top = e.pos[1] + ( offsetElement.offsetTop || 0),
11540 leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11541 topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
11542 leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
11543 topY = e.pos[1] + ( offsetElement.offsetTop || 0),
11544 xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
11545 yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
11547 if( tooltipX != null )
11548 nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
11549 if( tooltipY != null )
11550 nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
11551 if( tooltip != null )
11552 nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
11555 var controlsData = [
11556 { key: 'Magnify', disabled: true }
11559 //============================================================
11562 function chart(selection) {
11563 selection.each(function(data) {
11564 var container = d3.select(this),
11567 var availableWidth = (width || parseInt(container.style('width')) || 960)
11568 - margin.left - margin.right,
11569 availableHeight = (height || parseInt(container.style('height')) || 400)
11570 - margin.top - margin.bottom;
11572 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
11573 chart.container = this;
11575 //set state.disabled
11576 state.disabled = data.map(function(d) { return !!d.disabled });
11578 if (!defaultState) {
11581 for (key in state) {
11582 if (state[key] instanceof Array)
11583 defaultState[key] = state[key].slice(0);
11585 defaultState[key] = state[key];
11589 //------------------------------------------------------------
11590 // Display noData message if there's nothing to show.
11592 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11593 var noDataText = container.selectAll('.nv-noData').data([noData]);
11595 noDataText.enter().append('text')
11596 .attr('class', 'nvd3 nv-noData')
11597 .attr('dy', '-.7em')
11598 .style('text-anchor', 'middle');
11601 .attr('x', margin.left + availableWidth / 2)
11602 .attr('y', margin.top + availableHeight / 2)
11603 .text(function(d) { return d });
11607 container.selectAll('.nv-noData').remove();
11610 //------------------------------------------------------------
11613 //------------------------------------------------------------
11619 //------------------------------------------------------------
11622 //------------------------------------------------------------
11623 // Setup containers and skeleton of chart
11625 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11626 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11627 var gEnter = wrapEnter.append('g');
11628 var g = wrap.select('g');
11630 // background for pointer events
11631 gEnter.append('rect').attr('class', 'nvd3 nv-background');
11633 gEnter.append('g').attr('class', 'nv-x nv-axis');
11634 gEnter.append('g').attr('class', 'nv-y nv-axis');
11635 gEnter.append('g').attr('class', 'nv-scatterWrap');
11636 gEnter.append('g').attr('class', 'nv-distWrap');
11637 gEnter.append('g').attr('class', 'nv-legendWrap');
11638 gEnter.append('g').attr('class', 'nv-controlsWrap');
11640 //------------------------------------------------------------
11643 //------------------------------------------------------------
11647 var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
11648 legend.width(legendWidth);
11650 wrap.select('.nv-legendWrap')
11654 if ( margin.top != legend.height()) {
11655 margin.top = legend.height();
11656 availableHeight = (height || parseInt(container.style('height')) || 400)
11657 - margin.top - margin.bottom;
11660 wrap.select('.nv-legendWrap')
11661 .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
11664 //------------------------------------------------------------
11667 //------------------------------------------------------------
11670 if (showControls) {
11671 controls.width(180).color(['#444']);
11672 g.select('.nv-controlsWrap')
11673 .datum(controlsData)
11674 .attr('transform', 'translate(0,' + (-margin.top) +')')
11678 //------------------------------------------------------------
11681 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11683 if (rightAlignYAxis) {
11684 g.select(".nv-y.nv-axis")
11685 .attr("transform", "translate(" + availableWidth + ",0)");
11688 //------------------------------------------------------------
11689 // Main Chart Component(s)
11692 .width(availableWidth)
11693 .height(availableHeight)
11694 .color(data.map(function(d,i) {
11695 return d.color || color(d, i);
11696 }).filter(function(d,i) { return !data[i].disabled }));
11698 if (xPadding !== 0)
11699 scatter.xDomain(null);
11701 if (yPadding !== 0)
11702 scatter.yDomain(null);
11704 wrap.select('.nv-scatterWrap')
11705 .datum(data.filter(function(d) { return !d.disabled }))
11708 //Adjust for x and y padding
11709 if (xPadding !== 0) {
11710 var xRange = x.domain()[1] - x.domain()[0];
11711 scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
11714 if (yPadding !== 0) {
11715 var yRange = y.domain()[1] - y.domain()[0];
11716 scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
11719 //Only need to update the scatter again if x/yPadding changed the domain.
11720 if (yPadding !== 0 || xPadding !== 0) {
11721 wrap.select('.nv-scatterWrap')
11722 .datum(data.filter(function(d) { return !d.disabled }))
11726 //------------------------------------------------------------
11729 //------------------------------------------------------------
11734 .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
11735 .tickSize( -availableHeight , 0);
11737 g.select('.nv-x.nv-axis')
11738 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11746 .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
11747 .tickSize( -availableWidth, 0);
11749 g.select('.nv-y.nv-axis')
11756 .getData(scatter.x())
11758 .width(availableWidth)
11759 .color(data.map(function(d,i) {
11760 return d.color || color(d, i);
11761 }).filter(function(d,i) { return !data[i].disabled }));
11762 gEnter.select('.nv-distWrap').append('g')
11763 .attr('class', 'nv-distributionX');
11764 g.select('.nv-distributionX')
11765 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11766 .datum(data.filter(function(d) { return !d.disabled }))
11772 .getData(scatter.y())
11774 .width(availableHeight)
11775 .color(data.map(function(d,i) {
11776 return d.color || color(d, i);
11777 }).filter(function(d,i) { return !data[i].disabled }));
11778 gEnter.select('.nv-distWrap').append('g')
11779 .attr('class', 'nv-distributionY');
11780 g.select('.nv-distributionY')
11782 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11783 .datum(data.filter(function(d) { return !d.disabled }))
11787 //------------------------------------------------------------
11793 g.select('.nv-background')
11794 .attr('width', availableWidth)
11795 .attr('height', availableHeight);
11797 g.select('.nv-background').on('mousemove', updateFisheye);
11798 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
11799 scatter.dispatch.on('elementClick.freezeFisheye', function() {
11800 pauseFisheye = !pauseFisheye;
11805 function updateFisheye() {
11806 if (pauseFisheye) {
11807 g.select('.nv-point-paths').style('pointer-events', 'all');
11811 g.select('.nv-point-paths').style('pointer-events', 'none' );
11813 var mouse = d3.mouse(this);
11814 x.distortion(fisheye).focus(mouse[0]);
11815 y.distortion(fisheye).focus(mouse[1]);
11817 g.select('.nv-scatterWrap')
11821 g.select('.nv-x.nv-axis').call(xAxis);
11824 g.select('.nv-y.nv-axis').call(yAxis);
11826 g.select('.nv-distributionX')
11827 .datum(data.filter(function(d) { return !d.disabled }))
11829 g.select('.nv-distributionY')
11830 .datum(data.filter(function(d) { return !d.disabled }))
11836 //============================================================
11837 // Event Handling/Dispatching (in chart's scope)
11838 //------------------------------------------------------------
11840 controls.dispatch.on('legendClick', function(d,i) {
11841 d.disabled = !d.disabled;
11843 fisheye = d.disabled ? 0 : 2.5;
11844 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
11845 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
11848 x.distortion(fisheye).focus(0);
11849 y.distortion(fisheye).focus(0);
11851 g.select('.nv-scatterWrap').call(scatter);
11852 g.select('.nv-x.nv-axis').call(xAxis);
11853 g.select('.nv-y.nv-axis').call(yAxis);
11855 pauseFisheye = false;
11861 legend.dispatch.on('stateChange', function(newState) {
11862 state.disabled = newState.disabled;
11863 dispatch.stateChange(state);
11867 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
11868 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11869 .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
11870 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11871 .attr('x2', e.pos[0] + distX.size());
11873 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11874 dispatch.tooltipShow(e);
11877 dispatch.on('tooltipShow', function(e) {
11878 if (tooltips) showTooltip(e, that.parentNode);
11881 // Update chart from a state object passed to event handler
11882 dispatch.on('changeState', function(e) {
11884 if (typeof e.disabled !== 'undefined') {
11885 data.forEach(function(series,i) {
11886 series.disabled = e.disabled[i];
11889 state.disabled = e.disabled;
11895 //============================================================
11898 //store old scales for use in transitions on update
11909 //============================================================
11910 // Event Handling/Dispatching (out of chart's scope)
11911 //------------------------------------------------------------
11913 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11914 dispatch.tooltipHide(e);
11916 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11918 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11919 .attr('x2', distY.size());
11921 dispatch.on('tooltipHide', function() {
11922 if (tooltips) nv.tooltip.cleanup();
11925 //============================================================
11928 //============================================================
11929 // Expose Public Variables
11930 //------------------------------------------------------------
11932 // expose chart's sub-components
11933 chart.dispatch = dispatch;
11934 chart.scatter = scatter;
11935 chart.legend = legend;
11936 chart.controls = controls;
11937 chart.xAxis = xAxis;
11938 chart.yAxis = yAxis;
11939 chart.distX = distX;
11940 chart.distY = distY;
11942 d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
11943 chart.options = nv.utils.optionsFunc.bind(chart);
11945 chart.margin = function(_) {
11946 if (!arguments.length) return margin;
11947 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11948 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11949 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11950 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11954 chart.width = function(_) {
11955 if (!arguments.length) return width;
11960 chart.height = function(_) {
11961 if (!arguments.length) return height;
11966 chart.color = function(_) {
11967 if (!arguments.length) return color;
11968 color = nv.utils.getColor(_);
11969 legend.color(color);
11970 distX.color(color);
11971 distY.color(color);
11975 chart.showDistX = function(_) {
11976 if (!arguments.length) return showDistX;
11981 chart.showDistY = function(_) {
11982 if (!arguments.length) return showDistY;
11987 chart.showControls = function(_) {
11988 if (!arguments.length) return showControls;
11993 chart.showLegend = function(_) {
11994 if (!arguments.length) return showLegend;
11999 chart.showXAxis = function(_) {
12000 if (!arguments.length) return showXAxis;
12005 chart.showYAxis = function(_) {
12006 if (!arguments.length) return showYAxis;
12011 chart.rightAlignYAxis = function(_) {
12012 if(!arguments.length) return rightAlignYAxis;
12013 rightAlignYAxis = _;
12014 yAxis.orient( (_) ? 'right' : 'left');
12019 chart.fisheye = function(_) {
12020 if (!arguments.length) return fisheye;
12025 chart.xPadding = function(_) {
12026 if (!arguments.length) return xPadding;
12031 chart.yPadding = function(_) {
12032 if (!arguments.length) return yPadding;
12037 chart.tooltips = function(_) {
12038 if (!arguments.length) return tooltips;
12043 chart.tooltipContent = function(_) {
12044 if (!arguments.length) return tooltip;
12049 chart.tooltipXContent = function(_) {
12050 if (!arguments.length) return tooltipX;
12055 chart.tooltipYContent = function(_) {
12056 if (!arguments.length) return tooltipY;
12061 chart.state = function(_) {
12062 if (!arguments.length) return state;
12067 chart.defaultState = function(_) {
12068 if (!arguments.length) return defaultState;
12073 chart.noData = function(_) {
12074 if (!arguments.length) return noData;
12079 chart.transitionDuration = function(_) {
12080 if (!arguments.length) return transitionDuration;
12081 transitionDuration = _;
12085 //============================================================
12091 nv.models.scatterPlusLineChart = function() {
12093 //============================================================
12094 // Public Variables with Default Settings
12095 //------------------------------------------------------------
12097 var scatter = nv.models.scatter()
12098 , xAxis = nv.models.axis()
12099 , yAxis = nv.models.axis()
12100 , legend = nv.models.legend()
12101 , controls = nv.models.legend()
12102 , distX = nv.models.distribution()
12103 , distY = nv.models.distribution()
12106 var margin = {top: 30, right: 20, bottom: 50, left: 75}
12109 , color = nv.utils.defaultColor()
12110 , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12111 , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12112 , showDistX = false
12113 , showDistY = false
12114 , showLegend = true
12117 , rightAlignYAxis = false
12118 , showControls = !!d3.fisheye
12120 , pauseFisheye = false
12122 , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
12123 , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
12124 , tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
12125 + '<p>' + date + '</p>' }
12127 , defaultState = null
12128 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
12129 , noData = "No Data Available."
12130 , transitionDuration = 250
12142 .orient((rightAlignYAxis) ? 'right' : 'left')
12152 controls.updateState(false);
12153 //============================================================
12156 //============================================================
12157 // Private Variables
12158 //------------------------------------------------------------
12162 var showTooltip = function(e, offsetElement) {
12163 //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12165 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12166 top = e.pos[1] + ( offsetElement.offsetTop || 0),
12167 leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12168 topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12169 leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12170 topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12171 xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12172 yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12174 if( tooltipX != null )
12175 nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12176 if( tooltipY != null )
12177 nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12178 if( tooltip != null )
12179 nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12182 var controlsData = [
12183 { key: 'Magnify', disabled: true }
12186 //============================================================
12189 function chart(selection) {
12190 selection.each(function(data) {
12191 var container = d3.select(this),
12194 var availableWidth = (width || parseInt(container.style('width')) || 960)
12195 - margin.left - margin.right,
12196 availableHeight = (height || parseInt(container.style('height')) || 400)
12197 - margin.top - margin.bottom;
12199 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
12200 chart.container = this;
12202 //set state.disabled
12203 state.disabled = data.map(function(d) { return !!d.disabled });
12205 if (!defaultState) {
12208 for (key in state) {
12209 if (state[key] instanceof Array)
12210 defaultState[key] = state[key].slice(0);
12212 defaultState[key] = state[key];
12216 //------------------------------------------------------------
12217 // Display noData message if there's nothing to show.
12219 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12220 var noDataText = container.selectAll('.nv-noData').data([noData]);
12222 noDataText.enter().append('text')
12223 .attr('class', 'nvd3 nv-noData')
12224 .attr('dy', '-.7em')
12225 .style('text-anchor', 'middle');
12228 .attr('x', margin.left + availableWidth / 2)
12229 .attr('y', margin.top + availableHeight / 2)
12230 .text(function(d) { return d });
12234 container.selectAll('.nv-noData').remove();
12237 //------------------------------------------------------------
12240 //------------------------------------------------------------
12243 x = scatter.xScale();
12244 y = scatter.yScale();
12249 //------------------------------------------------------------
12252 //------------------------------------------------------------
12253 // Setup containers and skeleton of chart
12255 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12256 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12257 var gEnter = wrapEnter.append('g');
12258 var g = wrap.select('g')
12260 // background for pointer events
12261 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12263 gEnter.append('g').attr('class', 'nv-x nv-axis');
12264 gEnter.append('g').attr('class', 'nv-y nv-axis');
12265 gEnter.append('g').attr('class', 'nv-scatterWrap');
12266 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12267 gEnter.append('g').attr('class', 'nv-distWrap');
12268 gEnter.append('g').attr('class', 'nv-legendWrap');
12269 gEnter.append('g').attr('class', 'nv-controlsWrap');
12271 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12273 if (rightAlignYAxis) {
12274 g.select(".nv-y.nv-axis")
12275 .attr("transform", "translate(" + availableWidth + ",0)");
12278 //------------------------------------------------------------
12281 //------------------------------------------------------------
12285 legend.width( availableWidth / 2 );
12287 wrap.select('.nv-legendWrap')
12291 if ( margin.top != legend.height()) {
12292 margin.top = legend.height();
12293 availableHeight = (height || parseInt(container.style('height')) || 400)
12294 - margin.top - margin.bottom;
12297 wrap.select('.nv-legendWrap')
12298 .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
12301 //------------------------------------------------------------
12304 //------------------------------------------------------------
12307 if (showControls) {
12308 controls.width(180).color(['#444']);
12309 g.select('.nv-controlsWrap')
12310 .datum(controlsData)
12311 .attr('transform', 'translate(0,' + (-margin.top) +')')
12315 //------------------------------------------------------------
12318 //------------------------------------------------------------
12319 // Main Chart Component(s)
12322 .width(availableWidth)
12323 .height(availableHeight)
12324 .color(data.map(function(d,i) {
12325 return d.color || color(d, i);
12326 }).filter(function(d,i) { return !data[i].disabled }))
12328 wrap.select('.nv-scatterWrap')
12329 .datum(data.filter(function(d) { return !d.disabled }))
12332 wrap.select('.nv-regressionLinesWrap')
12333 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
12335 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
12336 .data(function(d) {return d });
12338 regWrap.enter().append('g').attr('class', 'nv-regLines');
12340 var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
12341 var regLineEnter = regLine.enter()
12342 .append('line').attr('class', 'nv-regLine')
12343 .style('stroke-opacity', 0);
12347 .attr('x1', x.range()[0])
12348 .attr('x2', x.range()[1])
12349 .attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
12350 .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
12351 .style('stroke', function(d,i,j) { return color(d,j) })
12352 .style('stroke-opacity', function(d,i) {
12353 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
12356 //------------------------------------------------------------
12359 //------------------------------------------------------------
12365 .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
12366 .tickSize( -availableHeight , 0);
12368 g.select('.nv-x.nv-axis')
12369 .attr('transform', 'translate(0,' + y.range()[0] + ')')
12376 .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
12377 .tickSize( -availableWidth, 0);
12379 g.select('.nv-y.nv-axis')
12386 .getData(scatter.x())
12388 .width(availableWidth)
12389 .color(data.map(function(d,i) {
12390 return d.color || color(d, i);
12391 }).filter(function(d,i) { return !data[i].disabled }));
12392 gEnter.select('.nv-distWrap').append('g')
12393 .attr('class', 'nv-distributionX');
12394 g.select('.nv-distributionX')
12395 .attr('transform', 'translate(0,' + y.range()[0] + ')')
12396 .datum(data.filter(function(d) { return !d.disabled }))
12402 .getData(scatter.y())
12404 .width(availableHeight)
12405 .color(data.map(function(d,i) {
12406 return d.color || color(d, i);
12407 }).filter(function(d,i) { return !data[i].disabled }));
12408 gEnter.select('.nv-distWrap').append('g')
12409 .attr('class', 'nv-distributionY');
12410 g.select('.nv-distributionY')
12411 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12412 .datum(data.filter(function(d) { return !d.disabled }))
12416 //------------------------------------------------------------
12422 g.select('.nv-background')
12423 .attr('width', availableWidth)
12424 .attr('height', availableHeight)
12427 g.select('.nv-background').on('mousemove', updateFisheye);
12428 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12429 scatter.dispatch.on('elementClick.freezeFisheye', function() {
12430 pauseFisheye = !pauseFisheye;
12435 function updateFisheye() {
12436 if (pauseFisheye) {
12437 g.select('.nv-point-paths').style('pointer-events', 'all');
12441 g.select('.nv-point-paths').style('pointer-events', 'none' );
12443 var mouse = d3.mouse(this);
12444 x.distortion(fisheye).focus(mouse[0]);
12445 y.distortion(fisheye).focus(mouse[1]);
12447 g.select('.nv-scatterWrap')
12448 .datum(data.filter(function(d) { return !d.disabled }))
12452 g.select('.nv-x.nv-axis').call(xAxis);
12455 g.select('.nv-y.nv-axis').call(yAxis);
12457 g.select('.nv-distributionX')
12458 .datum(data.filter(function(d) { return !d.disabled }))
12460 g.select('.nv-distributionY')
12461 .datum(data.filter(function(d) { return !d.disabled }))
12467 //============================================================
12468 // Event Handling/Dispatching (in chart's scope)
12469 //------------------------------------------------------------
12471 controls.dispatch.on('legendClick', function(d,i) {
12472 d.disabled = !d.disabled;
12474 fisheye = d.disabled ? 0 : 2.5;
12475 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12476 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12479 x.distortion(fisheye).focus(0);
12480 y.distortion(fisheye).focus(0);
12482 g.select('.nv-scatterWrap').call(scatter);
12483 g.select('.nv-x.nv-axis').call(xAxis);
12484 g.select('.nv-y.nv-axis').call(yAxis);
12486 pauseFisheye = false;
12492 legend.dispatch.on('stateChange', function(newState) {
12494 dispatch.stateChange(state);
12499 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12500 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12501 .attr('y1', e.pos[1] - availableHeight);
12502 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12503 .attr('x2', e.pos[0] + distX.size());
12505 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12506 dispatch.tooltipShow(e);
12509 dispatch.on('tooltipShow', function(e) {
12510 if (tooltips) showTooltip(e, that.parentNode);
12513 // Update chart from a state object passed to event handler
12514 dispatch.on('changeState', function(e) {
12516 if (typeof e.disabled !== 'undefined') {
12517 data.forEach(function(series,i) {
12518 series.disabled = e.disabled[i];
12521 state.disabled = e.disabled;
12527 //============================================================
12530 //store old scales for use in transitions on update
12541 //============================================================
12542 // Event Handling/Dispatching (out of chart's scope)
12543 //------------------------------------------------------------
12545 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12546 dispatch.tooltipHide(e);
12548 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12550 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12551 .attr('x2', distY.size());
12553 dispatch.on('tooltipHide', function() {
12554 if (tooltips) nv.tooltip.cleanup();
12557 //============================================================
12560 //============================================================
12561 // Expose Public Variables
12562 //------------------------------------------------------------
12564 // expose chart's sub-components
12565 chart.dispatch = dispatch;
12566 chart.scatter = scatter;
12567 chart.legend = legend;
12568 chart.controls = controls;
12569 chart.xAxis = xAxis;
12570 chart.yAxis = yAxis;
12571 chart.distX = distX;
12572 chart.distY = distY;
12574 d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
12576 chart.options = nv.utils.optionsFunc.bind(chart);
12578 chart.margin = function(_) {
12579 if (!arguments.length) return margin;
12580 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12581 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12582 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12583 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12587 chart.width = function(_) {
12588 if (!arguments.length) return width;
12593 chart.height = function(_) {
12594 if (!arguments.length) return height;
12599 chart.color = function(_) {
12600 if (!arguments.length) return color;
12601 color = nv.utils.getColor(_);
12602 legend.color(color);
12603 distX.color(color);
12604 distY.color(color);
12608 chart.showDistX = function(_) {
12609 if (!arguments.length) return showDistX;
12614 chart.showDistY = function(_) {
12615 if (!arguments.length) return showDistY;
12620 chart.showControls = function(_) {
12621 if (!arguments.length) return showControls;
12626 chart.showLegend = function(_) {
12627 if (!arguments.length) return showLegend;
12632 chart.showXAxis = function(_) {
12633 if (!arguments.length) return showXAxis;
12638 chart.showYAxis = function(_) {
12639 if (!arguments.length) return showYAxis;
12644 chart.rightAlignYAxis = function(_) {
12645 if(!arguments.length) return rightAlignYAxis;
12646 rightAlignYAxis = _;
12647 yAxis.orient( (_) ? 'right' : 'left');
12651 chart.fisheye = function(_) {
12652 if (!arguments.length) return fisheye;
12657 chart.tooltips = function(_) {
12658 if (!arguments.length) return tooltips;
12663 chart.tooltipContent = function(_) {
12664 if (!arguments.length) return tooltip;
12669 chart.tooltipXContent = function(_) {
12670 if (!arguments.length) return tooltipX;
12675 chart.tooltipYContent = function(_) {
12676 if (!arguments.length) return tooltipY;
12681 chart.state = function(_) {
12682 if (!arguments.length) return state;
12687 chart.defaultState = function(_) {
12688 if (!arguments.length) return defaultState;
12693 chart.noData = function(_) {
12694 if (!arguments.length) return noData;
12699 chart.transitionDuration = function(_) {
12700 if (!arguments.length) return transitionDuration;
12701 transitionDuration = _;
12705 //============================================================
12711 nv.models.sparkline = function() {
12713 //============================================================
12714 // Public Variables with Default Settings
12715 //------------------------------------------------------------
12717 var margin = {top: 2, right: 0, bottom: 2, left: 0}
12721 , x = d3.scale.linear()
12722 , y = d3.scale.linear()
12723 , getX = function(d) { return d.x }
12724 , getY = function(d) { return d.y }
12725 , color = nv.utils.getColor(['#000'])
12732 //============================================================
12735 function chart(selection) {
12736 selection.each(function(data) {
12737 var availableWidth = width - margin.left - margin.right,
12738 availableHeight = height - margin.top - margin.bottom,
12739 container = d3.select(this);
12742 //------------------------------------------------------------
12745 x .domain(xDomain || d3.extent(data, getX ))
12746 .range(xRange || [0, availableWidth]);
12748 y .domain(yDomain || d3.extent(data, getY ))
12749 .range(yRange || [availableHeight, 0]);
12751 //------------------------------------------------------------
12754 //------------------------------------------------------------
12755 // Setup containers and skeleton of chart
12757 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
12758 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
12759 var gEnter = wrapEnter.append('g');
12760 var g = wrap.select('g');
12762 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
12764 //------------------------------------------------------------
12767 var paths = wrap.selectAll('path')
12768 .data(function(d) { return [d] });
12769 paths.enter().append('path');
12770 paths.exit().remove();
12772 .style('stroke', function(d,i) { return d.color || color(d, i) })
12773 .attr('d', d3.svg.line()
12774 .x(function(d,i) { return x(getX(d,i)) })
12775 .y(function(d,i) { return y(getY(d,i)) })
12779 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
12780 var points = wrap.selectAll('circle.nv-point')
12781 .data(function(data) {
12782 var yValues = data.map(function(d, i) { return getY(d,i); });
12783 function pointIndex(index) {
12785 var result = data[index];
12786 result.pointIndex = index;
12792 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
12793 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
12794 currentPoint = pointIndex(yValues.length - 1);
12795 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
12797 points.enter().append('circle');
12798 points.exit().remove();
12800 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
12801 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
12803 .attr('class', function(d,i) {
12804 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
12805 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
12813 //============================================================
12814 // Expose Public Variables
12815 //------------------------------------------------------------
12816 chart.options = nv.utils.optionsFunc.bind(chart);
12818 chart.margin = function(_) {
12819 if (!arguments.length) return margin;
12820 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12821 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12822 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12823 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12827 chart.width = function(_) {
12828 if (!arguments.length) return width;
12833 chart.height = function(_) {
12834 if (!arguments.length) return height;
12839 chart.x = function(_) {
12840 if (!arguments.length) return getX;
12841 getX = d3.functor(_);
12845 chart.y = function(_) {
12846 if (!arguments.length) return getY;
12847 getY = d3.functor(_);
12851 chart.xScale = function(_) {
12852 if (!arguments.length) return x;
12857 chart.yScale = function(_) {
12858 if (!arguments.length) return y;
12863 chart.xDomain = function(_) {
12864 if (!arguments.length) return xDomain;
12869 chart.yDomain = function(_) {
12870 if (!arguments.length) return yDomain;
12875 chart.xRange = function(_) {
12876 if (!arguments.length) return xRange;
12881 chart.yRange = function(_) {
12882 if (!arguments.length) return yRange;
12887 chart.animate = function(_) {
12888 if (!arguments.length) return animate;
12893 chart.color = function(_) {
12894 if (!arguments.length) return color;
12895 color = nv.utils.getColor(_);
12899 //============================================================
12905 nv.models.sparklinePlus = function() {
12907 //============================================================
12908 // Public Variables with Default Settings
12909 //------------------------------------------------------------
12911 var sparkline = nv.models.sparkline();
12913 var margin = {top: 15, right: 100, bottom: 10, left: 50}
12920 , xTickFormat = d3.format(',r')
12921 , yTickFormat = d3.format(',.2f')
12923 , alignValue = true
12924 , rightAlignValue = false
12925 , noData = "No Data Available."
12928 //============================================================
12931 function chart(selection) {
12932 selection.each(function(data) {
12933 var container = d3.select(this);
12935 var availableWidth = (width || parseInt(container.style('width')) || 960)
12936 - margin.left - margin.right,
12937 availableHeight = (height || parseInt(container.style('height')) || 400)
12938 - margin.top - margin.bottom;
12942 chart.update = function() { chart(selection) };
12943 chart.container = this;
12946 //------------------------------------------------------------
12947 // Display No Data message if there's nothing to show.
12949 if (!data || !data.length) {
12950 var noDataText = container.selectAll('.nv-noData').data([noData]);
12952 noDataText.enter().append('text')
12953 .attr('class', 'nvd3 nv-noData')
12954 .attr('dy', '-.7em')
12955 .style('text-anchor', 'middle');
12958 .attr('x', margin.left + availableWidth / 2)
12959 .attr('y', margin.top + availableHeight / 2)
12960 .text(function(d) { return d });
12964 container.selectAll('.nv-noData').remove();
12967 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
12969 //------------------------------------------------------------
12973 //------------------------------------------------------------
12976 x = sparkline.xScale();
12977 y = sparkline.yScale();
12979 //------------------------------------------------------------
12982 //------------------------------------------------------------
12983 // Setup containers and skeleton of chart
12985 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
12986 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
12987 var gEnter = wrapEnter.append('g');
12988 var g = wrap.select('g');
12990 gEnter.append('g').attr('class', 'nv-sparklineWrap');
12991 gEnter.append('g').attr('class', 'nv-valueWrap');
12992 gEnter.append('g').attr('class', 'nv-hoverArea');
12994 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12996 //------------------------------------------------------------
12999 //------------------------------------------------------------
13000 // Main Chart Component(s)
13002 var sparklineWrap = g.select('.nv-sparklineWrap');
13005 .width(availableWidth)
13006 .height(availableHeight);
13011 //------------------------------------------------------------
13014 var valueWrap = g.select('.nv-valueWrap');
13016 var value = valueWrap.selectAll('.nv-currentValue')
13017 .data([currentValue]);
13019 value.enter().append('text').attr('class', 'nv-currentValue')
13020 .attr('dx', rightAlignValue ? -8 : 8)
13021 .attr('dy', '.9em')
13022 .style('text-anchor', rightAlignValue ? 'end' : 'start');
13025 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13026 .attr('y', alignValue ? function(d) { return y(d) } : 0)
13027 .style('fill', sparkline.color()(data[data.length-1], data.length-1))
13028 .text(yTickFormat(currentValue));
13032 gEnter.select('.nv-hoverArea').append('rect')
13033 .on('mousemove', sparklineHover)
13034 .on('click', function() { paused = !paused })
13035 .on('mouseout', function() { index = []; updateValueLine(); });
13036 //.on('mouseout', function() { index = null; updateValueLine(); });
13038 g.select('.nv-hoverArea rect')
13039 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13040 .attr('width', availableWidth + margin.left + margin.right)
13041 .attr('height', availableHeight + margin.top);
13045 function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13046 if (paused) return;
13048 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13050 var hoverEnter = hoverValue.enter()
13051 .append('g').attr('class', 'nv-hoverValue')
13052 .style('stroke-opacity', 0)
13053 .style('fill-opacity', 0);
13056 .transition().duration(250)
13057 .style('stroke-opacity', 0)
13058 .style('fill-opacity', 0)
13062 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13063 .transition().duration(250)
13064 .style('stroke-opacity', 1)
13065 .style('fill-opacity', 1);
13067 if (!index.length) return;
13069 hoverEnter.append('line')
13071 .attr('y1', -margin.top)
13073 .attr('y2', availableHeight);
13076 hoverEnter.append('text').attr('class', 'nv-xValue')
13078 .attr('y', -margin.top)
13079 .attr('text-anchor', 'end')
13080 .attr('dy', '.9em')
13083 g.select('.nv-hoverValue .nv-xValue')
13084 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13086 hoverEnter.append('text').attr('class', 'nv-yValue')
13088 .attr('y', -margin.top)
13089 .attr('text-anchor', 'start')
13090 .attr('dy', '.9em')
13092 g.select('.nv-hoverValue .nv-yValue')
13093 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13098 function sparklineHover() {
13099 if (paused) return;
13101 var pos = d3.mouse(this)[0] - margin.left;
13103 function getClosestIndex(data, x) {
13104 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13105 var closestIndex = 0;
13106 for (var i = 0; i < data.length; i++){
13107 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13108 distance = Math.abs(sparkline.x()(data[i], i) - x);
13112 return closestIndex;
13115 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13126 //============================================================
13127 // Expose Public Variables
13128 //------------------------------------------------------------
13130 // expose chart's sub-components
13131 chart.sparkline = sparkline;
13133 d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13135 chart.options = nv.utils.optionsFunc.bind(chart);
13137 chart.margin = function(_) {
13138 if (!arguments.length) return margin;
13139 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13140 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13141 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13142 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13146 chart.width = function(_) {
13147 if (!arguments.length) return width;
13152 chart.height = function(_) {
13153 if (!arguments.length) return height;
13158 chart.xTickFormat = function(_) {
13159 if (!arguments.length) return xTickFormat;
13164 chart.yTickFormat = function(_) {
13165 if (!arguments.length) return yTickFormat;
13170 chart.showValue = function(_) {
13171 if (!arguments.length) return showValue;
13176 chart.alignValue = function(_) {
13177 if (!arguments.length) return alignValue;
13182 chart.rightAlignValue = function(_) {
13183 if (!arguments.length) return rightAlignValue;
13184 rightAlignValue = _;
13188 chart.noData = function(_) {
13189 if (!arguments.length) return noData;
13194 //============================================================
13200 nv.models.stackedArea = function() {
13202 //============================================================
13203 // Public Variables with Default Settings
13204 //------------------------------------------------------------
13206 var margin = {top: 0, right: 0, bottom: 0, left: 0}
13209 , color = nv.utils.defaultColor() // a function that computes the color
13210 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13211 , getX = function(d) { return d.x } // accessor to get the x value from a data point
13212 , getY = function(d) { return d.y } // accessor to get the y value from a data point
13215 , order = 'default'
13216 , interpolate = 'linear' // controls the line interpolation
13217 , clipEdge = false // if true, masks lines within x and y scale
13218 , x //can be accessed via chart.xScale()
13219 , y //can be accessed via chart.yScale()
13220 , scatter = nv.models.scatter()
13221 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
13225 .size(2.2) // default size
13226 .sizeDomain([2.2,2.2]) // all the same size by default
13229 /************************************
13231 * 'wiggle' (stream)
13233 * 'expand' (normalize to 100%)
13234 * 'silhouette' (simple centered)
13237 * 'inside-out' (stream)
13238 * 'default' (input order)
13239 ************************************/
13241 //============================================================
13244 function chart(selection) {
13245 selection.each(function(data) {
13246 var availableWidth = width - margin.left - margin.right,
13247 availableHeight = height - margin.top - margin.bottom,
13248 container = d3.select(this);
13250 //------------------------------------------------------------
13253 x = scatter.xScale();
13254 y = scatter.yScale();
13256 //------------------------------------------------------------
13259 // Injecting point index into each point because d3.layout.stack().out does not give index
13260 data = data.map(function(aseries, i) {
13261 aseries.seriesIndex = i;
13262 aseries.values = aseries.values.map(function(d, j) {
13270 var dataFiltered = data.filter(function(series) {
13271 return !series.disabled;
13274 data = d3.layout.stack()
13277 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
13280 .out(function(d, y0, y) {
13281 var yHeight = (getY(d) === 0) ? 0 : y;
13290 //------------------------------------------------------------
13291 // Setup containers and skeleton of chart
13293 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
13294 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
13295 var defsEnter = wrapEnter.append('defs');
13296 var gEnter = wrapEnter.append('g');
13297 var g = wrap.select('g');
13299 gEnter.append('g').attr('class', 'nv-areaWrap');
13300 gEnter.append('g').attr('class', 'nv-scatterWrap');
13302 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13304 //------------------------------------------------------------
13308 .width(availableWidth)
13309 .height(availableHeight)
13311 .y(function(d) { return d.display.y + d.display.y0 })
13313 .color(data.map(function(d,i) {
13314 return d.color || color(d, d.seriesIndex);
13318 var scatterWrap = g.select('.nv-scatterWrap')
13321 scatterWrap.call(scatter);
13323 defsEnter.append('clipPath')
13324 .attr('id', 'nv-edge-clip-' + id)
13327 wrap.select('#nv-edge-clip-' + id + ' rect')
13328 .attr('width', availableWidth)
13329 .attr('height', availableHeight);
13331 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
13333 var area = d3.svg.area()
13334 .x(function(d,i) { return x(getX(d,i)) })
13336 return y(d.display.y0)
13339 return y(d.display.y + d.display.y0)
13341 .interpolate(interpolate);
13343 var zeroArea = d3.svg.area()
13344 .x(function(d,i) { return x(getX(d,i)) })
13345 .y0(function(d) { return y(d.display.y0) })
13346 .y1(function(d) { return y(d.display.y0) });
13349 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
13350 .data(function(d) { return d });
13352 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
13353 .attr('d', function(d,i){
13354 return zeroArea(d.values, d.seriesIndex);
13356 .on('mouseover', function(d,i) {
13357 d3.select(this).classed('hover', true);
13358 dispatch.areaMouseover({
13361 pos: [d3.event.pageX, d3.event.pageY],
13365 .on('mouseout', function(d,i) {
13366 d3.select(this).classed('hover', false);
13367 dispatch.areaMouseout({
13370 pos: [d3.event.pageX, d3.event.pageY],
13374 .on('click', function(d,i) {
13375 d3.select(this).classed('hover', false);
13376 dispatch.areaClick({
13379 pos: [d3.event.pageX, d3.event.pageY],
13383 path.exit().transition()
13384 .attr('d', function(d,i) { return zeroArea(d.values,i) })
13387 .style('fill', function(d,i){
13388 return d.color || color(d, d.seriesIndex)
13390 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
13392 .attr('d', function(d,i) {
13393 return area(d.values,i)
13398 //============================================================
13399 // Event Handling/Dispatching (in chart's scope)
13400 //------------------------------------------------------------
13402 scatter.dispatch.on('elementMouseover.area', function(e) {
13403 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
13405 scatter.dispatch.on('elementMouseout.area', function(e) {
13406 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
13409 //============================================================
13418 //============================================================
13419 // Event Handling/Dispatching (out of chart's scope)
13420 //------------------------------------------------------------
13422 scatter.dispatch.on('elementClick.area', function(e) {
13423 dispatch.areaClick(e);
13425 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13426 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13427 dispatch.tooltipShow(e);
13429 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13430 dispatch.tooltipHide(e);
13433 //============================================================
13436 //============================================================
13437 // Global getters and setters
13438 //------------------------------------------------------------
13440 chart.dispatch = dispatch;
13441 chart.scatter = scatter;
13443 d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
13444 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
13446 chart.options = nv.utils.optionsFunc.bind(chart);
13448 chart.x = function(_) {
13449 if (!arguments.length) return getX;
13450 getX = d3.functor(_);
13454 chart.y = function(_) {
13455 if (!arguments.length) return getY;
13456 getY = d3.functor(_);
13460 chart.margin = function(_) {
13461 if (!arguments.length) return margin;
13462 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13463 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13464 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13465 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13469 chart.width = function(_) {
13470 if (!arguments.length) return width;
13475 chart.height = function(_) {
13476 if (!arguments.length) return height;
13481 chart.clipEdge = function(_) {
13482 if (!arguments.length) return clipEdge;
13487 chart.color = function(_) {
13488 if (!arguments.length) return color;
13489 color = nv.utils.getColor(_);
13493 chart.offset = function(_) {
13494 if (!arguments.length) return offset;
13499 chart.order = function(_) {
13500 if (!arguments.length) return order;
13505 //shortcut for offset + order
13506 chart.style = function(_) {
13507 if (!arguments.length) return style;
13512 chart.offset('zero');
13513 chart.order('default');
13516 chart.offset('wiggle');
13517 chart.order('inside-out');
13519 case 'stream-center':
13520 chart.offset('silhouette');
13521 chart.order('inside-out');
13524 chart.offset('expand');
13525 chart.order('default');
13532 chart.interpolate = function(_) {
13533 if (!arguments.length) return interpolate;
13537 //============================================================
13543 nv.models.stackedAreaChart = function() {
13545 //============================================================
13546 // Public Variables with Default Settings
13547 //------------------------------------------------------------
13549 var stacked = nv.models.stackedArea()
13550 , xAxis = nv.models.axis()
13551 , yAxis = nv.models.axis()
13552 , legend = nv.models.legend()
13553 , controls = nv.models.legend()
13554 , interactiveLayer = nv.interactiveGuideline()
13557 var margin = {top: 30, right: 25, bottom: 50, left: 60}
13560 , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
13561 , showControls = true
13562 , showLegend = true
13565 , rightAlignYAxis = false
13566 , useInteractiveGuideline = false
13568 , tooltip = function(key, x, y, e, graph) {
13569 return '<h3>' + key + '</h3>' +
13570 '<p>' + y + ' on ' + x + '</p>'
13572 , x //can be accessed via chart.xScale()
13573 , y //can be accessed via chart.yScale()
13574 , yAxisTickFormat = d3.format(',.2f')
13575 , state = { style: stacked.style() }
13576 , defaultState = null
13577 , noData = 'No Data Available.'
13578 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
13579 , controlWidth = 250
13580 , cData = ['Stacked','Stream','Expanded']
13581 , transitionDuration = 250
13589 .orient((rightAlignYAxis) ? 'right' : 'left')
13592 controls.updateState(false);
13593 //============================================================
13596 //============================================================
13597 // Private Variables
13598 //------------------------------------------------------------
13600 var showTooltip = function(e, offsetElement) {
13601 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
13602 top = e.pos[1] + ( offsetElement.offsetTop || 0),
13603 x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
13604 y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
13605 content = tooltip(e.series.key, x, y, e, chart);
13607 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
13610 //============================================================
13613 function chart(selection) {
13614 selection.each(function(data) {
13615 var container = d3.select(this),
13618 var availableWidth = (width || parseInt(container.style('width')) || 960)
13619 - margin.left - margin.right,
13620 availableHeight = (height || parseInt(container.style('height')) || 400)
13621 - margin.top - margin.bottom;
13623 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
13624 chart.container = this;
13626 //set state.disabled
13627 state.disabled = data.map(function(d) { return !!d.disabled });
13629 if (!defaultState) {
13632 for (key in state) {
13633 if (state[key] instanceof Array)
13634 defaultState[key] = state[key].slice(0);
13636 defaultState[key] = state[key];
13640 //------------------------------------------------------------
13641 // Display No Data message if there's nothing to show.
13643 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
13644 var noDataText = container.selectAll('.nv-noData').data([noData]);
13646 noDataText.enter().append('text')
13647 .attr('class', 'nvd3 nv-noData')
13648 .attr('dy', '-.7em')
13649 .style('text-anchor', 'middle');
13652 .attr('x', margin.left + availableWidth / 2)
13653 .attr('y', margin.top + availableHeight / 2)
13654 .text(function(d) { return d });
13658 container.selectAll('.nv-noData').remove();
13661 //------------------------------------------------------------
13664 //------------------------------------------------------------
13667 x = stacked.xScale();
13668 y = stacked.yScale();
13670 //------------------------------------------------------------
13673 //------------------------------------------------------------
13674 // Setup containers and skeleton of chart
13676 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
13677 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
13678 var g = wrap.select('g');
13680 gEnter.append("rect").style("opacity",0);
13681 gEnter.append('g').attr('class', 'nv-x nv-axis');
13682 gEnter.append('g').attr('class', 'nv-y nv-axis');
13683 gEnter.append('g').attr('class', 'nv-stackedWrap');
13684 gEnter.append('g').attr('class', 'nv-legendWrap');
13685 gEnter.append('g').attr('class', 'nv-controlsWrap');
13686 gEnter.append('g').attr('class', 'nv-interactive');
13688 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
13689 //------------------------------------------------------------
13693 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
13695 .width(legendWidth);
13697 g.select('.nv-legendWrap')
13701 if ( margin.top != legend.height()) {
13702 margin.top = legend.height();
13703 availableHeight = (height || parseInt(container.style('height')) || 400)
13704 - margin.top - margin.bottom;
13707 g.select('.nv-legendWrap')
13708 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
13711 //------------------------------------------------------------
13714 //------------------------------------------------------------
13717 if (showControls) {
13718 var controlsData = [
13719 { key: 'Stacked', disabled: stacked.offset() != 'zero' },
13720 { key: 'Stream', disabled: stacked.offset() != 'wiggle' },
13721 { key: 'Expanded', disabled: stacked.offset() != 'expand' }
13724 controlWidth = (cData.length/3) * 260;
13726 controlsData = controlsData.filter(function(d) {
13727 return cData.indexOf(d.key) > -1;
13731 .width( controlWidth )
13732 .color(['#444', '#444', '#444']);
13734 g.select('.nv-controlsWrap')
13735 .datum(controlsData)
13739 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
13740 margin.top = Math.max(controls.height(), legend.height());
13741 availableHeight = (height || parseInt(container.style('height')) || 400)
13742 - margin.top - margin.bottom;
13746 g.select('.nv-controlsWrap')
13747 .attr('transform', 'translate(0,' + (-margin.top) +')');
13750 //------------------------------------------------------------
13753 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13755 if (rightAlignYAxis) {
13756 g.select(".nv-y.nv-axis")
13757 .attr("transform", "translate(" + availableWidth + ",0)");
13760 //------------------------------------------------------------
13761 // Main Chart Component(s)
13763 //------------------------------------------------------------
13764 //Set up interactive layer
13765 if (useInteractiveGuideline) {
13767 .width(availableWidth)
13768 .height(availableHeight)
13769 .margin({left: margin.left, top: margin.top})
13770 .svgContainer(container)
13772 wrap.select(".nv-interactive").call(interactiveLayer);
13776 .width(availableWidth)
13777 .height(availableHeight)
13779 var stackedWrap = g.select('.nv-stackedWrap')
13782 stackedWrap.transition().call(stacked);
13784 //------------------------------------------------------------
13787 //------------------------------------------------------------
13793 .ticks( availableWidth / 100 )
13794 .tickSize( -availableHeight, 0);
13796 g.select('.nv-x.nv-axis')
13797 .attr('transform', 'translate(0,' + availableHeight + ')');
13799 g.select('.nv-x.nv-axis')
13800 .transition().duration(0)
13807 .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
13808 .tickSize(-availableWidth, 0)
13809 .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
13811 g.select('.nv-y.nv-axis')
13812 .transition().duration(0)
13816 //------------------------------------------------------------
13819 //============================================================
13820 // Event Handling/Dispatching (in chart's scope)
13821 //------------------------------------------------------------
13823 stacked.dispatch.on('areaClick.toggle', function(e) {
13824 if (data.filter(function(d) { return !d.disabled }).length === 1)
13825 data = data.map(function(d) {
13826 d.disabled = false;
13830 data = data.map(function(d,i) {
13831 d.disabled = (i != e.seriesIndex);
13835 state.disabled = data.map(function(d) { return !!d.disabled });
13836 dispatch.stateChange(state);
13841 legend.dispatch.on('stateChange', function(newState) {
13842 state.disabled = newState.disabled;
13843 dispatch.stateChange(state);
13847 controls.dispatch.on('legendClick', function(d,i) {
13848 if (!d.disabled) return;
13850 controlsData = controlsData.map(function(s) {
13854 d.disabled = false;
13858 stacked.style('stack');
13861 stacked.style('stream');
13864 stacked.style('expand');
13868 state.style = stacked.style();
13869 dispatch.stateChange(state);
13875 interactiveLayer.dispatch.on('elementMousemove', function(e) {
13876 stacked.clearHighlights();
13877 var singlePoint, pointIndex, pointXLocation, allData = [];
13879 .filter(function(series, i) {
13880 series.seriesIndex = i;
13881 return !series.disabled;
13883 .forEach(function(series,i) {
13884 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
13885 stacked.highlightPoint(i, pointIndex, true);
13886 var point = series.values[pointIndex];
13887 if (typeof point === 'undefined') return;
13888 if (typeof singlePoint === 'undefined') singlePoint = point;
13889 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
13892 value: chart.y()(point, pointIndex),
13893 color: color(series,series.seriesIndex)
13897 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
13898 interactiveLayer.tooltip
13899 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
13900 .chartContainer(that.parentNode)
13902 .valueFormatter(function(d,i) {
13903 return yAxis.tickFormat()(d);
13912 interactiveLayer.renderGuideLine(pointXLocation);
13916 interactiveLayer.dispatch.on("elementMouseout",function(e) {
13917 dispatch.tooltipHide();
13918 stacked.clearHighlights();
13922 dispatch.on('tooltipShow', function(e) {
13923 if (tooltips) showTooltip(e, that.parentNode);
13926 // Update chart from a state object passed to event handler
13927 dispatch.on('changeState', function(e) {
13929 if (typeof e.disabled !== 'undefined') {
13930 data.forEach(function(series,i) {
13931 series.disabled = e.disabled[i];
13934 state.disabled = e.disabled;
13937 if (typeof e.style !== 'undefined') {
13938 stacked.style(e.style);
13951 //============================================================
13952 // Event Handling/Dispatching (out of chart's scope)
13953 //------------------------------------------------------------
13955 stacked.dispatch.on('tooltipShow', function(e) {
13956 //disable tooltips when value ~= 0
13957 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
13959 if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
13960 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
13965 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13966 dispatch.tooltipShow(e);
13969 stacked.dispatch.on('tooltipHide', function(e) {
13970 dispatch.tooltipHide(e);
13973 dispatch.on('tooltipHide', function() {
13974 if (tooltips) nv.tooltip.cleanup();
13977 //============================================================
13980 //============================================================
13981 // Expose Public Variables
13982 //------------------------------------------------------------
13984 // expose chart's sub-components
13985 chart.dispatch = dispatch;
13986 chart.stacked = stacked;
13987 chart.legend = legend;
13988 chart.controls = controls;
13989 chart.xAxis = xAxis;
13990 chart.yAxis = yAxis;
13991 chart.interactiveLayer = interactiveLayer;
13993 d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
13995 chart.options = nv.utils.optionsFunc.bind(chart);
13997 chart.margin = function(_) {
13998 if (!arguments.length) return margin;
13999 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
14000 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
14001 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14002 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
14006 chart.width = function(_) {
14007 if (!arguments.length) return width;
14012 chart.height = function(_) {
14013 if (!arguments.length) return height;
14018 chart.color = function(_) {
14019 if (!arguments.length) return color;
14020 color = nv.utils.getColor(_);
14021 legend.color(color);
14022 stacked.color(color);
14026 chart.showControls = function(_) {
14027 if (!arguments.length) return showControls;
14032 chart.showLegend = function(_) {
14033 if (!arguments.length) return showLegend;
14038 chart.showXAxis = function(_) {
14039 if (!arguments.length) return showXAxis;
14044 chart.showYAxis = function(_) {
14045 if (!arguments.length) return showYAxis;
14050 chart.rightAlignYAxis = function(_) {
14051 if(!arguments.length) return rightAlignYAxis;
14052 rightAlignYAxis = _;
14053 yAxis.orient( (_) ? 'right' : 'left');
14057 chart.useInteractiveGuideline = function(_) {
14058 if(!arguments.length) return useInteractiveGuideline;
14059 useInteractiveGuideline = _;
14061 chart.interactive(false);
14062 chart.useVoronoi(false);
14067 chart.tooltip = function(_) {
14068 if (!arguments.length) return tooltip;
14073 chart.tooltips = function(_) {
14074 if (!arguments.length) return tooltips;
14079 chart.tooltipContent = function(_) {
14080 if (!arguments.length) return tooltip;
14085 chart.state = function(_) {
14086 if (!arguments.length) return state;
14091 chart.defaultState = function(_) {
14092 if (!arguments.length) return defaultState;
14097 chart.noData = function(_) {
14098 if (!arguments.length) return noData;
14103 chart.transitionDuration = function(_) {
14104 if (!arguments.length) return transitionDuration;
14105 transitionDuration = _;
14109 chart.controlsData = function(_) {
14110 if (!arguments.length) return cData;
14115 yAxis.setTickFormat = yAxis.tickFormat;
14117 yAxis.tickFormat = function(_) {
14118 if (!arguments.length) return yAxisTickFormat;
14119 yAxisTickFormat = _;
14124 //============================================================