1 /* nvd3 version 1.6.0(https://github.com/liquidpele/nvd3) 2014-11-23 */
4 // set up main nv object on window
5 var nv = window.nv || {};
8 // the major global objects under the nv namespace
9 nv.dev = true; //set false when in production
10 nv.tooltip = nv.tooltip || {}; // For the tooltip system
11 nv.utils = nv.utils || {}; // Utility subsystem
12 nv.models = nv.models || {}; //stores all the possible models/components
13 nv.charts = {}; //stores all the ready to use charts
14 nv.graphs = []; //stores all the graphs currently on the page
15 nv.logs = {}; //stores some statistics and potential error messages
17 nv.dispatch = d3.dispatch('render_start', 'render_end');
19 // Function bind polyfill
20 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
21 // https://github.com/ariya/phantomjs/issues/10522
22 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
23 // phantomJS is used for running the test suite
24 if (!Function.prototype.bind) {
25 Function.prototype.bind = function (oThis) {
26 if (typeof this !== "function") {
27 // closest thing possible to the ECMAScript 5 internal IsCallable function
28 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
31 var aArgs = Array.prototype.slice.call(arguments, 1),
33 fNOP = function () {},
34 fBound = function () {
35 return fToBind.apply(this instanceof fNOP && oThis
38 aArgs.concat(Array.prototype.slice.call(arguments)));
41 fNOP.prototype = this.prototype;
42 fBound.prototype = new fNOP();
47 // Development render timers - disabled if dev = false
49 nv.dispatch.on('render_start', function(e) {
50 nv.logs.startTime = +new Date();
53 nv.dispatch.on('render_end', function(e) {
54 nv.logs.endTime = +new Date();
55 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
56 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
60 // Logs all arguments, and returns the last so you can test things in place
61 // Note: in IE8 console.log is an object not a function, and if modernizr is used
62 // then calling Function.prototype.bind with with anything other than a function
63 // causes a TypeError to be thrown.
65 if (nv.dev && window.console && console.log && console.log.apply)
66 console.log.apply(console, arguments);
67 else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
68 var log = Function.prototype.bind.call(console.log, console);
69 log.apply(console, arguments);
71 return arguments[arguments.length - 1];
74 // print console warning, should be used by deprecated functions
75 nv.deprecated = function(name) {
76 if (nv.dev && console && console.warn) {
77 console.warn('`' + name + '` has been deprecated.');
81 // render function is used to queue up chart rendering
82 // in non-blocking timeout functions
83 nv.render = function render(step) {
84 // number of graphs to generate in each timeout loop
87 nv.render.active = true;
88 nv.dispatch.render_start();
90 setTimeout(function() {
93 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
94 chart = graph.generate();
95 if (typeof graph.callback == typeof(Function)) graph.callback(chart);
96 nv.graphs.push(chart);
99 nv.render.queue.splice(0, i);
101 if (nv.render.queue.length) setTimeout(arguments.callee, 0);
103 nv.dispatch.render_end();
104 nv.render.active = false;
109 nv.render.active = false;
110 nv.render.queue = [];
112 // main function to use when adding a new graph, see examples
113 nv.addGraph = function(obj) {
114 if (typeof arguments[0] === typeof(Function)) {
115 obj = {generate: arguments[0], callback: arguments[1]};
118 nv.render.queue.push(obj);
120 if (!nv.render.active) {
123 };/* Utility class to handle creation of an interactive layer.
124 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
125 containing the X-coordinate. It can also render a vertical line where the mouse is located.
127 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
128 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
129 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
131 nv.interactiveGuideline = function() {
134 var tooltip = nv.models.tooltip();
140 //Please pass in the bounding chart's top and left margins
141 //This is important for calculating the correct mouseX/Y positions.
142 var margin = {left: 0, top: 0}
143 , xScale = d3.scale.linear()
144 , yScale = d3.scale.linear()
145 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
146 , showGuideLine = true;
147 //Must pass in the bounding chart's <svg> container.
148 //The mousemove event is attached to this container.
149 var svgContainer = null;
151 // check if IE by looking for activeX
152 var isMSIE = "ActiveXObject" in window;
155 function layer(selection) {
156 selection.each(function(data) {
157 var container = d3.select(this);
158 var availableWidth = (width || 960), availableHeight = (height || 400);
159 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
161 var wrapEnter = wrap.enter()
162 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
163 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
169 function mouseHandler() {
170 var d3mouse = d3.mouse(this);
171 var mouseX = d3mouse[0];
172 var mouseY = d3mouse[1];
173 var subtractMargin = true;
174 var mouseOutAnyReason = false;
177 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
178 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
179 over a rect in IE 10.
180 However, d3.event.offsetX/Y also returns the mouse coordinates
181 relative to the triggering <rect>. So we use offsetX/Y on IE.
183 mouseX = d3.event.offsetX;
184 mouseY = d3.event.offsetY;
187 On IE, if you attach a mouse event listener to the <svg> container,
188 it will actually trigger it for all the child elements (like <path>, <circle>, etc).
189 When this happens on IE, the offsetX/Y is set to where ever the child element
191 As a result, we do NOT need to subtract margins to figure out the mouse X/Y
192 position under this scenario. Removing the line below *will* cause
193 the interactive layer to not work right on IE.
195 if(d3.event.target.tagName !== "svg") {
196 subtractMargin = false;
199 if (d3.event.target.className.baseVal.match("nv-legend")) {
200 mouseOutAnyReason = true;
206 mouseX -= margin.left;
207 mouseY -= margin.top;
210 /* If mouseX/Y is outside of the chart's bounds,
211 trigger a mouseOut event.
213 if (mouseX < 0 || mouseY < 0
214 || mouseX > availableWidth || mouseY > availableHeight
215 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
220 if (d3.event.relatedTarget
221 && d3.event.relatedTarget.ownerSVGElement === undefined
222 && 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
242 //If user double clicks the layer, fire a elementDblclick
243 if (d3.event.type === "dblclick") {
244 dispatch.elementDblclick({
247 pointXValue: pointXValue
251 // if user single clicks the layer, fire elementClick
252 if (d3.event.type === 'click') {
253 dispatch.elementClick({
256 pointXValue: pointXValue
262 .on("mousemove",mouseHandler, true)
263 .on("mouseout" ,mouseHandler,true)
264 .on("dblclick" ,mouseHandler)
265 .on("click", mouseHandler)
268 //Draws a vertical guideline at the given X postion.
269 layer.renderGuideLine = function(x) {
270 if (!showGuideLine) return;
271 var line = wrap.select(".nv-interactiveGuideLine")
273 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
277 .attr("class", "nv-guideline")
278 .attr("x1", function(d) { return d;})
279 .attr("x2", function(d) { return d;})
280 .attr("y1", availableHeight)
283 line.exit().remove();
289 layer.dispatch = dispatch;
290 layer.tooltip = tooltip;
292 layer.margin = function(_) {
293 if (!arguments.length) return margin;
294 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
295 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
299 layer.width = function(_) {
300 if (!arguments.length) return width;
305 layer.height = function(_) {
306 if (!arguments.length) return height;
311 layer.xScale = function(_) {
312 if (!arguments.length) return xScale;
317 layer.showGuideLine = function(_) {
318 if (!arguments.length) return showGuideLine;
323 layer.svgContainer = function(_) {
324 if (!arguments.length) return svgContainer;
332 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
333 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
335 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
336 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
337 because 28 is closer to 30 than 10.
339 Unit tests can be found in: interactiveBisectTest.html
341 Has the following known issues:
342 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
343 * Won't work if there are duplicate x coordinate values.
345 nv.interactiveBisect = function (values, searchVal, xAccessor) {
347 if (! (values instanceof Array)) {
350 if (typeof xAccessor !== 'function') {
351 xAccessor = function(d,i) {
356 var bisect = d3.bisector(xAccessor).left;
357 var index = d3.max([0, bisect(values,searchVal) - 1]);
358 var currentValue = xAccessor(values[index], index);
360 if (typeof currentValue === 'undefined') {
361 currentValue = index;
364 if (currentValue === searchVal) {
365 return index; //found exact match
368 var nextIndex = d3.min([index+1, values.length - 1]);
369 var nextValue = xAccessor(values[nextIndex], nextIndex);
371 if (typeof nextValue === 'undefined') {
372 nextValue = nextIndex;
375 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
383 Returns the index in the array "values" that is closest to searchVal.
384 Only returns an index if searchVal is within some "threshold".
385 Otherwise, returns null.
387 nv.nearestValueIndex = function (values, searchVal, threshold) {
389 var yDistMax = Infinity, indexToHighlight = null;
390 values.forEach(function(d,i) {
391 var delta = Math.abs(searchVal - d);
392 if ( delta <= yDistMax && delta < threshold) {
394 indexToHighlight = i;
397 return indexToHighlight;
399 /* Tooltip rendering model for nvd3 charts.
400 window.nv.models.tooltip is the updated,new way to render tooltips.
402 window.nv.tooltip.show is the old tooltip code.
403 window.nv.tooltip.* also has various helper methods.
407 window.nv.tooltip = {};
409 /* Model which can be instantiated to handle tooltip rendering.
411 var tip = nv.models.tooltip().gravity('w').distance(23)
414 tip(); //just invoke the returned function to render tooltip.
416 window.nv.models.tooltip = function() {
417 //HTML contents of the tooltip. If null, the content is generated via the data variable.
421 Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
422 Example Format of data:
425 value: "August 2009",
427 {key: "Series 1", value: "Value 1", color: "#000"},
428 {key: "Series 2", value: "Value 2", color: "#00f"}
434 var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
435 ,distance = 50 //Distance to offset tooltip from the mouse location.
436 ,snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
437 , fixedTop = null //If not null, this fixes the top position of the tooltip.
438 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
439 , chartContainer = null //Parent DIV, of the SVG Container that holds the chart.
440 , tooltipElem = null //actual DOM element representing the tooltip.
441 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
442 , enabled = true; //True -> tooltips are rendered. False -> don't render tooltips.
444 //Generates a unique id when you create a new tooltip() object
445 var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
447 //CSS class to specify whether element should not have mouse events.
448 var nvPointerEventsClass = "nv-pointer-events-none";
450 //Format function for the tooltip values column
451 var valueFormatter = function(d,i) {
455 //Format function for the tooltip header value.
456 var headerFormatter = function(d) {
460 //By default, the tooltip model renders a beautiful table inside a DIV.
461 //You can override this function if a custom tooltip is desired.
462 var contentGenerator = function(d) {
463 if (content != null) {
471 var table = d3.select(document.createElement("table"));
472 var theadEnter = table.selectAll("thead")
474 .enter().append("thead");
476 theadEnter.append("tr")
480 .classed("x-value",true)
481 .html(headerFormatter(d.value));
483 var tbodyEnter = table.selectAll("tbody")
485 .enter().append("tbody");
487 var trowEnter = tbodyEnter.selectAll("tr")
488 .data(function(p) { return p.series})
491 .classed("highlight", function(p) { return p.highlight});
493 trowEnter.append("td")
494 .classed("legend-color-guide",true)
496 .style("background-color", function(p) { return p.color});
498 trowEnter.append("td")
500 .html(function(p) {return p.key});
502 trowEnter.append("td")
503 .classed("value",true)
504 .html(function(p,i) { return valueFormatter(p.value,i) });
507 trowEnter.selectAll("td").each(function(p) {
509 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
512 .style("border-bottom-color", opacityScale(opacity))
513 .style("border-top-color", opacityScale(opacity))
518 var html = table.node().outerHTML;
519 if (d.footer !== undefined)
520 html += "<div class='footer'>" + d.footer + "</div>";
525 var dataSeriesExists = function(d) {
526 if (d && d.series && d.series.length > 0) {
532 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
533 function convertViewBoxRatio() {
534 if (chartContainer) {
535 var svg = d3.select(chartContainer);
536 if (svg.node().tagName !== "svg") {
537 svg = svg.select("svg");
539 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
541 viewBox = viewBox.split(' ');
542 var ratio = parseInt(svg.style('width')) / viewBox[2];
544 position.left = position.left * ratio;
545 position.top = position.top * ratio;
550 //Creates new tooltip container, or uses existing one on DOM.
551 function getTooltipContainer(newContent) {
553 if (chartContainer) {
554 body = d3.select(chartContainer);
556 body = d3.select("body");
559 var container = body.select(".nvtooltip");
560 if (container.node() === null) {
561 //Create new tooltip div if it doesn't exist on DOM.
562 container = body.append("div")
563 .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
568 container.node().innerHTML = newContent;
569 container.style("top",0).style("left",0).style("opacity",0);
570 container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
571 container.classed(nvPointerEventsClass,true);
572 return container.node();
575 //Draw the tooltip onto the DOM.
576 function nvtooltip() {
577 if (!enabled) return;
578 if (!dataSeriesExists(data)) return;
580 convertViewBoxRatio();
582 var left = position.left;
583 var top = (fixedTop != null) ? fixedTop : position.top;
584 var container = getTooltipContainer(contentGenerator(data));
585 tooltipElem = container;
586 if (chartContainer) {
587 var svgComp = chartContainer.getElementsByTagName("svg")[0];
588 var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
589 var svgOffset = {left:0,top:0};
591 var svgBound = svgComp.getBoundingClientRect();
592 var chartBound = chartContainer.getBoundingClientRect();
593 var svgBoundTop = svgBound.top;
595 //Defensive code. Sometimes, svgBoundTop can be a really negative
596 // number, like -134254. That's a bug.
597 // If such a number is found, use zero instead. FireFox bug only
598 if (svgBoundTop < 0) {
599 var containerBound = chartContainer.getBoundingClientRect();
600 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
602 svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
603 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
605 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
606 //You need to also add any offset between the <svg> element and its containing <div>
607 //Finally, add any offset of the containing <div> on the whole page.
608 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
609 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
612 if (snapDistance && snapDistance > 0) {
613 top = Math.floor(top/snapDistance) * snapDistance;
616 nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
620 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
622 nvtooltip.content = function(_) {
623 if (!arguments.length) return content;
628 //Returns tooltipElem...not able to set it.
629 nvtooltip.tooltipElem = function() {
633 nvtooltip.contentGenerator = function(_) {
634 if (!arguments.length) return contentGenerator;
635 if (typeof _ === 'function') {
636 contentGenerator = _;
641 nvtooltip.data = function(_) {
642 if (!arguments.length) return data;
647 nvtooltip.gravity = function(_) {
648 if (!arguments.length) return gravity;
653 nvtooltip.distance = function(_) {
654 if (!arguments.length) return distance;
659 nvtooltip.snapDistance = function(_) {
660 if (!arguments.length) return snapDistance;
665 nvtooltip.classes = function(_) {
666 if (!arguments.length) return classes;
671 nvtooltip.chartContainer = function(_) {
672 if (!arguments.length) return chartContainer;
677 nvtooltip.position = function(_) {
678 if (!arguments.length) return position;
679 position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
680 position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
684 nvtooltip.fixedTop = function(_) {
685 if (!arguments.length) return fixedTop;
690 nvtooltip.enabled = function(_) {
691 if (!arguments.length) return enabled;
696 nvtooltip.valueFormatter = function(_) {
697 if (!arguments.length) return valueFormatter;
698 if (typeof _ === 'function') {
704 nvtooltip.headerFormatter = function(_) {
705 if (!arguments.length) return headerFormatter;
706 if (typeof _ === 'function') {
712 //id() is a read-only function. You can't use it to set the id.
713 nvtooltip.id = function() {
720 //Original tooltip.show function. Kept for backward compatibility.
722 nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
724 //Create new tooltip div if it doesn't exist on DOM.
725 var container = document.createElement('div');
726 container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
728 var body = parentContainer;
729 if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
730 //If the parent element is an SVG element, place tooltip in the <body> element.
731 body = document.getElementsByTagName('body')[0];
734 container.style.left = 0;
735 container.style.top = 0;
736 container.style.opacity = 0;
737 // Content can also be dom element
738 if (typeof content !== 'string')
739 container.appendChild(content);
741 container.innerHTML = content;
742 body.appendChild(container);
744 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
745 if (parentContainer) {
746 pos[0] = pos[0] - parentContainer.scrollLeft;
747 pos[1] = pos[1] - parentContainer.scrollTop;
749 nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
752 //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
753 nv.tooltip.findFirstNonSVGParent = function(Elem) {
754 while(Elem.tagName.match(/^g|svg$/i) !== null) {
755 Elem = Elem.parentNode;
760 //Finds the total offsetTop of a given DOM element.
761 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
762 nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
763 var offsetTop = initialTop;
766 if( !isNaN( Elem.offsetTop ) ) {
767 offsetTop += (Elem.offsetTop);
769 } while( Elem = Elem.offsetParent );
773 //Finds the total offsetLeft of a given DOM element.
774 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
775 nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
776 var offsetLeft = initialLeft;
779 if( !isNaN( Elem.offsetLeft ) ) {
780 offsetLeft += (Elem.offsetLeft);
782 } while( Elem = Elem.offsetParent );
786 //Global utility function to render a tooltip on the DOM.
787 //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
788 //gravity = how to orient the tooltip
789 //dist = how far away from the mouse to place tooltip
790 //container = tooltip DIV
791 nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
793 var height = parseInt(container.offsetHeight),
794 width = parseInt(container.offsetWidth),
795 windowWidth = nv.utils.windowSize().width,
796 windowHeight = nv.utils.windowSize().height,
797 scrollTop = window.pageYOffset,
798 scrollLeft = window.pageXOffset,
801 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
802 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
804 gravity = gravity || 's';
807 var tooltipTop = function ( Elem ) {
808 return nv.tooltip.findTotalOffsetTop(Elem, top);
811 var tooltipLeft = function ( Elem ) {
812 return nv.tooltip.findTotalOffsetLeft(Elem,left);
817 left = pos[0] - width - dist;
818 top = pos[1] - (height / 2);
819 var tLeft = tooltipLeft(container);
820 var tTop = tooltipTop(container);
821 if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
822 if (tTop < scrollTop) top = scrollTop - tTop + top;
823 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
826 left = pos[0] + dist;
827 top = pos[1] - (height / 2);
828 var tLeft = tooltipLeft(container);
829 var tTop = tooltipTop(container);
830 if (tLeft + width > windowWidth) left = pos[0] - width - dist;
831 if (tTop < scrollTop) top = scrollTop + 5;
832 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
835 left = pos[0] - (width / 2) - 5;
837 var tLeft = tooltipLeft(container);
838 var tTop = tooltipTop(container);
839 if (tLeft < scrollLeft) left = scrollLeft + 5;
840 if (tLeft + width > windowWidth) left = left - width/2 + 5;
841 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
844 left = pos[0] - (width / 2);
845 top = pos[1] - height - dist;
846 var tLeft = tooltipLeft(container);
847 var tTop = tooltipTop(container);
848 if (tLeft < scrollLeft) left = scrollLeft + 5;
849 if (tLeft + width > windowWidth) left = left - width/2 + 5;
850 if (scrollTop > tTop) top = scrollTop;
855 var tLeft = tooltipLeft(container);
856 var tTop = tooltipTop(container);
860 container.style.left = left+'px';
861 container.style.top = top+'px';
862 container.style.opacity = 1;
863 container.style.position = 'absolute';
868 //Global utility function to remove tooltips from the DOM.
869 nv.tooltip.cleanup = function() {
871 // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
872 var tooltips = document.getElementsByClassName('nvtooltip');
874 while(tooltips.length) {
875 purging.push(tooltips[0]);
876 tooltips[0].style.transitionDelay = '0 !important';
877 tooltips[0].style.opacity = 0;
878 tooltips[0].className = 'nvtooltip-pending-removal';
881 setTimeout(function() {
883 while (purging.length) {
884 var removeMe = purging.pop();
885 removeMe.parentNode.removeChild(removeMe);
894 Gets the browser window size
896 Returns object with height and width properties
898 nv.utils.windowSize = function() {
900 var size = {width: 640, height: 480};
902 // Earlier IE uses Doc.body
903 if (document.body && document.body.offsetWidth) {
904 size.width = document.body.offsetWidth;
905 size.height = document.body.offsetHeight;
908 // IE can use depending on mode it is in
909 if (document.compatMode=='CSS1Compat' &&
910 document.documentElement &&
911 document.documentElement.offsetWidth ) {
913 size.width = document.documentElement.offsetWidth;
914 size.height = document.documentElement.offsetHeight;
917 // Most recent browsers use
918 if (window.innerWidth && window.innerHeight) {
919 size.width = window.innerWidth;
920 size.height = window.innerHeight;
927 Binds callback function to run when window is resized
929 nv.utils.windowResize = function(handler) {
930 if (window.addEventListener) {
931 window.addEventListener('resize', handler);
933 nv.log("ERROR: Failed to bind to window.resize with: ", handler);
935 // return object with clear function to remove the single added callback.
939 window.removeEventListener('resize', handler);
946 Backwards compatible way to implement more d3-like coloring of graphs.
947 If passed an array, wrap it in a function which implements the old behavior
949 nv.utils.getColor = function(color) {
950 //if you pass in nothing or not an array, get default colors back
951 if (!arguments.length || !(color instanceof Array)) {
952 return nv.utils.defaultColor();
954 return function (d, i) {
955 return d.color || color[i % color.length];
962 Default color chooser uses the index of an object as before.
964 nv.utils.defaultColor = function() {
965 var colors = d3.scale.category20().range();
966 return function(d, i) {
967 return d.color || colors[i % colors.length]
973 Returns a color function that takes the result of 'getKey' for each series and
974 looks for a corresponding color from the dictionary
976 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
977 // use default series.key if getKey is undefined
978 getKey = getKey || function(series) { return series.key };
979 defaultColors = defaultColors || d3.scale.category20().range();
981 // start at end of default color list and walk back to index 0
982 var defIndex = defaultColors.length;
984 return function(series, index) {
985 var key = getKey(series);
986 if (typeof dictionary[key] === 'function') {
987 return dictionary[key]();
988 } else if (dictionary[key] !== undefined) {
989 return dictionary[key];
991 // no match in dictionary, use a default color
993 // used all the default colors, start over
994 defIndex = defaultColors.length;
996 defIndex = defIndex - 1;
997 return defaultColors[defIndex];
1004 From the PJAX example on d3js.org, while this is not really directly needed
1005 it's a very cool method for doing pjax, I may expand upon it a little bit,
1006 open to suggestions on anything that may be useful
1008 nv.utils.pjax = function(links, content) {
1010 var load = function(href) {
1011 d3.html(href, function(fragment) {
1012 var target = d3.select(content).node();
1013 target.parentNode.replaceChild(
1014 d3.select(fragment).select(content).node(),
1016 nv.utils.pjax(links, content);
1020 d3.selectAll(links).on("click", function() {
1021 history.pushState(this.href, this.textContent, this.href);
1023 d3.event.preventDefault();
1026 d3.select(window).on("popstate", function() {
1027 if (d3.event.state) {
1028 load(d3.event.state);
1035 For when we want to approximate the width in pixels for an SVG:text element.
1036 Most common instance is when the element is in a display:none; container.
1037 Forumla is : text.length * font-size * constant_factor
1039 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1040 if (typeof svgTextElem.style === 'function'
1041 && typeof svgTextElem.text === 'function') {
1043 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
1044 var textLength = svgTextElem.text().length;
1045 return textLength * fontSize * 0.5;
1052 Numbers that are undefined, null or NaN, convert them to zeros.
1054 nv.utils.NaNtoZero = function(n) {
1055 if (typeof n !== 'number'
1059 || n === -Infinity) {
1067 Add a way to watch for d3 transition ends to d3
1069 d3.selection.prototype.watchTransition = function(renderWatch){
1070 var args = [this].concat([].slice.call(arguments, 1));
1071 return renderWatch.transition.apply(renderWatch, args);
1076 Helper object to watch when d3 has rendered something
1078 nv.utils.renderWatch = function(dispatch, duration) {
1079 if (!(this instanceof nv.utils.renderWatch)) {
1080 return new nv.utils.renderWatch(dispatch, duration);
1083 var _duration = duration !== undefined ? duration : 250;
1084 var renderStack = [];
1087 this.models = function(models) {
1088 models = [].slice.call(arguments, 0);
1089 models.forEach(function(model){
1090 model.__rendered = false;
1092 m.dispatch.on('renderEnd', function(arg){
1093 m.__rendered = true;
1094 self.renderEnd('model');
1098 if (renderStack.indexOf(model) < 0) {
1099 renderStack.push(model);
1105 this.reset = function(duration) {
1106 if (duration !== undefined) {
1107 _duration = duration;
1112 this.transition = function(selection, args, duration) {
1113 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1115 if (args.length > 1) {
1116 duration = args.pop();
1118 duration = _duration !== undefined ? _duration : 250;
1120 selection.__rendered = false;
1122 if (renderStack.indexOf(selection) < 0) {
1123 renderStack.push(selection);
1126 if (duration === 0) {
1127 selection.__rendered = true;
1128 selection.delay = function() { return this; };
1129 selection.duration = function() { return this; };
1132 if (selection.length === 0) {
1133 selection.__rendered = true;
1134 } else if (selection.every( function(d){ return !d.length; } )) {
1135 selection.__rendered = true;
1137 selection.__rendered = false;
1144 .each(function(){ ++n; })
1145 .each('end', function(d, i) {
1147 selection.__rendered = true;
1148 self.renderEnd.apply(this, args);
1154 this.renderEnd = function() {
1155 if (renderStack.every( function(d){ return d.__rendered; } )) {
1156 renderStack.forEach( function(d){ d.__rendered = false; });
1157 dispatch.renderEnd.apply(this, arguments);
1165 Takes multiple objects and combines them into the first one (dst)
1166 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1167 gives: {a: 2, b: 3, c: 4}
1169 nv.utils.deepExtend = function(dst){
1170 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1171 sources.forEach(function(source) {
1172 for (key in source) {
1173 var isArray = dst[key] instanceof Array;
1174 var isObject = typeof dst[key] === 'object';
1175 var srcObj = typeof source[key] === 'object';
1177 if (isObject && !isArray && srcObj) {
1178 nv.utils.deepExtend(dst[key], source[key]);
1180 dst[key] = source[key];
1188 state utility object, used to track d3 states in the models
1190 nv.utils.state = function(){
1191 if (!(this instanceof nv.utils.state)) {
1192 return new nv.utils.state();
1196 var _setState = function(){};
1197 var _getState = function(){ return {}; };
1201 this.dispatch = d3.dispatch('change', 'set');
1203 this.dispatch.on('set', function(state){
1204 _setState(state, true);
1207 this.getter = function(fn){
1212 this.setter = function(fn, callback) {
1214 callback = function(){};
1216 _setState = function(state, update){
1225 this.init = function(state){
1227 nv.utils.deepExtend(init, state);
1230 var _set = function(){
1231 var settings = _getState();
1233 if (JSON.stringify(settings) === JSON.stringify(state)) {
1237 for (var key in settings) {
1238 if (state[key] === undefined) {
1241 state[key] = settings[key];
1247 this.update = function(){
1249 _setState(init, false);
1252 if (_set.call(this)) {
1253 this.dispatch.change(state);
1261 Snippet of code you can insert into each nv.models.* to give you the ability to
1268 To enable in the chart:
1269 chart.options = nv.utils.optionsFunc.bind(chart);
1271 nv.utils.optionsFunc = function(args) {
1272 nv.deprecated('nv.utils.optionsFunc');
1274 d3.map(args).forEach((function(key,value) {
1275 if (typeof this[key] === "function") {
1285 numTicks: requested number of ticks
1286 data: the chart data
1288 returns the number of ticks to actually use on X axis, based on chart data
1289 to avoid duplicate ticks with the same value
1291 nv.utils.calcTicksX = function(numTicks, data) {
1292 // find max number of values from all data streams
1295 for (i; i < data.length; i += 1) {
1296 var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1297 numValues = stream_len > numValues ? stream_len : numValues;
1299 nv.log("Requested number of ticks: ", numTicks);
1300 nv.log("Calculated max values to be: ", numValues);
1301 // make sure we don't have more ticks than values to avoid duplicates
1302 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1303 // make sure we have at least one tick
1304 numTicks = numTicks < 1 ? 1 : numTicks;
1305 // make sure it's an integer
1306 numTicks = Math.floor(numTicks);
1307 nv.log("Calculating tick count as: ", numTicks);
1313 returns number of ticks to actually use on Y axis, based on chart data
1315 nv.utils.calcTicksY = function(numTicks, data) {
1316 // currently uses the same logic but we can adjust here if needed later
1317 return nv.utils.calcTicksX(numTicks, data);
1322 Add a particular option from an options object onto chart
1323 Options exposed on a chart are a getter/setter function that returns chart
1324 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1326 option objects should be generated via Object.create() to provide
1327 the option of manipulating data via get/set functions.
1329 nv.utils.initOption = function(chart, name) {
1330 chart[name] = function (_) {
1331 if (!arguments.length) return chart._options[name];
1332 chart._options[name] = _;
1339 Add all options in an options object to the chart
1341 nv.utils.initOptions = function(chart) {
1342 var ops = Object.getOwnPropertyNames(chart._options);
1343 for (var i in ops) {
1344 nv.utils.initOption(chart, ops[i]);
1350 Inherit option getter/setter functions from source to target
1351 d3.rebind makes calling the function on target actually call it on source
1353 nv.utils.inheritOptions = function(target, source) {
1354 var args = Object.getOwnPropertyNames(source._options);
1355 args.unshift(source);
1356 args.unshift(target);
1357 d3.rebind.apply(this, args);
1362 Runs common initialize code on the svg before the chart builds
1364 nv.utils.initSVG = function(svg) {
1365 svg.classed({'nvd3-svg':true});
1366 };nv.models.axis = function() {
1368 //============================================================
1369 // Public Variables with Default Settings
1370 //------------------------------------------------------------
1372 var axis = d3.svg.axis();
1374 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1375 , width = 75 //only used for tickLabel currently
1376 , height = 60 //only used for tickLabel currently
1377 , scale = d3.scale.linear()
1378 , axisLabelText = null
1379 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1380 , highlightZero = true
1382 , rotateYLabel = true
1383 , staggerLabels = false
1386 , axisLabelDistance = 0
1388 , dispatch = d3.dispatch('renderEnd')
1389 , axisRendered = false
1390 , maxMinRendered = false
1395 .tickFormat(function(d) { return d })
1398 //============================================================
1401 //============================================================
1402 // Private Variables
1403 //------------------------------------------------------------
1406 , renderWatch = nv.utils.renderWatch(dispatch, duration)
1409 //============================================================
1411 function chart(selection) {
1412 renderWatch.reset();
1413 selection.each(function(data) {
1414 var container = d3.select(this);
1415 nv.utils.initSVG(container);
1417 //------------------------------------------------------------
1418 // Setup containers and skeleton of chart
1420 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1421 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1422 var gEnter = wrapEnter.append('g');
1423 var g = wrap.select('g')
1425 //------------------------------------------------------------
1430 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1431 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1434 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1435 g.watchTransition(renderWatch, 'axis').call(axis);
1437 scale0 = scale0 || axis.scale();
1439 var fmt = axis.tickFormat();
1441 fmt = scale0.tickFormat();
1444 var axisLabel = g.selectAll('text.nv-axislabel')
1445 .data([axisLabelText || null]);
1446 axisLabel.exit().remove();
1447 switch (axis.orient()) {
1449 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1451 if (scale.range().length < 2) {
1453 } else if (scale.range().length === 2) {
1454 w = scale.range()[1];
1456 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1459 .attr('text-anchor', 'middle')
1463 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1464 .data(scale.domain());
1465 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1466 axisMaxMin.exit().remove();
1468 .attr('transform', function(d,i) {
1469 return 'translate(' + scale(d) + ',0)'
1472 .attr('dy', '-0.5em')
1473 .attr('y', -axis.tickPadding())
1474 .attr('text-anchor', 'middle')
1475 .text(function(d,i) {
1477 return ('' + v).match('NaN') ? '' : v;
1479 axisMaxMin.watchTransition(renderWatch, 'min-max top')
1480 .attr('transform', function(d,i) {
1481 return 'translate(' + scale.range()[i] + ',0)'
1486 var xLabelMargin = axisLabelDistance + 36;
1487 var maxTextWidth = 30;
1488 var xTicks = g.selectAll('g').select("text");
1489 if (rotateLabels%360) {
1490 //Calculate the longest xTick width
1491 xTicks.each(function(d,i){
1492 var width = this.getBoundingClientRect().width;
1493 if(width > maxTextWidth) maxTextWidth = width;
1495 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1496 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1497 var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1500 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1501 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1503 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1505 if (scale.range().length < 2) {
1507 } else if (scale.range().length === 2) {
1508 w = scale.range()[1];
1510 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1513 .attr('text-anchor', 'middle')
1514 .attr('y', xLabelMargin)
1517 //if (showMaxMin && !isOrdinal) {
1518 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1519 //.data(scale.domain())
1520 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1521 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1522 axisMaxMin.exit().remove();
1524 .attr('transform', function(d,i) {
1525 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1528 .attr('dy', '.71em')
1529 .attr('y', axis.tickPadding())
1530 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1531 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1532 .text(function(d,i) {
1534 return ('' + v).match('NaN') ? '' : v;
1536 axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1537 .attr('transform', function(d,i) {
1538 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1543 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1547 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1549 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1550 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1551 .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
1552 .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1554 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1555 .data(scale.domain());
1556 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1557 .style('opacity', 0);
1558 axisMaxMin.exit().remove();
1560 .attr('transform', function(d,i) {
1561 return 'translate(0,' + scale(d) + ')'
1564 .attr('dy', '.32em')
1566 .attr('x', axis.tickPadding())
1567 .style('text-anchor', 'start')
1568 .text(function(d,i) {
1570 return ('' + v).match('NaN') ? '' : v;
1572 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1573 .attr('transform', function(d,i) {
1574 return 'translate(0,' + scale.range()[i] + ')'
1577 .style('opacity', 1);
1582 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1583 var yTicks = g.selectAll('g').select("text");
1584 yTicks.each(function(d,i){
1585 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1586 if(labelPadding > width) width = labelPadding;
1589 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1591 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1592 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1593 .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 25 - (axisLabelDistance || 0)) : -10)
1594 .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1596 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1597 .data(scale.domain());
1598 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1599 .style('opacity', 0);
1600 axisMaxMin.exit().remove();
1602 .attr('transform', function(d,i) {
1603 return 'translate(0,' + scale0(d) + ')'
1606 .attr('dy', '.32em')
1608 .attr('x', -axis.tickPadding())
1609 .attr('text-anchor', 'end')
1610 .text(function(d,i) {
1612 return ('' + v).match('NaN') ? '' : v;
1614 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1615 .attr('transform', function(d,i) {
1616 return 'translate(0,' + scale.range()[i] + ')'
1619 .style('opacity', 1);
1624 .text(function(d) { return d });
1627 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1628 //check if max and min overlap other values, if so, hide the values that overlap
1629 g.selectAll('g') // the g's wrapping each tick
1630 .each(function(d,i) {
1631 d3.select(this).select('text').attr('opacity', 1);
1632 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!
1633 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1634 d3.select(this).attr('opacity', 0);
1636 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1640 //if Max and Min = 0 only show min, Issue #281
1641 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1642 wrap.selectAll('g.nv-axisMaxMin')
1643 .style('opacity', function(d,i) { return !i ? 1 : 0 });
1647 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1648 var maxMinRange = [];
1649 wrap.selectAll('g.nv-axisMaxMin')
1650 .each(function(d,i) {
1652 if (i) // i== 1, max position
1653 maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
1654 else // i==0, min position
1655 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1657 if (i) // i== 1, max position
1658 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)
1659 else // i==0, min position
1660 maxMinRange.push(scale(d) + 4)
1663 g.selectAll('g') // the g's wrapping each tick
1664 .each(function(d,i) {
1665 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1666 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1667 d3.select(this).remove();
1669 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1675 //highlight zero line ... Maybe should not be an option and should just be in CSS?
1677 g.selectAll('.tick')
1678 .filter(function(d) { return !parseFloat(Math.round(this.__data__*100000)/1000000) && (this.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1679 .classed('zero', true);
1681 //store old scales for use in transitions on update
1682 scale0 = scale.copy();
1686 renderWatch.renderEnd('axis immediate');
1691 //============================================================
1692 // Expose Public Variables
1693 //------------------------------------------------------------
1695 // expose chart's sub-components
1697 chart.dispatch = dispatch;
1699 d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1700 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1702 chart.options = nv.utils.optionsFunc.bind(chart);
1704 chart.margin = function(_) {
1705 if(!arguments.length) return margin;
1706 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1707 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1708 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1709 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1713 chart.width = function(_) {
1714 if (!arguments.length) return width;
1719 chart.ticks = function(_) {
1720 if (!arguments.length) return ticks;
1725 chart.height = function(_) {
1726 if (!arguments.length) return height;
1731 chart.axisLabel = function(_) {
1732 if (!arguments.length) return axisLabelText;
1737 chart.showMaxMin = function(_) {
1738 if (!arguments.length) return showMaxMin;
1743 chart.highlightZero = function(_) {
1744 if (!arguments.length) return highlightZero;
1749 chart.scale = function(_) {
1750 if (!arguments.length) return scale;
1753 isOrdinal = typeof scale.rangeBands === 'function';
1754 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1758 chart.rotateYLabel = function(_) {
1759 if(!arguments.length) return rotateYLabel;
1764 chart.rotateLabels = function(_) {
1765 if(!arguments.length) return rotateLabels;
1770 chart.staggerLabels = function(_) {
1771 if (!arguments.length) return staggerLabels;
1776 chart.axisLabelDistance = function(_) {
1777 if (!arguments.length) return axisLabelDistance;
1778 axisLabelDistance = _;
1782 chart.duration = function(_) {
1783 if (!arguments.length) return duration;
1785 renderWatch.reset(duration);
1790 //============================================================
1796 // Chart design based on the recommendations of Stephen Few. Implementation
1797 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1798 // http://projects.instantcognition.com/protovis/bulletchart/
1800 nv.models.bullet = function() {
1802 //============================================================
1803 // Public Variables with Default Settings
1804 //------------------------------------------------------------
1806 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1807 , orient = 'left' // TODO top & bottom
1809 , ranges = function(d) { return d.ranges }
1810 , markers = function(d) { return d.markers ? d.markers : [0] }
1811 , measures = function(d) { return d.measures }
1812 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1813 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
1814 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
1815 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1819 , color = nv.utils.getColor(['#1f77b4'])
1820 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1823 //============================================================
1826 function chart(selection) {
1827 selection.each(function(d, i) {
1828 var availableWidth = width - margin.left - margin.right,
1829 availableHeight = height - margin.top - margin.bottom,
1830 container = d3.select(this);
1831 nv.utils.initSVG(container);
1833 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1834 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1835 measurez = measures.call(this, d, i).slice().sort(d3.descending),
1836 rangeLabelz = rangeLabels.call(this, d, i).slice(),
1837 markerLabelz = markerLabels.call(this, d, i).slice(),
1838 measureLabelz = measureLabels.call(this, d, i).slice();
1841 //------------------------------------------------------------
1844 // Compute the new x-scale.
1845 var x1 = d3.scale.linear()
1846 .domain( d3.extent(d3.merge([forceX, rangez])) )
1847 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1849 // Retrieve the old x-scale, if this is an update.
1850 var x0 = this.__chart__ || d3.scale.linear()
1851 .domain([0, Infinity])
1854 // Stash the new scale.
1855 this.__chart__ = x1;
1858 var rangeMin = d3.min(rangez), //rangez[2]
1859 rangeMax = d3.max(rangez), //rangez[0]
1860 rangeAvg = rangez[1];
1862 //------------------------------------------------------------
1865 //------------------------------------------------------------
1866 // Setup containers and skeleton of chart
1868 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1869 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1870 var gEnter = wrapEnter.append('g');
1871 var g = wrap.select('g');
1873 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1874 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1875 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1876 gEnter.append('rect').attr('class', 'nv-measure');
1877 gEnter.append('path').attr('class', 'nv-markerTriangle');
1879 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1881 //------------------------------------------------------------
1885 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1886 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1887 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1888 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1891 g.select('rect.nv-rangeMax')
1892 .attr('height', availableHeight)
1893 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1894 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1895 .datum(rangeMax > 0 ? rangeMax : rangeMin)
1897 .attr('x', rangeMin < 0 ?
1904 g.select('rect.nv-rangeAvg')
1905 .attr('height', availableHeight)
1906 .attr('width', w1(rangeAvg))
1907 .attr('x', xp1(rangeAvg))
1910 .attr('width', rangeMax <= 0 ?
1911 x1(rangeMax) - x1(rangeAvg)
1912 : x1(rangeAvg) - x1(rangeMin))
1913 .attr('x', rangeMax <= 0 ?
1918 g.select('rect.nv-rangeMin')
1919 .attr('height', availableHeight)
1920 .attr('width', w1(rangeMax))
1921 .attr('x', xp1(rangeMax))
1922 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1923 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1924 .datum(rangeMax > 0 ? rangeMin : rangeMax)
1926 .attr('width', rangeMax <= 0 ?
1927 x1(rangeAvg) - x1(rangeMin)
1928 : x1(rangeMax) - x1(rangeAvg))
1929 .attr('x', rangeMax <= 0 ?
1934 g.select('rect.nv-measure')
1935 .style('fill', color)
1936 .attr('height', availableHeight / 3)
1937 .attr('y', availableHeight / 3)
1938 .attr('width', measurez < 0 ?
1939 x1(0) - x1(measurez[0])
1940 : x1(measurez[0]) - x1(0))
1941 .attr('x', xp1(measurez))
1942 .on('mouseover', function() {
1943 dispatch.elementMouseover({
1945 label: measureLabelz[0] || 'Current',
1946 pos: [x1(measurez[0]), availableHeight/2]
1949 .on('mouseout', function() {
1950 dispatch.elementMouseout({
1952 label: measureLabelz[0] || 'Current'
1956 var h3 = availableHeight / 6;
1958 g.selectAll('path.nv-markerTriangle')
1959 .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1960 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1961 .on('mouseover', function() {
1962 dispatch.elementMouseover({
1964 label: markerLabelz[0] || 'Previous',
1965 pos: [x1(markerz[0]), availableHeight/2]
1968 .on('mouseout', function() {
1969 dispatch.elementMouseout({
1971 label: markerLabelz[0] || 'Previous'
1975 g.selectAll('path.nv-markerTriangle').remove();
1979 wrap.selectAll('.nv-range')
1980 .on('mouseover', function(d,i) {
1981 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1983 dispatch.elementMouseover({
1986 pos: [x1(d), availableHeight/2]
1989 .on('mouseout', function(d,i) {
1990 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1992 dispatch.elementMouseout({
1999 // d3.timer.flush(); // Not needed?
2005 //============================================================
2006 // Expose Public Variables
2007 //------------------------------------------------------------
2009 chart.dispatch = dispatch;
2011 chart.options = nv.utils.optionsFunc.bind(chart);
2013 // left, right, top, bottom
2014 chart.orient = function(_) {
2015 if (!arguments.length) return orient;
2017 reverse = orient == 'right' || orient == 'bottom';
2021 // ranges (bad, satisfactory, good)
2022 chart.ranges = function(_) {
2023 if (!arguments.length) return ranges;
2028 // markers (previous, goal)
2029 chart.markers = function(_) {
2030 if (!arguments.length) return markers;
2035 // measures (actual, forecast)
2036 chart.measures = function(_) {
2037 if (!arguments.length) return measures;
2042 chart.forceX = function(_) {
2043 if (!arguments.length) return forceX;
2048 chart.width = function(_) {
2049 if (!arguments.length) return width;
2054 chart.height = function(_) {
2055 if (!arguments.length) return height;
2060 chart.margin = function(_) {
2061 if (!arguments.length) return margin;
2062 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2063 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2064 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2065 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2069 chart.tickFormat = function(_) {
2070 if (!arguments.length) return tickFormat;
2075 chart.color = function(_) {
2076 if (!arguments.length) return color;
2077 color = nv.utils.getColor(_);
2081 //============================================================
2089 // Chart design based on the recommendations of Stephen Few. Implementation
2090 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2091 // http://projects.instantcognition.com/protovis/bulletchart/
2092 nv.models.bulletChart = function() {
2094 //============================================================
2095 // Public Variables with Default Settings
2096 //------------------------------------------------------------
2098 var bullet = nv.models.bullet()
2101 var orient = 'left' // TODO top & bottom
2103 , margin = {top: 5, right: 40, bottom: 20, left: 120}
2104 , ranges = function(d) { return d.ranges }
2105 , markers = function(d) { return d.markers ? d.markers : [0] }
2106 , measures = function(d) { return d.measures }
2111 , tooltip = function(key, x, y, e, graph) {
2112 return '<h3>' + x + '</h3>' +
2115 , noData = 'No Data Available.'
2116 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2119 //============================================================
2122 //============================================================
2123 // Private Variables
2124 //------------------------------------------------------------
2126 var showTooltip = function(e, offsetElement) {
2127 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
2128 top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
2129 content = tooltip(e.key, e.label, e.value, e, chart);
2131 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
2134 //============================================================
2137 function chart(selection) {
2138 selection.each(function(d, i) {
2139 var container = d3.select(this);
2140 nv.utils.initSVG(container);
2142 var availableWidth = (width || parseInt(container.style('width')) || 960)
2143 - margin.left - margin.right,
2144 availableHeight = height - margin.top - margin.bottom,
2148 chart.update = function() { chart(selection) };
2149 chart.container = this;
2151 //------------------------------------------------------------
2152 // Display No Data message if there's nothing to show.
2153 if (!d || !ranges.call(this, d, i)) {
2154 var noDataText = container.selectAll('.nv-noData').data([noData]);
2156 noDataText.enter().append('text')
2157 .attr('class', 'nvd3 nv-noData')
2158 .attr('dy', '-.7em')
2159 .style('text-anchor', 'middle');
2162 .attr('x', margin.left + availableWidth / 2)
2163 .attr('y', 18 + margin.top + availableHeight / 2)
2164 .text(function(d) { return d });
2168 container.selectAll('.nv-noData').remove();
2171 //------------------------------------------------------------
2175 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2176 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2177 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2180 //------------------------------------------------------------
2181 // Setup containers and skeleton of chart
2183 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2184 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2185 var gEnter = wrapEnter.append('g');
2186 var g = wrap.select('g');
2188 gEnter.append('g').attr('class', 'nv-bulletWrap');
2189 gEnter.append('g').attr('class', 'nv-titles');
2191 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2193 //------------------------------------------------------------
2196 // Compute the new x-scale.
2197 var x1 = d3.scale.linear()
2198 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
2199 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2201 // Retrieve the old x-scale, if this is an update.
2202 var x0 = this.__chart__ || d3.scale.linear()
2203 .domain([0, Infinity])
2206 // Stash the new scale.
2207 this.__chart__ = x1;
2210 // Derive width-scales from the x-scales.
2211 var w0 = bulletWidth(x0),
2212 w1 = bulletWidth(x1);
2214 function bulletWidth(x) {
2216 return function(d) {
2217 return Math.abs(x(d) - x(0));
2221 function bulletTranslate(x) {
2222 return function(d) {
2223 return 'translate(' + x(d) + ',0)';
2228 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2229 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2232 var title = gEnter.select('.nv-titles').append('g')
2233 .attr('text-anchor', 'end')
2234 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2235 title.append('text')
2236 .attr('class', 'nv-title')
2237 .text(function(d) { return d.title; });
2239 title.append('text')
2240 .attr('class', 'nv-subtitle')
2242 .text(function(d) { return d.subtitle; });
2247 .width(availableWidth)
2248 .height(availableHeight)
2250 var bulletWrap = g.select('.nv-bulletWrap');
2252 d3.transition(bulletWrap).call(bullet);
2256 // Compute the tick format.
2257 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2259 // Update the tick groups.
2260 var tick = g.selectAll('g.nv-tick')
2261 .data(x1.ticks( availableWidth / 50 ), function(d) {
2262 return this.textContent || format(d);
2265 // Initialize the ticks with the old scale, x0.
2266 var tickEnter = tick.enter().append('g')
2267 .attr('class', 'nv-tick')
2268 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2269 .style('opacity', 1e-6);
2271 tickEnter.append('line')
2272 .attr('y1', availableHeight)
2273 .attr('y2', availableHeight * 7 / 6);
2275 tickEnter.append('text')
2276 .attr('text-anchor', 'middle')
2278 .attr('y', availableHeight * 7 / 6)
2282 // Transition the updating ticks to the new scale, x1.
2283 var tickUpdate = d3.transition(tick)
2284 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2285 .style('opacity', 1);
2287 tickUpdate.select('line')
2288 .attr('y1', availableHeight)
2289 .attr('y2', availableHeight * 7 / 6);
2291 tickUpdate.select('text')
2292 .attr('y', availableHeight * 7 / 6);
2294 // Transition the exiting ticks to the new scale, x1.
2295 d3.transition(tick.exit())
2296 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2297 .style('opacity', 1e-6)
2301 //============================================================
2302 // Event Handling/Dispatching (in chart's scope)
2303 //------------------------------------------------------------
2305 dispatch.on('tooltipShow', function(e) {
2307 if (tooltips) showTooltip(e, that.parentNode);
2310 //============================================================
2320 //============================================================
2321 // Event Handling/Dispatching (out of chart's scope)
2322 //------------------------------------------------------------
2324 bullet.dispatch.on('elementMouseover.tooltip', function(e) {
2325 dispatch.tooltipShow(e);
2328 bullet.dispatch.on('elementMouseout.tooltip', function(e) {
2329 dispatch.tooltipHide(e);
2332 dispatch.on('tooltipHide', function() {
2333 if (tooltips) nv.tooltip.cleanup();
2336 //============================================================
2339 //============================================================
2340 // Expose Public Variables
2341 //------------------------------------------------------------
2343 chart.dispatch = dispatch;
2344 chart.bullet = bullet;
2346 d3.rebind(chart, bullet, 'color');
2348 chart.options = nv.utils.optionsFunc.bind(chart);
2350 // left, right, top, bottom
2351 chart.orient = function(x) {
2352 if (!arguments.length) return orient;
2354 reverse = orient == 'right' || orient == 'bottom';
2358 // ranges (bad, satisfactory, good)
2359 chart.ranges = function(x) {
2360 if (!arguments.length) return ranges;
2365 // markers (previous, goal)
2366 chart.markers = function(x) {
2367 if (!arguments.length) return markers;
2372 // measures (actual, forecast)
2373 chart.measures = function(x) {
2374 if (!arguments.length) return measures;
2379 chart.width = function(x) {
2380 if (!arguments.length) return width;
2385 chart.height = function(x) {
2386 if (!arguments.length) return height;
2391 chart.margin = function(_) {
2392 if (!arguments.length) return margin;
2393 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2394 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2395 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2396 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2400 chart.tickFormat = function(x) {
2401 if (!arguments.length) return tickFormat;
2406 chart.tooltips = function(_) {
2407 if (!arguments.length) return tooltips;
2412 chart.tooltipContent = function(_) {
2413 if (!arguments.length) return tooltip;
2418 chart.noData = function(_) {
2419 if (!arguments.length) return noData;
2424 //============================================================
2432 nv.models.cumulativeLineChart = function() {
2434 //============================================================
2435 // Public Variables with Default Settings
2436 //------------------------------------------------------------
2438 var lines = nv.models.line()
2439 , xAxis = nv.models.axis()
2440 , yAxis = nv.models.axis()
2441 , legend = nv.models.legend()
2442 , controls = nv.models.legend()
2443 , interactiveLayer = nv.interactiveGuideline()
2446 var margin = {top: 30, right: 30, bottom: 50, left: 60}
2447 , color = nv.utils.defaultColor()
2453 , rightAlignYAxis = false
2455 , showControls = true
2456 , useInteractiveGuideline = false
2458 , tooltip = function(key, x, y, e, graph) {
2459 return '<h3>' + key + '</h3>' +
2460 '<p>' + y + ' at ' + x + '</p>'
2462 , x //can be accessed via chart.xScale()
2463 , y //can be accessed via chart.yScale()
2465 , state = nv.utils.state()
2466 , defaultState = null
2467 , noData = 'No Data Available.'
2468 , average = function(d) { return d.average }
2469 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
2470 , transitionDuration = 250
2472 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
2476 state.rescaleY = rescaleY;
2483 .orient((rightAlignYAxis) ? 'right' : 'left')
2486 //============================================================
2487 controls.updateState(false);
2489 //============================================================
2490 // Private Variables
2491 //------------------------------------------------------------
2493 var dx = d3.scale.linear()
2494 , index = {i: 0, x: 0}
2495 , renderWatch = nv.utils.renderWatch(dispatch, duration)
2498 var showTooltip = function(e, offsetElement) {
2499 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2500 top = e.pos[1] + ( offsetElement.offsetTop || 0),
2501 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2502 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2503 content = tooltip(e.series.key, x, y, e, chart);
2505 nv.tooltip.show([left, top], content, null, null, offsetElement);
2508 var stateGetter = function(data) {
2511 active: data.map(function(d) { return !d.disabled }),
2518 var stateSetter = function(data) {
2519 return function(state) {
2520 if (state.index !== undefined)
2521 index.i = state.index;
2522 if (state.rescaleY !== undefined)
2523 rescaleY = state.rescaleY;
2524 if (state.active !== undefined)
2525 data.forEach(function(series,i) {
2526 series.disabled = !state.active[i];
2531 //============================================================
2533 function chart(selection) {
2534 renderWatch.reset();
2535 renderWatch.models(lines);
2536 if (showXAxis) renderWatch.models(xAxis);
2537 if (showYAxis) renderWatch.models(yAxis);
2538 selection.each(function(data) {
2539 var container = d3.select(this);
2540 nv.utils.initSVG(container);
2541 container.classed('nv-chart-' + id, true);
2544 var availableWidth = (width || parseInt(container.style('width')) || 960)
2545 - margin.left - margin.right,
2546 availableHeight = (height || parseInt(container.style('height')) || 400)
2547 - margin.top - margin.bottom;
2550 chart.update = function() {
2552 container.call(chart);
2554 container.transition().duration(duration).call(chart)
2556 chart.container = this;
2559 .setter(stateSetter(data), chart.update)
2560 .getter(stateGetter(data))
2563 // DEPRECATED set state.disableddisabled
2564 state.disabled = data.map(function(d) { return !!d.disabled });
2566 if (!defaultState) {
2569 for (key in state) {
2570 if (state[key] instanceof Array)
2571 defaultState[key] = state[key].slice(0);
2573 defaultState[key] = state[key];
2577 var indexDrag = d3.behavior.drag()
2578 .on('dragstart', dragStart)
2579 .on('drag', dragMove)
2580 .on('dragend', dragEnd);
2583 function dragStart(d,i) {
2584 d3.select(chart.container)
2585 .style('cursor', 'ew-resize');
2588 function dragMove(d,i) {
2589 index.x = d3.event.x;
2590 index.i = Math.round(dx.invert(index.x));
2594 function dragEnd(d,i) {
2595 d3.select(chart.container)
2596 .style('cursor', 'auto');
2598 // update state and send stateChange with new index
2599 state.index = index.i;
2600 dispatch.stateChange(state);
2603 //------------------------------------------------------------
2604 // Display No Data message if there's nothing to show.
2606 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2607 var noDataText = container.selectAll('.nv-noData').data([noData]);
2609 noDataText.enter().append('text')
2610 .attr('class', 'nvd3 nv-noData')
2611 .attr('dy', '-.7em')
2612 .style('text-anchor', 'middle');
2615 .attr('x', margin.left + availableWidth / 2)
2616 .attr('y', margin.top + availableHeight / 2)
2617 .text(function(d) { return d });
2621 container.selectAll('.nv-noData').remove();
2624 //------------------------------------------------------------
2627 //------------------------------------------------------------
2635 var seriesDomains = data
2636 .filter(function(series) { return !series.disabled })
2637 .map(function(series,i) {
2638 var initialDomain = d3.extent(series.values, lines.y());
2640 //account for series being disabled when losing 95% or more
2641 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2644 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2645 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2649 var completeDomain = [
2650 d3.min(seriesDomains, function(d) { return d[0] }),
2651 d3.max(seriesDomains, function(d) { return d[1] })
2654 lines.yDomain(completeDomain);
2656 lines.yDomain(null);
2660 dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2661 .range([0, availableWidth])
2664 //------------------------------------------------------------
2667 var data = indexify(index.i, data);
2670 //------------------------------------------------------------
2671 // Setup containers and skeleton of chart
2672 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2673 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2674 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2675 var g = wrap.select('g');
2677 gEnter.append('g').attr('class', 'nv-interactive');
2678 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2679 gEnter.append('g').attr('class', 'nv-y nv-axis');
2680 gEnter.append('g').attr('class', 'nv-background');
2681 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2682 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2683 gEnter.append('g').attr('class', 'nv-legendWrap');
2684 gEnter.append('g').attr('class', 'nv-controlsWrap');
2687 //------------------------------------------------------------
2691 legend.width(availableWidth);
2693 g.select('.nv-legendWrap')
2697 if ( margin.top != legend.height()) {
2698 margin.top = legend.height();
2699 availableHeight = (height || parseInt(container.style('height')) || 400)
2700 - margin.top - margin.bottom;
2703 g.select('.nv-legendWrap')
2704 .attr('transform', 'translate(0,' + (-margin.top) +')')
2707 //------------------------------------------------------------
2710 //------------------------------------------------------------
2714 var controlsData = [
2715 { key: 'Re-scale y-axis', disabled: !rescaleY }
2720 .color(['#444', '#444', '#444'])
2722 .margin({top: 5, right: 0, bottom: 5, left: 20})
2725 g.select('.nv-controlsWrap')
2726 .datum(controlsData)
2727 .attr('transform', 'translate(0,' + (-margin.top) +')')
2731 //------------------------------------------------------------
2734 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2736 if (rightAlignYAxis) {
2737 g.select(".nv-y.nv-axis")
2738 .attr("transform", "translate(" + availableWidth + ",0)");
2741 // Show error if series goes below 100%
2742 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2744 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2745 if (tempDisabled.length) {
2746 wrap.append('text').attr('class', 'tempDisabled')
2747 .attr('x', availableWidth / 2)
2748 .attr('y', '-.71em')
2749 .style('text-anchor', 'end')
2750 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2753 //------------------------------------------------------------
2754 // Main Chart Component(s)
2756 //------------------------------------------------------------
2757 //Set up interactive layer
2758 if (useInteractiveGuideline) {
2760 .width(availableWidth)
2761 .height(availableHeight)
2762 .margin({left:margin.left,top:margin.top})
2763 .svgContainer(container)
2765 wrap.select(".nv-interactive").call(interactiveLayer);
2768 gEnter.select('.nv-background')
2771 g.select('.nv-background rect')
2772 .attr('width', availableWidth)
2773 .attr('height', availableHeight);
2776 //.x(function(d) { return d.x })
2777 .y(function(d) { return d.display.y })
2778 .width(availableWidth)
2779 .height(availableHeight)
2780 .color(data.map(function(d,i) {
2781 return d.color || color(d, i);
2782 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2786 var linesWrap = g.select('.nv-linesWrap')
2787 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
2789 //d3.transition(linesWrap).call(lines);
2790 linesWrap.call(lines);
2792 /*Handle average lines [AN-612] ----------------------------*/
2794 //Store a series index number in the data array.
2795 data.forEach(function(d,i) {
2799 var avgLineData = data.filter(function(d) {
2800 return !d.disabled && !!average(d);
2803 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2804 .data(avgLineData, function(d) { return d.key; });
2806 var getAvgLineY = function(d) {
2807 //If average lines go off the svg element, clamp them to the svg bounds.
2808 var yVal = y(average(d));
2809 if (yVal < 0) return 0;
2810 if (yVal > availableHeight) return availableHeight;
2816 .style('stroke-width',2)
2817 .style('stroke-dasharray','10,10')
2818 .style('stroke',function (d,i) {
2819 return lines.color()(d,d.seriesIndex);
2822 .attr('x2',availableWidth)
2823 .attr('y1', getAvgLineY)
2824 .attr('y2', getAvgLineY);
2827 .style('stroke-opacity',function(d){
2828 //If average lines go offscreen, make them transparent
2829 var yVal = y(average(d));
2830 if (yVal < 0 || yVal > availableHeight) return 0;
2834 .attr('x2',availableWidth)
2835 .attr('y1', getAvgLineY)
2836 .attr('y2', getAvgLineY);
2838 avgLines.exit().remove();
2840 //Create index line -----------------------------------------
2842 var indexLine = linesWrap.selectAll('.nv-indexLine')
2844 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2847 .attr('fill', 'red')
2848 .attr('fill-opacity', .5)
2849 .style("pointer-events","all")
2853 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2854 .attr('height', availableHeight);
2856 //------------------------------------------------------------
2859 //------------------------------------------------------------
2865 .ticks( nv.utils.calcTicksX(availableWidth/70, data) )
2866 .tickSize(-availableHeight, 0);
2868 g.select('.nv-x.nv-axis')
2869 .attr('transform', 'translate(0,' + y.range()[0] + ')');
2870 g.select('.nv-x.nv-axis')
2878 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
2879 .tickSize( -availableWidth, 0);
2881 g.select('.nv-y.nv-axis')
2884 //------------------------------------------------------------
2887 //============================================================
2888 // Event Handling/Dispatching (in chart's scope)
2889 //------------------------------------------------------------
2892 function updateZero() {
2896 //When dragging the index line, turn off line transitions.
2897 // Then turn them back on when done dragging.
2898 var oldDuration = chart.duration();
2901 chart.duration(oldDuration);
2904 g.select('.nv-background rect')
2905 .on('click', function() {
2906 index.x = d3.mouse(this)[0];
2907 index.i = Math.round(dx.invert(index.x));
2909 // update state and send stateChange with new index
2910 state.index = index.i;
2911 dispatch.stateChange(state);
2916 lines.dispatch.on('elementClick', function(e) {
2917 index.i = e.pointIndex;
2918 index.x = dx(index.i);
2920 // update state and send stateChange with new index
2921 state.index = index.i;
2922 dispatch.stateChange(state);
2927 controls.dispatch.on('legendClick', function(d,i) {
2928 d.disabled = !d.disabled;
2929 rescaleY = !d.disabled;
2931 state.rescaleY = rescaleY;
2932 dispatch.stateChange(state);
2937 legend.dispatch.on('stateChange', function(newState) {
2938 for (var key in newState)
2939 state[key] = newState[key];
2940 dispatch.stateChange(state);
2944 interactiveLayer.dispatch.on('elementMousemove', function(e) {
2945 lines.clearHighlights();
2946 var singlePoint, pointIndex, pointXLocation, allData = [];
2949 .filter(function(series, i) {
2950 series.seriesIndex = i;
2951 return !series.disabled;
2953 .forEach(function(series,i) {
2954 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2955 lines.highlightPoint(i, pointIndex, true);
2956 var point = series.values[pointIndex];
2957 if (typeof point === 'undefined') return;
2958 if (typeof singlePoint === 'undefined') singlePoint = point;
2959 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2962 value: chart.y()(point, pointIndex),
2963 color: color(series,series.seriesIndex)
2967 //Highlight the tooltip entry based on which point the mouse is closest to.
2968 if (allData.length > 2) {
2969 var yValue = chart.yScale().invert(e.mouseY);
2970 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
2971 var threshold = 0.03 * domainExtent;
2972 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
2973 if (indexToHighlight !== null)
2974 allData[indexToHighlight].highlight = true;
2977 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
2978 interactiveLayer.tooltip
2979 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2980 .chartContainer(that.parentNode)
2982 .valueFormatter(function(d,i) {
2983 return yAxis.tickFormat()(d);
2992 interactiveLayer.renderGuideLine(pointXLocation);
2996 interactiveLayer.dispatch.on("elementMouseout",function(e) {
2997 dispatch.tooltipHide();
2998 lines.clearHighlights();
3001 dispatch.on('tooltipShow', function(e) {
3002 if (tooltips) showTooltip(e, that.parentNode);
3006 // Update chart from a state object passed to event handler
3007 dispatch.on('changeState', function(e) {
3009 if (typeof e.disabled !== 'undefined') {
3010 data.forEach(function(series,i) {
3011 series.disabled = e.disabled[i];
3014 state.disabled = e.disabled;
3018 if (typeof e.index !== 'undefined') {
3020 index.x = dx(index.i);
3022 state.index = e.index;
3028 if (typeof e.rescaleY !== 'undefined') {
3029 rescaleY = e.rescaleY;
3035 //============================================================
3039 renderWatch.renderEnd('cumulativeLineChart immediate');
3045 //============================================================
3046 // Event Handling/Dispatching (out of chart's scope)
3047 //------------------------------------------------------------
3049 lines.dispatch.on('elementMouseover.tooltip', function(e) {
3050 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3051 dispatch.tooltipShow(e);
3054 lines.dispatch.on('elementMouseout.tooltip', function(e) {
3055 dispatch.tooltipHide(e);
3058 dispatch.on('tooltipHide', function() {
3059 if (tooltips) nv.tooltip.cleanup();
3062 //============================================================
3065 //============================================================
3066 // Expose Public Variables
3067 //------------------------------------------------------------
3069 // expose chart's sub-components
3070 chart.dispatch = dispatch;
3071 chart.lines = lines;
3072 chart.legend = legend;
3073 chart.xAxis = xAxis;
3074 chart.yAxis = yAxis;
3075 chart.interactiveLayer = interactiveLayer;
3077 // DO NOT DELETE. This is currently overridden below
3078 // until deprecated portions are removed.
3079 chart.state = state;
3081 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi', 'id');
3083 chart.options = nv.utils.optionsFunc.bind(chart);
3085 chart.margin = function(_) {
3086 if (!arguments.length) return margin;
3087 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3088 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3089 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3090 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3094 chart.width = function(_) {
3095 if (!arguments.length) return width;
3100 chart.height = function(_) {
3101 if (!arguments.length) return height;
3106 chart.color = function(_) {
3107 if (!arguments.length) return color;
3108 color = nv.utils.getColor(_);
3109 legend.color(color);
3113 chart.rescaleY = function(_) {
3114 if (!arguments.length) return rescaleY;
3119 chart.showControls = function(_) {
3120 if (!arguments.length) return showControls;
3125 chart.useInteractiveGuideline = function(_) {
3126 if(!arguments.length) return useInteractiveGuideline;
3127 useInteractiveGuideline = _;
3129 chart.interactive(false);
3130 chart.useVoronoi(false);
3135 chart.showLegend = function(_) {
3136 if (!arguments.length) return showLegend;
3141 chart.showXAxis = function(_) {
3142 if (!arguments.length) return showXAxis;
3147 chart.showYAxis = function(_) {
3148 if (!arguments.length) return showYAxis;
3153 chart.rightAlignYAxis = function(_) {
3154 if(!arguments.length) return rightAlignYAxis;
3155 rightAlignYAxis = _;
3156 yAxis.orient( (_) ? 'right' : 'left');
3160 chart.tooltips = function(_) {
3161 if (!arguments.length) return tooltips;
3166 chart.tooltipContent = function(_) {
3167 if (!arguments.length) return tooltip;
3173 chart.state = function(_) {
3174 nv.deprecated('cululativeLineChart.state');
3175 if (!arguments.length) return state;
3179 for (var key in state) {
3180 chart.state[key] = state[key];
3184 chart.defaultState = function(_) {
3185 if (!arguments.length) return defaultState;
3190 chart.noData = function(_) {
3191 if (!arguments.length) return noData;
3196 chart.average = function(_) {
3197 if(!arguments.length) return average;
3202 chart.transitionDuration = function(_) {
3203 nv.deprecated('cumulativeLineChart.transitionDuration');
3204 return chart.duration(_);
3207 chart.duration = function(_) {
3208 if(!arguments.length) return duration;
3210 lines.duration(duration);
3211 xAxis.duration(duration);
3212 yAxis.duration(duration);
3213 renderWatch.reset(duration);
3217 chart.noErrorCheck = function(_) {
3218 if (!arguments.length) return noErrorCheck;
3223 //============================================================
3226 //============================================================
3228 //------------------------------------------------------------
3230 var indexifyYGetter = null;
3231 /* Normalize the data according to an index point. */
3232 function indexify(idx, data) {
3233 if (!indexifyYGetter) indexifyYGetter = lines.y();
3234 return data.map(function(line, i) {
3238 var indexValue = line.values[idx];
3239 if (indexValue == null) {
3242 var v = indexifyYGetter(indexValue, idx);
3244 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
3245 if (v < -.95 && !noErrorCheck) {
3246 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
3248 line.tempDisabled = true;
3252 line.tempDisabled = false;
3254 line.values = line.values.map(function(point, pointIndex) {
3255 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3263 //============================================================
3267 };//TODO: consider deprecating by adding necessary features to multiBar model
3268 nv.models.discreteBar = function() {
3270 //============================================================
3271 // Public Variables with Default Settings
3272 //------------------------------------------------------------
3274 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3277 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3278 , x = d3.scale.ordinal()
3279 , y = d3.scale.linear()
3280 , getX = function(d) { return d.x }
3281 , getY = function(d) { return d.y }
3282 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
3283 , color = nv.utils.defaultColor()
3284 , showValues = false
3285 , valueFormat = d3.format(',.2f')
3290 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
3291 , rectClass = 'discreteBar'
3295 //============================================================
3298 //============================================================
3299 // Private Variables
3300 //------------------------------------------------------------
3303 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3305 //============================================================
3308 function chart(selection) {
3309 renderWatch.reset();
3310 selection.each(function(data) {
3311 var availableWidth = width - margin.left - margin.right,
3312 availableHeight = height - margin.top - margin.bottom,
3313 container = d3.select(this);
3314 nv.utils.initSVG(container);
3317 //add series index to each data point for reference
3318 data.forEach(function(series, i) {
3319 series.values.forEach(function(point) {
3325 //------------------------------------------------------------
3328 // remap and flatten the data for use in calculating the scales' domains
3329 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
3330 data.map(function(d) {
3331 return d.values.map(function(d,i) {
3332 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
3336 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
3337 .rangeBands(xRange || [0, availableWidth], .1);
3339 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3342 // If showValues, pad the Y axis range to account for label height
3343 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3344 else y.range(yRange || [availableHeight, 0]);
3346 //store old scales if they exist
3348 y0 = y0 || y.copy().range([y(0),y(0)]);
3350 //------------------------------------------------------------
3353 //------------------------------------------------------------
3354 // Setup containers and skeleton of chart
3356 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3357 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3358 var gEnter = wrapEnter.append('g');
3359 var g = wrap.select('g');
3361 gEnter.append('g').attr('class', 'nv-groups');
3363 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3365 //------------------------------------------------------------
3369 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3370 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3371 .data(function(d) { return d }, function(d) { return d.key });
3372 groups.enter().append('g')
3373 .style('stroke-opacity', 1e-6)
3374 .style('fill-opacity', 1e-6);
3376 .watchTransition(renderWatch, 'discreteBar: exit groups')
3377 .style('stroke-opacity', 1e-6)
3378 .style('fill-opacity', 1e-6)
3381 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3382 .classed('hover', function(d) { return d.hover });
3384 .watchTransition(renderWatch, 'discreteBar: groups')
3385 .style('stroke-opacity', 1)
3386 .style('fill-opacity', .75);
3389 var bars = groups.selectAll('g.nv-bar')
3390 .data(function(d) { return d.values });
3392 bars.exit().remove();
3395 var barsEnter = bars.enter().append('g')
3396 .attr('transform', function(d,i,j) {
3397 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3399 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3400 d3.select(this).classed('hover', true);
3401 dispatch.elementMouseover({
3404 series: data[d.series],
3405 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
3407 seriesIndex: d.series,
3411 .on('mouseout', function(d,i) {
3412 d3.select(this).classed('hover', false);
3413 dispatch.elementMouseout({
3416 series: data[d.series],
3418 seriesIndex: d.series,
3422 .on('click', function(d,i) {
3423 dispatch.elementClick({
3426 series: data[d.series],
3427 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
3429 seriesIndex: d.series,
3432 d3.event.stopPropagation();
3434 .on('dblclick', function(d,i) {
3435 dispatch.elementDblClick({
3438 series: data[d.series],
3439 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
3441 seriesIndex: d.series,
3444 d3.event.stopPropagation();
3447 barsEnter.append('rect')
3449 .attr('width', x.rangeBand() * .9 / data.length )
3452 barsEnter.append('text')
3453 .attr('text-anchor', 'middle')
3457 .text(function(d,i) { return valueFormat(getY(d,i)) })
3458 .watchTransition(renderWatch, 'discreteBar: bars text')
3459 .attr('x', x.rangeBand() * .9 / 2)
3460 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3464 bars.selectAll('text').remove();
3468 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3469 .style('fill', function(d,i) { return d.color || color(d,i) })
3470 .style('stroke', function(d,i) { return d.color || color(d,i) })
3472 .attr('class', rectClass)
3473 .watchTransition(renderWatch, 'discreteBar: bars rect')
3474 .attr('width', x.rangeBand() * .9 / data.length);
3475 bars.watchTransition(renderWatch, 'discreteBar: bars')
3476 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3477 .attr('transform', function(d,i) {
3478 var left = x(getX(d,i)) + x.rangeBand() * .05,
3479 top = getY(d,i) < 0 ?
3481 y(0) - y(getY(d,i)) < 1 ?
3482 y(0) - 1 : //make 1 px positive bars show up above y=0
3485 return 'translate(' + left + ', ' + top + ')'
3488 .attr('height', function(d,i) {
3489 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3493 //store old scales for use in transitions on update
3499 renderWatch.renderEnd('discreteBar immediate');
3504 //============================================================
3505 // Expose Public Variables
3506 //------------------------------------------------------------
3508 chart.dispatch = dispatch;
3510 chart.options = nv.utils.optionsFunc.bind(chart);
3512 chart.x = function(_) {
3513 if (!arguments.length) return getX;
3518 chart.y = function(_) {
3519 if (!arguments.length) return getY;
3524 chart.margin = function(_) {
3525 if (!arguments.length) return margin;
3526 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3527 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3528 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3529 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3533 chart.width = function(_) {
3534 if (!arguments.length) return width;
3539 chart.height = function(_) {
3540 if (!arguments.length) return height;
3545 chart.xScale = function(_) {
3546 if (!arguments.length) return x;
3551 chart.yScale = function(_) {
3552 if (!arguments.length) return y;
3557 chart.xDomain = function(_) {
3558 if (!arguments.length) return xDomain;
3563 chart.yDomain = function(_) {
3564 if (!arguments.length) return yDomain;
3569 chart.xRange = function(_) {
3570 if (!arguments.length) return xRange;
3575 chart.yRange = function(_) {
3576 if (!arguments.length) return yRange;
3581 chart.forceY = function(_) {
3582 if (!arguments.length) return forceY;
3587 chart.color = function(_) {
3588 if (!arguments.length) return color;
3589 color = nv.utils.getColor(_);
3593 chart.id = function(_) {
3594 if (!arguments.length) return id;
3599 chart.showValues = function(_) {
3600 if (!arguments.length) return showValues;
3605 chart.valueFormat= function(_) {
3606 if (!arguments.length) return valueFormat;
3611 chart.rectClass= function(_) {
3612 if (!arguments.length) return rectClass;
3617 chart.duration = function(_) {
3618 if (!arguments.length) return duration;
3620 renderWatch.reset(duration);
3623 //============================================================
3629 nv.models.discreteBarChart = function() {
3631 //============================================================
3632 // Public Variables with Default Settings
3633 //------------------------------------------------------------
3635 var discretebar = nv.models.discreteBar()
3636 , xAxis = nv.models.axis()
3637 , yAxis = nv.models.axis()
3640 var margin = {top: 15, right: 10, bottom: 50, left: 60}
3643 , color = nv.utils.getColor()
3646 , rightAlignYAxis = false
3647 , staggerLabels = false
3649 , tooltip = function(key, x, y, e, graph) {
3650 return '<h3>' + x + '</h3>' +
3655 , noData = "No Data Available."
3656 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate','renderEnd')
3662 .highlightZero(false)
3664 .tickFormat(function(d) { return d })
3667 .orient((rightAlignYAxis) ? 'right' : 'left')
3668 .tickFormat(d3.format(',.1f'))
3671 //============================================================
3674 //============================================================
3675 // Private Variables
3676 //------------------------------------------------------------
3678 var showTooltip = function(e, offsetElement) {
3679 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3680 top = e.pos[1] + ( offsetElement.offsetTop || 0),
3681 x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3682 y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3683 content = tooltip(e.series.key, x, y, e, chart);
3685 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3688 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3689 //============================================================
3692 function chart(selection) {
3693 renderWatch.reset();
3694 renderWatch.models(discretebar);
3695 if (showXAxis) renderWatch.models(xAxis);
3696 if (showYAxis) renderWatch.models(yAxis);
3698 selection.each(function(data) {
3699 var container = d3.select(this),
3701 nv.utils.initSVG(container);
3702 var availableWidth = (width || parseInt(container.style('width')) || 960)
3703 - margin.left - margin.right,
3704 availableHeight = (height || parseInt(container.style('height')) || 400)
3705 - margin.top - margin.bottom;
3708 chart.update = function() {
3709 dispatch.beforeUpdate();
3710 container.transition().duration(duration).call(chart);
3712 chart.container = this;
3715 //------------------------------------------------------------
3716 // Display No Data message if there's nothing to show.
3718 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3719 var noDataText = container.selectAll('.nv-noData').data([noData]);
3721 noDataText.enter().append('text')
3722 .attr('class', 'nvd3 nv-noData')
3723 .attr('dy', '-.7em')
3724 .style('text-anchor', 'middle');
3727 .attr('x', margin.left + availableWidth / 2)
3728 .attr('y', margin.top + availableHeight / 2)
3729 .text(function(d) { return d });
3733 container.selectAll('.nv-noData').remove();
3736 //------------------------------------------------------------
3739 //------------------------------------------------------------
3742 x = discretebar.xScale();
3743 y = discretebar.yScale().clamp(true);
3745 //------------------------------------------------------------
3748 //------------------------------------------------------------
3749 // Setup containers and skeleton of chart
3751 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3752 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3753 var defsEnter = gEnter.append('defs');
3754 var g = wrap.select('g');
3756 gEnter.append('g').attr('class', 'nv-x nv-axis');
3757 gEnter.append('g').attr('class', 'nv-y nv-axis')
3758 .append('g').attr('class', 'nv-zeroLine')
3761 gEnter.append('g').attr('class', 'nv-barsWrap');
3763 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3765 if (rightAlignYAxis) {
3766 g.select(".nv-y.nv-axis")
3767 .attr("transform", "translate(" + availableWidth + ",0)");
3770 //------------------------------------------------------------
3773 //------------------------------------------------------------
3774 // Main Chart Component(s)
3777 .width(availableWidth)
3778 .height(availableHeight);
3781 var barsWrap = g.select('.nv-barsWrap')
3782 .datum(data.filter(function(d) { return !d.disabled }))
3784 barsWrap.transition().call(discretebar);
3786 //------------------------------------------------------------
3790 defsEnter.append('clipPath')
3791 .attr('id', 'nv-x-label-clip-' + discretebar.id())
3794 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3795 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3797 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3800 //------------------------------------------------------------
3806 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
3807 .tickSize(-availableHeight, 0);
3809 g.select('.nv-x.nv-axis')
3810 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3812 g.select('.nv-x.nv-axis').call(xAxis);
3815 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3817 if (staggerLabels) {
3820 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3827 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3828 .tickSize( -availableWidth, 0);
3830 g.select('.nv-y.nv-axis').call(yAxis);
3834 g.select(".nv-zeroLine line")
3836 .attr("x2",availableWidth)
3841 //------------------------------------------------------------
3844 //============================================================
3845 // Event Handling/Dispatching (in chart's scope)
3846 //------------------------------------------------------------
3848 dispatch.on('tooltipShow', function(e) {
3849 if (tooltips) showTooltip(e, that.parentNode);
3852 //============================================================
3857 renderWatch.renderEnd('discreteBar chart immediate');
3861 //============================================================
3862 // Event Handling/Dispatching (out of chart's scope)
3863 //------------------------------------------------------------
3865 discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3866 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3867 dispatch.tooltipShow(e);
3870 discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3871 dispatch.tooltipHide(e);
3874 dispatch.on('tooltipHide', function() {
3875 if (tooltips) nv.tooltip.cleanup();
3878 //============================================================
3881 //============================================================
3882 // Expose Public Variables
3883 //------------------------------------------------------------
3885 // expose chart's sub-components
3886 chart.dispatch = dispatch;
3887 chart.discretebar = discretebar;
3888 chart.xAxis = xAxis;
3889 chart.yAxis = yAxis;
3891 d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3893 chart.options = nv.utils.optionsFunc.bind(chart);
3895 chart.margin = function(_) {
3896 if (!arguments.length) return margin;
3897 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3898 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3899 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3900 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3904 chart.width = function(_) {
3905 if (!arguments.length) return width;
3910 chart.height = function(_) {
3911 if (!arguments.length) return height;
3916 chart.color = function(_) {
3917 if (!arguments.length) return color;
3918 color = nv.utils.getColor(_);
3919 discretebar.color(color);
3923 chart.showXAxis = function(_) {
3924 if (!arguments.length) return showXAxis;
3929 chart.showYAxis = function(_) {
3930 if (!arguments.length) return showYAxis;
3935 chart.rightAlignYAxis = function(_) {
3936 if(!arguments.length) return rightAlignYAxis;
3937 rightAlignYAxis = _;
3938 yAxis.orient( (_) ? 'right' : 'left');
3942 chart.staggerLabels = function(_) {
3943 if (!arguments.length) return staggerLabels;
3948 chart.tooltips = function(_) {
3949 if (!arguments.length) return tooltips;
3954 chart.tooltipContent = function(_) {
3955 if (!arguments.length) return tooltip;
3960 chart.noData = function(_) {
3961 if (!arguments.length) return noData;
3966 chart.transitionDuration = function(_) {
3967 nv.deprecated('discreteBar.transitionDuration');
3968 return chart.duration(_);
3971 chart.duration = function(_) {
3972 if (!arguments.length) return duration;
3974 renderWatch.reset(duration);
3975 discretebar.duration(duration);
3976 xAxis.duration(duration);
3977 yAxis.duration(duration);
3981 //============================================================
3987 nv.models.distribution = function() {
3989 //============================================================
3990 // Public Variables with Default Settings
3991 //------------------------------------------------------------
3993 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3994 , width = 400 //technically width or height depending on x or y....
3996 , axis = 'x' // 'x' or 'y'... horizontal or vertical
3997 , getData = function(d) { return d[axis] } // defaults d.x or d.y
3998 , color = nv.utils.defaultColor()
3999 , scale = d3.scale.linear()
4002 , dispatch = d3.dispatch('renderEnd')
4005 //============================================================
4008 //============================================================
4009 // Private Variables
4010 //------------------------------------------------------------
4013 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4015 //============================================================
4018 function chart(selection) {
4019 renderWatch.reset();
4020 selection.each(function(data) {
4021 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
4022 naxis = axis == 'x' ? 'y' : 'x',
4023 container = d3.select(this);
4024 nv.utils.initSVG(container);
4026 //------------------------------------------------------------
4029 scale0 = scale0 || scale;
4031 //------------------------------------------------------------
4034 //------------------------------------------------------------
4035 // Setup containers and skeleton of chart
4037 var wrap = container.selectAll('g.nv-distribution').data([data]);
4038 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
4039 var gEnter = wrapEnter.append('g');
4040 var g = wrap.select('g');
4042 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4044 //------------------------------------------------------------
4047 var distWrap = g.selectAll('g.nv-dist')
4048 .data(function(d) { return d }, function(d) { return d.key });
4050 distWrap.enter().append('g');
4052 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
4053 .style('stroke', function(d,i) { return color(d, i) });
4055 var dist = distWrap.selectAll('line.nv-dist' + axis)
4056 .data(function(d) { return d.values })
4057 dist.enter().append('line')
4058 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
4059 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
4060 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
4062 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4063 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4064 .style('stroke-opacity', 0)
4067 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
4068 .attr(naxis + '1', 0)
4069 .attr(naxis + '2', size);
4070 renderWatch.transition(dist, 'dist')
4072 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4073 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4076 scale0 = scale.copy();
4079 renderWatch.renderEnd('distribution immediate');
4084 //============================================================
4085 // Expose Public Variables
4086 //------------------------------------------------------------
4087 chart.options = nv.utils.optionsFunc.bind(chart);
4088 chart.dispatch = dispatch;
4090 chart.margin = function(_) {
4091 if (!arguments.length) return margin;
4092 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4093 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4094 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4095 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4099 chart.width = function(_) {
4100 if (!arguments.length) return width;
4105 chart.axis = function(_) {
4106 if (!arguments.length) return axis;
4111 chart.size = function(_) {
4112 if (!arguments.length) return size;
4117 chart.getData = function(_) {
4118 if (!arguments.length) return getData;
4119 getData = d3.functor(_);
4123 chart.scale = function(_) {
4124 if (!arguments.length) return scale;
4129 chart.color = function(_) {
4130 if (!arguments.length) return color;
4131 color = nv.utils.getColor(_);
4135 chart.duration = function(_) {
4136 if (!arguments.length) return duration;
4138 renderWatch.reset(duration);
4141 //============================================================
4146 //TODO: consider deprecating and using multibar with single series for this
4147 nv.models.historicalBar = function() {
4149 //============================================================
4150 // Public Variables with Default Settings
4151 //------------------------------------------------------------
4153 var margin = {top: 0, right: 0, bottom: 0, left: 0}
4156 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
4157 , x = d3.scale.linear()
4158 , y = d3.scale.linear()
4159 , getX = function(d) { return d.x }
4160 , getY = function(d) { return d.y }
4165 , color = nv.utils.defaultColor()
4170 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
4171 , interactive = true
4174 //============================================================
4175 var renderWatch = nv.utils.renderWatch(dispatch, 0);
4177 function chart(selection) {
4178 selection.each(function(data) {
4179 renderWatch.reset();
4180 var availableWidth = width - margin.left - margin.right,
4181 availableHeight = height - margin.top - margin.bottom,
4182 container = d3.select(this);
4183 nv.utils.initSVG(container);
4185 //------------------------------------------------------------
4188 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
4191 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
4193 x.range(xRange || [0, availableWidth]);
4195 y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
4196 .range(yRange || [availableHeight, 0]);
4198 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
4200 if (x.domain()[0] === x.domain()[1])
4202 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
4205 if (y.domain()[0] === y.domain()[1])
4207 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
4210 //------------------------------------------------------------
4213 //------------------------------------------------------------
4214 // Setup containers and skeleton of chart
4216 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
4217 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
4218 var defsEnter = wrapEnter.append('defs');
4219 var gEnter = wrapEnter.append('g');
4220 var g = wrap.select('g');
4222 gEnter.append('g').attr('class', 'nv-bars');
4224 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4226 //------------------------------------------------------------
4230 .on('click', function(d,i) {
4231 dispatch.chartClick({
4240 defsEnter.append('clipPath')
4241 .attr('id', 'nv-chart-clip-path-' + id)
4244 wrap.select('#nv-chart-clip-path-' + id + ' rect')
4245 .attr('width', availableWidth)
4246 .attr('height', availableHeight);
4248 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
4252 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
4253 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
4255 bars.exit().remove();
4258 var barsEnter = bars.enter().append('rect')
4259 //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4261 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
4262 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
4263 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4264 .on('mouseover', function(d,i) {
4265 if (!interactive) return;
4266 d3.select(this).classed('hover', true);
4267 dispatch.elementMouseover({
4270 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
4277 .on('mouseout', function(d,i) {
4278 if (!interactive) return;
4279 d3.select(this).classed('hover', false);
4280 dispatch.elementMouseout({
4288 .on('click', function(d,i) {
4289 if (!interactive) return;
4290 dispatch.elementClick({
4295 pos: [x(getX(d,i)), y(getY(d,i))],
4299 d3.event.stopPropagation();
4301 .on('dblclick', function(d,i) {
4302 if (!interactive) return;
4303 dispatch.elementDblClick({
4308 pos: [x(getX(d,i)), y(getY(d,i))],
4312 d3.event.stopPropagation();
4316 .attr('fill', function(d,i) { return color(d, i); })
4317 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4318 .watchTransition(renderWatch, 'bars')
4319 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4320 //TODO: better width calculations that don't assume always uniform data spacing;w
4321 .attr('width', (availableWidth / data[0].values.length) * .9 );
4324 bars.watchTransition(renderWatch, 'bars')
4325 .attr('y', function(d,i) {
4326 var rval = getY(d,i) < 0 ?
4328 y(0) - y(getY(d,i)) < 1 ?
4331 return nv.utils.NaNtoZero(rval);
4333 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
4337 renderWatch.renderEnd('historicalBar immediate');
4341 //Create methods to allow outside functions to highlight a specific bar.
4342 chart.highlightPoint = function(pointIndex, isHoverOver) {
4343 d3.select(".nv-historicalBar-" + id)
4344 .select(".nv-bars .nv-bar-0-" + pointIndex)
4345 .classed("hover", isHoverOver)
4349 chart.clearHighlights = function() {
4350 d3.select(".nv-historicalBar-" + id)
4351 .select(".nv-bars .nv-bar.hover")
4352 .classed("hover", false)
4355 //============================================================
4356 // Expose Public Variables
4357 //------------------------------------------------------------
4359 chart.dispatch = dispatch;
4361 chart.options = nv.utils.optionsFunc.bind(chart);
4363 chart.x = function(_) {
4364 if (!arguments.length) return getX;
4369 chart.y = function(_) {
4370 if (!arguments.length) return getY;
4375 chart.margin = function(_) {
4376 if (!arguments.length) return margin;
4377 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4378 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4379 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4380 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4384 chart.width = function(_) {
4385 if (!arguments.length) return width;
4390 chart.height = function(_) {
4391 if (!arguments.length) return height;
4396 chart.xScale = function(_) {
4397 if (!arguments.length) return x;
4402 chart.yScale = function(_) {
4403 if (!arguments.length) return y;
4408 chart.xDomain = function(_) {
4409 if (!arguments.length) return xDomain;
4414 chart.yDomain = function(_) {
4415 if (!arguments.length) return yDomain;
4420 chart.xRange = function(_) {
4421 if (!arguments.length) return xRange;
4426 chart.yRange = function(_) {
4427 if (!arguments.length) return yRange;
4432 chart.forceX = function(_) {
4433 if (!arguments.length) return forceX;
4438 chart.forceY = function(_) {
4439 if (!arguments.length) return forceY;
4444 chart.padData = function(_) {
4445 if (!arguments.length) return padData;
4450 chart.clipEdge = function(_) {
4451 if (!arguments.length) return clipEdge;
4456 chart.color = function(_) {
4457 if (!arguments.length) return color;
4458 color = nv.utils.getColor(_);
4462 chart.id = function(_) {
4463 if (!arguments.length) return id;
4468 chart.interactive = function(_) {
4469 if(!arguments.length) return interactive;
4470 interactive = false;
4474 //============================================================
4480 nv.models.historicalBarChart = function() {
4482 //============================================================
4483 // Public Variables with Default Settings
4484 //------------------------------------------------------------
4486 var bars = nv.models.historicalBar()
4487 , xAxis = nv.models.axis()
4488 , yAxis = nv.models.axis()
4489 , legend = nv.models.legend()
4490 , interactiveLayer = nv.interactiveGuideline()
4494 var margin = {top: 30, right: 90, bottom: 50, left: 90}
4495 , color = nv.utils.defaultColor()
4498 , showLegend = false
4501 , rightAlignYAxis = false
4502 , useInteractiveGuideline = false
4504 , tooltip = function(key, x, y, e, graph) {
4505 return '<h3>' + key + '</h3>' +
4506 '<p>' + y + ' at ' + x + '</p>'
4511 , defaultState = null
4512 , noData = 'No Data Available.'
4513 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
4514 , transitionDuration = 250
4522 .orient( (rightAlignYAxis) ? 'right' : 'left')
4525 //============================================================
4528 //============================================================
4529 // Private Variables
4530 //------------------------------------------------------------
4532 var showTooltip = function(e, offsetElement) {
4534 // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4535 if (offsetElement) {
4536 var svg = d3.select(offsetElement).select('svg');
4537 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4539 viewBox = viewBox.split(' ');
4540 var ratio = parseInt(svg.style('width')) / viewBox[2];
4541 e.pos[0] = e.pos[0] * ratio;
4542 e.pos[1] = e.pos[1] * ratio;
4546 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4547 top = e.pos[1] + ( offsetElement.offsetTop || 0),
4548 x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4549 y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4550 content = tooltip(e.series.key, x, y, e, chart);
4552 nv.tooltip.show([left, top], content, null, null, offsetElement);
4555 var renderWatch = nv.utils.renderWatch(dispatch, 0);
4557 //============================================================
4560 function chart(selection) {
4561 selection.each(function(data) {
4562 renderWatch.reset();
4563 renderWatch.models(bars);
4564 if (showXAxis) renderWatch.models(xAxis);
4565 if (showYAxis) renderWatch.models(yAxis);
4567 var container = d3.select(this),
4569 nv.utils.initSVG(container);
4570 var availableWidth = (width || parseInt(container.style('width')) || 960)
4571 - margin.left - margin.right,
4572 availableHeight = (height || parseInt(container.style('height')) || 400)
4573 - margin.top - margin.bottom;
4576 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4577 chart.container = this;
4579 //set state.disabled
4580 state.disabled = data.map(function(d) { return !!d.disabled });
4582 if (!defaultState) {
4585 for (key in state) {
4586 if (state[key] instanceof Array)
4587 defaultState[key] = state[key].slice(0);
4589 defaultState[key] = state[key];
4593 //------------------------------------------------------------
4594 // Display noData message if there's nothing to show.
4596 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4597 var noDataText = container.selectAll('.nv-noData').data([noData]);
4599 noDataText.enter().append('text')
4600 .attr('class', 'nvd3 nv-noData')
4601 .attr('dy', '-.7em')
4602 .style('text-anchor', 'middle');
4605 .attr('x', margin.left + availableWidth / 2)
4606 .attr('y', margin.top + availableHeight / 2)
4607 .text(function(d) { return d });
4611 container.selectAll('.nv-noData').remove();
4614 //------------------------------------------------------------
4617 //------------------------------------------------------------
4623 //------------------------------------------------------------
4626 //------------------------------------------------------------
4627 // Setup containers and skeleton of chart
4629 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4630 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4631 var g = wrap.select('g');
4633 gEnter.append('g').attr('class', 'nv-x nv-axis');
4634 gEnter.append('g').attr('class', 'nv-y nv-axis');
4635 gEnter.append('g').attr('class', 'nv-barsWrap');
4636 gEnter.append('g').attr('class', 'nv-legendWrap');
4637 gEnter.append('g').attr('class', 'nv-interactive');
4639 //------------------------------------------------------------
4642 //------------------------------------------------------------
4646 legend.width(availableWidth);
4648 g.select('.nv-legendWrap')
4652 if ( margin.top != legend.height()) {
4653 margin.top = legend.height();
4654 availableHeight = (height || parseInt(container.style('height')) || 400)
4655 - margin.top - margin.bottom;
4658 wrap.select('.nv-legendWrap')
4659 .attr('transform', 'translate(0,' + (-margin.top) +')')
4662 //------------------------------------------------------------
4664 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4666 if (rightAlignYAxis) {
4667 g.select(".nv-y.nv-axis")
4668 .attr("transform", "translate(" + availableWidth + ",0)");
4672 //------------------------------------------------------------
4673 // Main Chart Component(s)
4675 //------------------------------------------------------------
4676 //Set up interactive layer
4677 if (useInteractiveGuideline) {
4679 .width(availableWidth)
4680 .height(availableHeight)
4681 .margin({left:margin.left, top:margin.top})
4682 .svgContainer(container)
4684 wrap.select(".nv-interactive").call(interactiveLayer);
4688 .width(availableWidth)
4689 .height(availableHeight)
4690 .color(data.map(function(d,i) {
4691 return d.color || color(d, i);
4692 }).filter(function(d,i) { return !data[i].disabled }));
4695 var barsWrap = g.select('.nv-barsWrap')
4696 .datum(data.filter(function(d) { return !d.disabled }))
4698 barsWrap.transition().call(bars);
4700 //------------------------------------------------------------
4703 //------------------------------------------------------------
4709 .tickSize(-availableHeight, 0);
4711 g.select('.nv-x.nv-axis')
4712 .attr('transform', 'translate(0,' + y.range()[0] + ')');
4713 g.select('.nv-x.nv-axis')
4721 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4722 .tickSize( -availableWidth, 0);
4724 g.select('.nv-y.nv-axis')
4728 //------------------------------------------------------------
4731 //============================================================
4732 // Event Handling/Dispatching (in chart's scope)
4733 //------------------------------------------------------------
4735 interactiveLayer.dispatch.on('elementMousemove', function(e) {
4736 bars.clearHighlights()
4738 var singlePoint, pointIndex, pointXLocation, allData = [];
4740 .filter(function(series, i) {
4741 series.seriesIndex = i;
4742 return !series.disabled;
4744 .forEach(function(series,i) {
4745 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4746 bars.highlightPoint(pointIndex,true);
4747 var point = series.values[pointIndex];
4748 if (typeof point === 'undefined') return;
4749 if (typeof singlePoint === 'undefined') singlePoint = point;
4750 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4753 value: chart.y()(point, pointIndex),
4754 color: color(series,series.seriesIndex)
4758 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4759 interactiveLayer.tooltip
4760 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4761 .chartContainer(that.parentNode)
4763 .valueFormatter(function(d,i) {
4764 return yAxis.tickFormat()(d);
4773 interactiveLayer.renderGuideLine(pointXLocation);
4777 interactiveLayer.dispatch.on("elementMouseout",function(e) {
4778 dispatch.tooltipHide();
4779 bars.clearHighlights();
4782 legend.dispatch.on('legendClick', function(d,i) {
4783 d.disabled = !d.disabled;
4785 if (!data.filter(function(d) { return !d.disabled }).length) {
4786 data.map(function(d) {
4788 wrap.selectAll('.nv-series').classed('disabled', false);
4793 state.disabled = data.map(function(d) { return !!d.disabled });
4794 dispatch.stateChange(state);
4796 selection.transition().call(chart);
4799 legend.dispatch.on('legendDblclick', function(d) {
4800 //Double clicking should always enable current series, and disabled all others.
4801 data.forEach(function(d) {
4806 state.disabled = data.map(function(d) { return !!d.disabled });
4807 dispatch.stateChange(state);
4811 dispatch.on('tooltipShow', function(e) {
4812 if (tooltips) showTooltip(e, that.parentNode);
4816 dispatch.on('changeState', function(e) {
4818 if (typeof e.disabled !== 'undefined') {
4819 data.forEach(function(series,i) {
4820 series.disabled = e.disabled[i];
4823 state.disabled = e.disabled;
4829 //============================================================
4833 renderWatch.renderEnd('historicalBarChart immediate');
4838 //============================================================
4839 // Event Handling/Dispatching (out of chart's scope)
4840 //------------------------------------------------------------
4842 bars.dispatch.on('elementMouseover.tooltip', function(e) {
4843 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
4844 dispatch.tooltipShow(e);
4847 bars.dispatch.on('elementMouseout.tooltip', function(e) {
4848 dispatch.tooltipHide(e);
4851 dispatch.on('tooltipHide', function() {
4852 if (tooltips) nv.tooltip.cleanup();
4855 //============================================================
4858 //============================================================
4859 // Expose Public Variables
4860 //------------------------------------------------------------
4862 // expose chart's sub-components
4863 chart.dispatch = dispatch;
4865 chart.legend = legend;
4866 chart.xAxis = xAxis;
4867 chart.yAxis = yAxis;
4868 chart.interactiveLayer = interactiveLayer;
4870 d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale',
4871 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights');
4873 chart.options = nv.utils.optionsFunc.bind(chart);
4875 chart.margin = function(_) {
4876 if (!arguments.length) return margin;
4877 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4878 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4879 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4880 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4884 chart.width = function(_) {
4885 if (!arguments.length) return width;
4890 chart.height = function(_) {
4891 if (!arguments.length) return height;
4896 chart.color = function(_) {
4897 if (!arguments.length) return color;
4898 color = nv.utils.getColor(_);
4899 legend.color(color);
4903 chart.showLegend = function(_) {
4904 if (!arguments.length) return showLegend;
4909 chart.showXAxis = function(_) {
4910 if (!arguments.length) return showXAxis;
4915 chart.showYAxis = function(_) {
4916 if (!arguments.length) return showYAxis;
4921 chart.rightAlignYAxis = function(_) {
4922 if(!arguments.length) return rightAlignYAxis;
4923 rightAlignYAxis = _;
4924 yAxis.orient( (_) ? 'right' : 'left');
4928 chart.tooltips = function(_) {
4929 if (!arguments.length) return tooltips;
4934 chart.tooltipContent = function(_) {
4935 if (!arguments.length) return tooltip;
4940 chart.state = function(_) {
4941 if (!arguments.length) return state;
4946 chart.defaultState = function(_) {
4947 if (!arguments.length) return defaultState;
4952 chart.noData = function(_) {
4953 if (!arguments.length) return noData;
4958 chart.transitionDuration = function(_) {
4959 if (!arguments.length) return transitionDuration;
4960 transitionDuration = _;
4964 chart.useInteractiveGuideline = function(_) {
4965 if(!arguments.length) return useInteractiveGuideline;
4966 useInteractiveGuideline = _;
4968 chart.interactive(false);
4973 //============================================================
4978 nv.models.legend = function() {
4980 //============================================================
4981 // Public Variables with Default Settings
4982 //------------------------------------------------------------
4984 var margin = {top: 5, right: 0, bottom: 5, left: 0}
4987 , getKey = function(d) { return d.key }
4988 , color = nv.utils.defaultColor()
4991 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4992 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4993 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4996 //============================================================
4999 function chart(selection) {
5000 selection.each(function(data) {
5001 var availableWidth = width - margin.left - margin.right,
5002 container = d3.select(this);
5003 nv.utils.initSVG(container);
5005 //------------------------------------------------------------
5006 // Setup containers and skeleton of chart
5008 var wrap = container.selectAll('g.nv-legend').data([data]);
5009 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
5010 var g = wrap.select('g');
5012 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5014 //------------------------------------------------------------
5017 var series = g.selectAll('.nv-series')
5018 .data(function(d) { return d });
5019 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
5020 .on('mouseover', function(d,i) {
5021 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
5023 .on('mouseout', function(d,i) {
5024 dispatch.legendMouseout(d,i);
5026 .on('click', function(d,i) {
5027 dispatch.legendClick(d,i);
5029 if (radioButtonMode) {
5030 //Radio button mode: set every series to disabled,
5031 // and enable the clicked series.
5032 data.forEach(function(series) { series.disabled = true});
5036 d.disabled = !d.disabled;
5037 if (data.every(function(series) { return series.disabled})) {
5038 //the default behavior of NVD3 legends is, if every single series
5039 // is disabled, turn all series' back on.
5040 data.forEach(function(series) { series.disabled = false});
5043 dispatch.stateChange({
5044 disabled: data.map(function(d) { return !!d.disabled })
5048 .on('dblclick', function(d,i) {
5049 dispatch.legendDblclick(d,i);
5051 //the default behavior of NVD3 legends, when double clicking one,
5052 // is to set all other series' to false, and make the double clicked series enabled.
5053 data.forEach(function(series) {
5054 series.disabled = true;
5057 dispatch.stateChange({
5058 disabled: data.map(function(d) { return !!d.disabled })
5062 seriesEnter.append('circle')
5063 .style('stroke-width', 2)
5064 .attr('class','nv-legend-symbol')
5066 seriesEnter.append('text')
5067 .attr('text-anchor', 'start')
5068 .attr('class','nv-legend-text')
5069 .attr('dy', '.32em')
5071 series.classed('disabled', function(d) { return d.disabled });
5072 series.exit().remove();
5073 series.select('circle')
5074 .style('fill', function(d,i) { return d.color || color(d,i)})
5075 .style('stroke', function(d,i) { return d.color || color(d, i) });
5076 series.select('text').text(getKey);
5079 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
5081 // NEW ALIGNING CODE, TODO: clean up
5084 var seriesWidths = [];
5085 series.each(function(d,i) {
5086 var legendText = d3.select(this).select('text');
5089 nodeTextLength = legendText.node().getComputedTextLength();
5090 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
5091 if(nodeTextLength <= 0) throw Error();
5094 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5097 seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
5100 var seriesPerRow = 0;
5101 var legendWidth = 0;
5102 var columnWidths = [];
5104 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
5105 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
5106 legendWidth += seriesWidths[seriesPerRow++];
5108 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
5111 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
5115 for (var k = 0; k < seriesWidths.length; k++) {
5116 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
5117 columnWidths[k % seriesPerRow] = seriesWidths[k];
5120 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
5125 var xPositions = [];
5126 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5127 xPositions[i] = curX;
5128 curX += columnWidths[i];
5132 .attr('transform', function(d, i) {
5133 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
5136 //position legend as far right as possible within the total width
5138 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5141 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5144 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
5153 .attr('transform', function(d, i) {
5154 var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
5157 if (width < margin.left + margin.right + xpos + length) {
5163 if (newxpos > maxwidth) maxwidth = newxpos;
5165 return 'translate(' + xpos + ',' + ypos + ')';
5168 //position legend as far right as possible within the total width
5169 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5171 height = margin.top + margin.bottom + ypos + 15;
5181 //============================================================
5182 // Expose Public Variables
5183 //------------------------------------------------------------
5185 chart.dispatch = dispatch;
5186 chart.options = nv.utils.optionsFunc.bind(chart);
5188 chart.margin = function(_) {
5189 if (!arguments.length) return margin;
5190 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5191 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5192 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5193 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5197 chart.width = function(_) {
5198 if (!arguments.length) return width;
5203 chart.height = function(_) {
5204 if (!arguments.length) return height;
5209 chart.key = function(_) {
5210 if (!arguments.length) return getKey;
5215 chart.color = function(_) {
5216 if (!arguments.length) return color;
5217 color = nv.utils.getColor(_);
5221 chart.align = function(_) {
5222 if (!arguments.length) return align;
5227 chart.rightAlign = function(_) {
5228 if (!arguments.length) return rightAlign;
5233 chart.updateState = function(_) {
5234 if (!arguments.length) return updateState;
5239 chart.radioButtonMode = function(_) {
5240 if (!arguments.length) return radioButtonMode;
5241 radioButtonMode = _;
5245 //============================================================
5251 nv.models.line = function() {
5253 //============================================================
5254 // Public Variables with Default Settings
5255 //------------------------------------------------------------
5257 var scatter = nv.models.scatter()
5260 var margin = {top: 0, right: 0, bottom: 0, left: 0}
5263 , color = nv.utils.defaultColor() // a function that returns a color
5264 , getX = function(d) { return d.x } // accessor to get the x value from a data point
5265 , getY = function(d) { return d.y } // accessor to get the y value from a data point
5266 , 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
5267 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5268 , clipEdge = false // if true, masks lines within x and y scale
5269 , x //can be accessed via chart.xScale()
5270 , y //can be accessed via chart.yScale()
5271 , interpolate = "linear" // controls the line interpolation
5273 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
5277 .size(16) // default size
5278 .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5281 //============================================================
5284 //============================================================
5285 // Private Variables
5286 //------------------------------------------------------------
5288 var x0, y0 //used to store previous scales
5289 , renderWatch = nv.utils.renderWatch(dispatch, duration)
5292 //============================================================
5295 function chart(selection) {
5296 renderWatch.reset();
5297 renderWatch.models(scatter);
5298 selection.each(function(data) {
5299 var availableWidth = width - margin.left - margin.right,
5300 availableHeight = height - margin.top - margin.bottom,
5301 container = d3.select(this);
5302 nv.utils.initSVG(container);
5303 //------------------------------------------------------------
5306 x = scatter.xScale();
5307 y = scatter.yScale();
5312 //------------------------------------------------------------
5315 //------------------------------------------------------------
5316 // Setup containers and skeleton of chart
5318 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5319 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5320 var defsEnter = wrapEnter.append('defs');
5321 var gEnter = wrapEnter.append('g');
5322 var g = wrap.select('g')
5324 gEnter.append('g').attr('class', 'nv-groups');
5325 gEnter.append('g').attr('class', 'nv-scatterWrap');
5327 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5329 //------------------------------------------------------------
5335 .width(availableWidth)
5336 .height(availableHeight)
5338 var scatterWrap = wrap.select('.nv-scatterWrap');
5339 //.datum(data); // Data automatically trickles down from the wrap
5341 scatterWrap.call(scatter);
5345 defsEnter.append('clipPath')
5346 .attr('id', 'nv-edge-clip-' + scatter.id())
5349 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5350 .attr('width', availableWidth)
5351 .attr('height', (availableHeight > 0) ? availableHeight : 0);
5353 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5355 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5359 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5360 .data(function(d) { return d }, function(d) { return d.key });
5361 groups.enter().append('g')
5362 .style('stroke-opacity', 1e-6)
5363 .style('fill-opacity', 1e-6);
5365 groups.exit().remove();
5368 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5369 .classed('hover', function(d) { return d.hover })
5370 .style('fill', function(d,i){ return color(d, i) })
5371 .style('stroke', function(d,i){ return color(d, i)});
5372 groups.watchTransition(renderWatch, 'line: groups')
5373 .style('stroke-opacity', 1)
5374 .style('fill-opacity', .5);
5378 var areaPaths = groups.selectAll('path.nv-area')
5379 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5380 areaPaths.enter().append('path')
5381 .attr('class', 'nv-area')
5382 .attr('d', function(d) {
5383 return d3.svg.area()
5384 .interpolate(interpolate)
5386 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5387 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5388 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5389 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5390 .apply(this, [d.values])
5392 groups.exit().selectAll('path.nv-area')
5395 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
5396 .attr('d', function(d) {
5397 return d3.svg.area()
5398 .interpolate(interpolate)
5400 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5401 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5402 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5403 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5404 .apply(this, [d.values])
5409 var linePaths = groups.selectAll('path.nv-line')
5410 .data(function(d) { return [d.values] });
5411 linePaths.enter().append('path')
5412 .attr('class', 'nv-line')
5415 .interpolate(interpolate)
5417 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5418 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5421 linePaths.watchTransition(renderWatch, 'line: linePaths')
5424 .interpolate(interpolate)
5426 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5427 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5432 //store old scales for use in transitions on update
5437 renderWatch.renderEnd('line immediate');
5442 //============================================================
5443 // Expose Public Variables
5444 //------------------------------------------------------------
5446 chart.dispatch = dispatch;
5447 chart.scatter = scatter;
5448 // Pass through events
5449 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); })
5450 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); })
5451 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); })
5453 d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
5454 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5456 chart.options = nv.utils.optionsFunc.bind(chart);
5458 chart.margin = function(_) {
5459 if (!arguments.length) return margin;
5460 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5461 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5462 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5463 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5467 chart.width = function(_) {
5468 if (!arguments.length) return width;
5473 chart.height = function(_) {
5474 if (!arguments.length) return height;
5479 chart.x = function(_) {
5480 if (!arguments.length) return getX;
5486 chart.y = function(_) {
5487 if (!arguments.length) return getY;
5493 chart.clipEdge = function(_) {
5494 if (!arguments.length) return clipEdge;
5499 chart.color = function(_) {
5500 if (!arguments.length) return color;
5501 color = nv.utils.getColor(_);
5502 scatter.color(color);
5506 chart.interpolate = function(_) {
5507 if (!arguments.length) return interpolate;
5512 chart.defined = function(_) {
5513 if (!arguments.length) return defined;
5518 chart.isArea = function(_) {
5519 if (!arguments.length) return isArea;
5520 isArea = d3.functor(_);
5524 chart.duration = function(_) {
5525 if (!arguments.length) return duration;
5527 renderWatch.reset(duration);
5528 scatter.duration(duration);
5532 //============================================================
5537 nv.models.lineChart = function() {
5540 //============================================================
5541 // Public Variables with Default Settings
5542 //------------------------------------------------------------
5544 var lines = nv.models.line()
5545 , xAxis = nv.models.axis()
5546 , yAxis = nv.models.axis()
5547 , legend = nv.models.legend()
5548 , interactiveLayer = nv.interactiveGuideline()
5551 var margin = {top: 30, right: 20, bottom: 50, left: 60}
5552 , color = nv.utils.defaultColor()
5558 , rightAlignYAxis = false
5559 , useInteractiveGuideline = false
5561 , tooltip = function(key, x, y, e, graph) {
5562 return '<h3>' + key + '</h3>' +
5563 '<p>' + y + ' at ' + x + '</p>'
5567 , state = nv.utils.state()
5568 , defaultState = null
5569 , noData = 'No Data Available.'
5570 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
5579 .orient((rightAlignYAxis) ? 'right' : 'left')
5582 //============================================================
5585 //============================================================
5586 // Private Variables
5587 //------------------------------------------------------------
5589 var showTooltip = function(e, offsetElement) {
5590 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5591 top = e.pos[1] + ( offsetElement.offsetTop || 0),
5592 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5593 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5594 content = tooltip(e.series.key, x, y, e, chart);
5596 nv.tooltip.show([left, top], content, null, null, offsetElement);
5599 var renderWatch = nv.utils.renderWatch(dispatch, duration);
5601 var stateGetter = function(data) {
5604 active: data.map(function(d) { return !d.disabled })
5609 var stateSetter = function(data) {
5610 return function(state) {
5611 if (state.active !== undefined)
5612 data.forEach(function(series,i) {
5613 series.disabled = !state.active[i];
5618 //============================================================
5621 function chart(selection) {
5622 renderWatch.reset();
5623 renderWatch.models(lines);
5624 if (showXAxis) renderWatch.models(xAxis);
5625 if (showYAxis) renderWatch.models(yAxis);
5627 selection.each(function(data) {
5628 var container = d3.select(this),
5630 nv.utils.initSVG(container);
5631 var availableWidth = (width || parseInt(container.style('width')) || 960)
5632 - margin.left - margin.right,
5633 availableHeight = (height || parseInt(container.style('height')) || 400)
5634 - margin.top - margin.bottom;
5637 chart.update = function() {
5639 container.call(chart);
5641 container.transition().duration(duration).call(chart)
5643 chart.container = this;
5646 .setter(stateSetter(data), chart.update)
5647 .getter(stateGetter(data))
5650 // DEPRECATED set state.disableddisabled
5651 state.disabled = data.map(function(d) { return !!d.disabled });
5653 if (!defaultState) {
5656 for (key in state) {
5657 if (state[key] instanceof Array)
5658 defaultState[key] = state[key].slice(0);
5660 defaultState[key] = state[key];
5664 //------------------------------------------------------------
5665 // Display noData message if there's nothing to show.
5667 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5668 var noDataText = container.selectAll('.nv-noData').data([noData]);
5670 noDataText.enter().append('text')
5671 .attr('class', 'nvd3 nv-noData')
5672 .attr('dy', '-.7em')
5673 .style('text-anchor', 'middle');
5676 .attr('x', margin.left + availableWidth / 2)
5677 .attr('y', margin.top + availableHeight / 2)
5678 .text(function(d) { return d });
5682 container.selectAll('.nv-noData').remove();
5685 //------------------------------------------------------------
5688 //------------------------------------------------------------
5694 //------------------------------------------------------------
5697 //------------------------------------------------------------
5698 // Setup containers and skeleton of chart
5700 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5701 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5702 var g = wrap.select('g');
5704 gEnter.append("rect").style("opacity",0);
5705 gEnter.append('g').attr('class', 'nv-x nv-axis');
5706 gEnter.append('g').attr('class', 'nv-y nv-axis');
5707 gEnter.append('g').attr('class', 'nv-linesWrap');
5708 gEnter.append('g').attr('class', 'nv-legendWrap');
5709 gEnter.append('g').attr('class', 'nv-interactive');
5712 .attr("width",availableWidth)
5713 .attr("height",(availableHeight > 0) ? availableHeight : 0);
5714 //------------------------------------------------------------
5718 legend.width(availableWidth);
5720 g.select('.nv-legendWrap')
5724 if ( margin.top != legend.height()) {
5725 margin.top = legend.height();
5726 availableHeight = (height || parseInt(container.style('height')) || 400)
5727 - margin.top - margin.bottom;
5730 wrap.select('.nv-legendWrap')
5731 .attr('transform', 'translate(0,' + (-margin.top) +')')
5734 //------------------------------------------------------------
5736 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5738 if (rightAlignYAxis) {
5739 g.select(".nv-y.nv-axis")
5740 .attr("transform", "translate(" + availableWidth + ",0)");
5743 //------------------------------------------------------------
5744 // Main Chart Component(s)
5747 //------------------------------------------------------------
5748 //Set up interactive layer
5749 if (useInteractiveGuideline) {
5751 .width(availableWidth)
5752 .height(availableHeight)
5753 .margin({left:margin.left, top:margin.top})
5754 .svgContainer(container)
5756 wrap.select(".nv-interactive").call(interactiveLayer);
5761 .width(availableWidth)
5762 .height(availableHeight)
5763 .color(data.map(function(d,i) {
5764 return d.color || color(d, i);
5765 }).filter(function(d,i) { return !data[i].disabled }));
5768 var linesWrap = g.select('.nv-linesWrap')
5769 .datum(data.filter(function(d) { return !d.disabled }))
5771 linesWrap.call(lines);
5773 //------------------------------------------------------------
5776 //------------------------------------------------------------
5782 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5783 .tickSize(-availableHeight, 0);
5785 g.select('.nv-x.nv-axis')
5786 .attr('transform', 'translate(0,' + y.range()[0] + ')');
5787 g.select('.nv-x.nv-axis')
5794 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5795 .tickSize( -availableWidth, 0);
5797 g.select('.nv-y.nv-axis')
5800 //------------------------------------------------------------
5803 //============================================================
5804 // Event Handling/Dispatching (in chart's scope)
5805 //------------------------------------------------------------
5807 legend.dispatch.on('stateChange', function(newState) {
5808 for (var key in newState)
5809 state[key] = newState[key];
5810 dispatch.stateChange(state);
5814 interactiveLayer.dispatch.on('elementMousemove', function(e) {
5815 lines.clearHighlights();
5816 var singlePoint, pointIndex, pointXLocation, allData = [];
5818 .filter(function(series, i) {
5819 series.seriesIndex = i;
5820 return !series.disabled;
5822 .forEach(function(series,i) {
5823 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5824 lines.highlightPoint(i, pointIndex, true);
5825 var point = series.values[pointIndex];
5826 if (typeof point === 'undefined') return;
5827 if (typeof singlePoint === 'undefined') singlePoint = point;
5828 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5831 value: chart.y()(point, pointIndex),
5832 color: color(series,series.seriesIndex)
5835 //Highlight the tooltip entry based on which point the mouse is closest to.
5836 if (allData.length > 2) {
5837 var yValue = chart.yScale().invert(e.mouseY);
5838 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
5839 var threshold = 0.03 * domainExtent;
5840 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
5841 if (indexToHighlight !== null)
5842 allData[indexToHighlight].highlight = true;
5845 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5846 interactiveLayer.tooltip
5847 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5848 .chartContainer(that.parentNode)
5850 .valueFormatter(function(d,i) {
5851 return yAxis.tickFormat()(d);
5860 interactiveLayer.renderGuideLine(pointXLocation);
5864 interactiveLayer.dispatch.on('elementClick', function(e) {
5865 var pointXLocation, allData = [];
5867 data.filter(function(series, i) {
5868 series.seriesIndex = i;
5869 return !series.disabled;
5870 }).forEach(function(series) {
5871 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5872 var point = series.values[pointIndex];
5873 if (typeof point === 'undefined') return;
5874 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5875 var yPos = chart.yScale()(chart.y()(point,pointIndex));
5878 pointIndex: pointIndex,
5879 pos: [pointXLocation, yPos],
5880 seriesIndex: series.seriesIndex,
5885 lines.dispatch.elementClick(allData);
5888 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5889 dispatch.tooltipHide();
5890 lines.clearHighlights();
5893 dispatch.on('tooltipShow', function(e) {
5894 if (tooltips) showTooltip(e, that.parentNode);
5898 dispatch.on('changeState', function(e) {
5900 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
5901 data.forEach(function(series,i) {
5902 series.disabled = e.disabled[i];
5905 state.disabled = e.disabled;
5911 //============================================================
5915 renderWatch.renderEnd('lineChart immediate');
5920 //============================================================
5921 // Event Handling/Dispatching (out of chart's scope)
5922 //------------------------------------------------------------
5924 lines.dispatch.on('elementMouseover.tooltip', function(e) {
5925 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5926 dispatch.tooltipShow(e);
5929 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5930 dispatch.tooltipHide(e);
5933 dispatch.on('tooltipHide', function() {
5934 if (tooltips) nv.tooltip.cleanup();
5937 //============================================================
5940 //============================================================
5941 // Expose Public Variables
5942 //------------------------------------------------------------
5944 // expose chart's sub-components
5945 chart.dispatch = dispatch;
5946 chart.lines = lines;
5947 chart.legend = legend;
5948 chart.xAxis = xAxis;
5949 chart.yAxis = yAxis;
5950 chart.interactiveLayer = interactiveLayer;
5951 // DO NOT DELETE. This is currently overridden below
5952 // until deprecated portions are removed.
5953 chart.state = state;
5955 d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5956 , 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5958 chart.options = nv.utils.optionsFunc.bind(chart);
5960 chart.margin = function(_) {
5961 if (!arguments.length) return margin;
5962 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5963 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5964 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5965 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5969 chart.width = function(_) {
5970 if (!arguments.length) return width;
5975 chart.height = function(_) {
5976 if (!arguments.length) return height;
5981 chart.color = function(_) {
5982 if (!arguments.length) return color;
5983 color = nv.utils.getColor(_);
5984 legend.color(color);
5988 chart.showLegend = function(_) {
5989 if (!arguments.length) return showLegend;
5994 chart.showXAxis = function(_) {
5995 if (!arguments.length) return showXAxis;
6000 chart.showYAxis = function(_) {
6001 if (!arguments.length) return showYAxis;
6006 chart.rightAlignYAxis = function(_) {
6007 if(!arguments.length) return rightAlignYAxis;
6008 rightAlignYAxis = _;
6009 yAxis.orient( (_) ? 'right' : 'left');
6013 chart.useInteractiveGuideline = function(_) {
6014 if(!arguments.length) return useInteractiveGuideline;
6015 useInteractiveGuideline = _;
6017 chart.interactive(false);
6018 chart.useVoronoi(false);
6023 chart.tooltips = function(_) {
6024 if (!arguments.length) return tooltips;
6029 chart.tooltipContent = function(_) {
6030 if (!arguments.length) return tooltip;
6036 chart.state = function(_) {
6037 nv.deprecated('lineChart.state');
6038 if (!arguments.length) return state;
6042 for (var key in state) {
6043 chart.state[key] = state[key];
6047 chart.defaultState = function(_) {
6048 if (!arguments.length) return defaultState;
6053 chart.noData = function(_) {
6054 if (!arguments.length) return noData;
6059 chart.transitionDuration = function(_) {
6060 nv.deprecated('lineChart.transitionDuration');
6061 return chart.duration(_);
6064 chart.duration = function(_) {
6065 if (!arguments.length) return duration;
6067 renderWatch.reset(duration);
6068 lines.duration(duration);
6069 xAxis.duration(duration);
6070 yAxis.duration(duration);
6074 //============================================================
6079 nv.models.linePlusBarChart = function() {
6081 //============================================================
6082 // Public Variables with Default Settings
6083 //------------------------------------------------------------
6085 var lines = nv.models.line()
6086 , bars = nv.models.historicalBar()
6087 , xAxis = nv.models.axis()
6088 , y1Axis = nv.models.axis()
6089 , y2Axis = nv.models.axis()
6090 , legend = nv.models.legend()
6093 var margin = {top: 30, right: 60, bottom: 50, left: 60}
6096 , getX = function(d) { return d.x }
6097 , getY = function(d) { return d.y }
6098 , color = nv.utils.defaultColor()
6101 , tooltip = function(key, x, y, e, graph) {
6102 return '<h3>' + key + '</h3>' +
6103 '<p>' + y + ' at ' + x + '</p>';
6108 , state = nv.utils.state()
6109 , defaultState = null
6110 , noData = "No Data Available."
6111 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
6124 .highlightZero(false)
6133 //============================================================
6136 //============================================================
6137 // Private Variables
6138 //------------------------------------------------------------
6140 var showTooltip = function(e, offsetElement) {
6141 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6142 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6143 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6144 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6145 content = tooltip(e.series.key, x, y, e, chart);
6147 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6151 var stateGetter = function(data) {
6154 active: data.map(function(d) { return !d.disabled })
6159 var stateSetter = function(data) {
6160 return function(state) {
6161 if (state.active !== undefined)
6162 data.forEach(function(series,i) {
6163 series.disabled = !state.active[i];
6168 //------------------------------------------------------------
6172 function chart(selection) {
6173 selection.each(function(data) {
6174 var container = d3.select(this),
6176 nv.utils.initSVG(container);
6177 var availableWidth = (width || parseInt(container.style('width')) || 960)
6178 - margin.left - margin.right,
6179 availableHeight = (height || parseInt(container.style('height')) || 400)
6180 - margin.top - margin.bottom;
6182 chart.update = function() { container.transition().call(chart); };
6183 // chart.container = this;
6186 .setter(stateSetter(data), chart.update)
6187 .getter(stateGetter(data))
6190 // DEPRECATED set state.disableddisabled
6191 state.disabled = data.map(function(d) { return !!d.disabled });
6193 if (!defaultState) {
6196 for (key in state) {
6197 if (state[key] instanceof Array)
6198 defaultState[key] = state[key].slice(0);
6200 defaultState[key] = state[key];
6204 //------------------------------------------------------------
6205 // Display No Data message if there's nothing to show.
6207 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6208 var noDataText = container.selectAll('.nv-noData').data([noData]);
6210 noDataText.enter().append('text')
6211 .attr('class', 'nvd3 nv-noData')
6212 .attr('dy', '-.7em')
6213 .style('text-anchor', 'middle');
6216 .attr('x', margin.left + availableWidth / 2)
6217 .attr('y', margin.top + availableHeight / 2)
6218 .text(function(d) { return d });
6222 container.selectAll('.nv-noData').remove();
6225 //------------------------------------------------------------
6228 //------------------------------------------------------------
6231 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6232 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6234 //x = xAxis.scale();
6235 x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
6236 //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
6238 y2 = lines.yScale();
6240 //------------------------------------------------------------
6242 //------------------------------------------------------------
6243 // Setup containers and skeleton of chart
6245 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6246 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6247 var g = wrap.select('g');
6249 gEnter.append('g').attr('class', 'nv-x nv-axis');
6250 gEnter.append('g').attr('class', 'nv-y1 nv-axis');
6251 gEnter.append('g').attr('class', 'nv-y2 nv-axis');
6252 gEnter.append('g').attr('class', 'nv-barsWrap');
6253 gEnter.append('g').attr('class', 'nv-linesWrap');
6254 gEnter.append('g').attr('class', 'nv-legendWrap');
6256 //------------------------------------------------------------
6259 //------------------------------------------------------------
6263 legend.width( availableWidth / 2 );
6265 g.select('.nv-legendWrap')
6266 .datum(data.map(function(series) {
6267 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6268 series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6273 if ( margin.top != legend.height()) {
6274 margin.top = legend.height();
6275 availableHeight = (height || parseInt(container.style('height')) || 400)
6276 - margin.top - margin.bottom;
6279 g.select('.nv-legendWrap')
6280 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6283 //------------------------------------------------------------
6286 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6289 //------------------------------------------------------------
6290 // Main Chart Component(s)
6294 .width(availableWidth)
6295 .height(availableHeight)
6296 .color(data.map(function(d,i) {
6297 return d.color || color(d, i);
6298 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6301 .width(availableWidth)
6302 .height(availableHeight)
6303 .color(data.map(function(d,i) {
6304 return d.color || color(d, i);
6305 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6309 var barsWrap = g.select('.nv-barsWrap')
6310 .datum(dataBars.length ? dataBars : [{values:[]}]);
6312 var linesWrap = g.select('.nv-linesWrap')
6313 .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
6314 //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
6316 d3.transition(barsWrap).call(bars);
6317 d3.transition(linesWrap).call(lines);
6319 //------------------------------------------------------------
6322 //------------------------------------------------------------
6327 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6328 .tickSize(-availableHeight, 0);
6330 g.select('.nv-x.nv-axis')
6331 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6332 d3.transition(g.select('.nv-x.nv-axis'))
6338 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6339 .tickSize(-availableWidth, 0);
6341 d3.transition(g.select('.nv-y1.nv-axis'))
6342 .style('opacity', dataBars.length ? 1 : 0)
6348 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6349 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6351 g.select('.nv-y2.nv-axis')
6352 .style('opacity', dataLines.length ? 1 : 0)
6353 .attr('transform', 'translate(' + availableWidth + ',0)');
6354 //.attr('transform', 'translate(' + x.range()[1] + ',0)');
6356 d3.transition(g.select('.nv-y2.nv-axis'))
6359 //------------------------------------------------------------
6362 //============================================================
6363 // Event Handling/Dispatching (in chart's scope)
6364 //------------------------------------------------------------
6366 legend.dispatch.on('stateChange', function(newState) {
6367 for (var key in newState)
6368 state[key] = newState[key];
6369 dispatch.stateChange(state);
6373 dispatch.on('tooltipShow', function(e) {
6374 if (tooltips) showTooltip(e, that.parentNode);
6378 // Update chart from a state object passed to event handler
6379 dispatch.on('changeState', function(e) {
6381 if (typeof e.disabled !== 'undefined') {
6382 data.forEach(function(series,i) {
6383 series.disabled = e.disabled[i];
6386 state.disabled = e.disabled;
6392 //============================================================
6401 //============================================================
6402 // Event Handling/Dispatching (out of chart's scope)
6403 //------------------------------------------------------------
6405 lines.dispatch.on('elementMouseover.tooltip', function(e) {
6406 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6407 dispatch.tooltipShow(e);
6410 lines.dispatch.on('elementMouseout.tooltip', function(e) {
6411 dispatch.tooltipHide(e);
6414 bars.dispatch.on('elementMouseover.tooltip', function(e) {
6415 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6416 dispatch.tooltipShow(e);
6419 bars.dispatch.on('elementMouseout.tooltip', function(e) {
6420 dispatch.tooltipHide(e);
6423 dispatch.on('tooltipHide', function() {
6424 if (tooltips) nv.tooltip.cleanup();
6427 //============================================================
6430 //============================================================
6431 // Expose Public Variables
6432 //------------------------------------------------------------
6434 // expose chart's sub-components
6435 chart.dispatch = dispatch;
6436 chart.legend = legend;
6437 chart.lines = lines;
6439 chart.xAxis = xAxis;
6440 chart.y1Axis = y1Axis;
6441 chart.y2Axis = y2Axis;
6443 // DO NOT DELETE. This is currently overridden below
6444 // until deprecated portions are removed.
6445 chart.state = state;
6447 d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6448 //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6449 //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6451 chart.options = nv.utils.optionsFunc.bind(chart);
6453 chart.x = function(_) {
6454 if (!arguments.length) return getX;
6461 chart.y = function(_) {
6462 if (!arguments.length) return getY;
6469 chart.margin = function(_) {
6470 if (!arguments.length) return margin;
6471 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6472 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6473 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6474 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6478 chart.width = function(_) {
6479 if (!arguments.length) return width;
6484 chart.height = function(_) {
6485 if (!arguments.length) return height;
6490 chart.color = function(_) {
6491 if (!arguments.length) return color;
6492 color = nv.utils.getColor(_);
6493 legend.color(color);
6497 chart.showLegend = function(_) {
6498 if (!arguments.length) return showLegend;
6503 chart.tooltips = function(_) {
6504 if (!arguments.length) return tooltips;
6509 chart.tooltipContent = function(_) {
6510 if (!arguments.length) return tooltip;
6516 chart.state = function(_) {
6517 nv.deprecated('linePlusBarChart.state');
6518 if (!arguments.length) return state;
6522 for (var key in state) {
6523 chart.state[key] = state[key];
6527 chart.defaultState = function(_) {
6528 if (!arguments.length) return defaultState;
6533 chart.noData = function(_) {
6534 if (!arguments.length) return noData;
6539 //============================================================
6543 nv.models.linePlusBarWithFocusChart = function() {
6545 //============================================================
6546 // Public Variables with Default Settings
6547 //------------------------------------------------------------
6549 var lines = nv.models.line()
6550 , lines2 = nv.models.line()
6551 , bars = nv.models.historicalBar()
6552 , bars2 = nv.models.historicalBar()
6553 , xAxis = nv.models.axis()
6554 , x2Axis = nv.models.axis()
6555 , y1Axis = nv.models.axis()
6556 , y2Axis = nv.models.axis()
6557 , y3Axis = nv.models.axis()
6558 , y4Axis = nv.models.axis()
6559 , legend = nv.models.legend()
6560 , brush = d3.svg.brush()
6563 var margin = {top: 30, right: 30, bottom: 30, left: 60}
6564 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6568 , getX = function(d) { return d.x }
6569 , getY = function(d) { return d.y }
6570 , color = nv.utils.defaultColor()
6573 , brushExtent = null
6575 , tooltip = function(key, x, y, e, graph) {
6576 return '<h3>' + key + '</h3>' +
6577 '<p>' + y + ' at ' + x + '</p>';
6585 , noData = "No Data Available."
6586 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
6587 , transitionDuration = 0
6588 , state = nv.utils.state()
6589 , defaultState = null
6619 //============================================================
6622 //============================================================
6623 // Private Variables
6624 //------------------------------------------------------------
6626 var showTooltip = function(e, offsetElement) {
6628 e.pointIndex += Math.ceil(extent[0]);
6630 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6631 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6632 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6633 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6634 content = tooltip(e.series.key, x, y, e, chart);
6636 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6639 var stateGetter = function(data) {
6642 active: data.map(function(d) { return !d.disabled })
6647 var stateSetter = function(data) {
6648 return function(state) {
6649 if (state.active !== undefined)
6650 data.forEach(function(series,i) {
6651 series.disabled = !state.active[i];
6656 //------------------------------------------------------------
6660 function chart(selection) {
6661 selection.each(function(data) {
6662 var container = d3.select(this),
6664 nv.utils.initSVG(container);
6665 var availableWidth = (width || parseInt(container.style('width')) || 960)
6666 - margin.left - margin.right,
6667 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6668 - margin.top - margin.bottom - height2,
6669 availableHeight2 = height2 - margin2.top - margin2.bottom;
6671 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6672 chart.container = this;
6675 .setter(stateSetter(data), chart.update)
6676 .getter(stateGetter(data))
6679 // DEPRECATED set state.disableddisabled
6680 state.disabled = data.map(function(d) { return !!d.disabled });
6682 if (!defaultState) {
6685 for (key in state) {
6686 if (state[key] instanceof Array)
6687 defaultState[key] = state[key].slice(0);
6689 defaultState[key] = state[key];
6693 //------------------------------------------------------------
6694 // Display No Data message if there's nothing to show.
6696 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6697 var noDataText = container.selectAll('.nv-noData').data([noData]);
6699 noDataText.enter().append('text')
6700 .attr('class', 'nvd3 nv-noData')
6701 .attr('dy', '-.7em')
6702 .style('text-anchor', 'middle');
6705 .attr('x', margin.left + availableWidth / 2)
6706 .attr('y', margin.top + availableHeight1 / 2)
6707 .text(function(d) { return d });
6711 container.selectAll('.nv-noData').remove();
6714 //------------------------------------------------------------
6717 //------------------------------------------------------------
6720 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6721 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6724 x2 = x2Axis.scale();
6726 y2 = lines.yScale();
6727 y3 = bars2.yScale();
6728 y4 = lines2.yScale();
6731 .filter(function(d) { return !d.disabled && d.bar })
6733 return d.values.map(function(d,i) {
6734 return { x: getX(d,i), y: getY(d,i) }
6739 .filter(function(d) { return !d.disabled && !d.bar })
6741 return d.values.map(function(d,i) {
6742 return { x: getX(d,i), y: getY(d,i) }
6746 x .range([0, availableWidth]);
6748 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6749 .range([0, availableWidth]);
6752 //------------------------------------------------------------
6755 //------------------------------------------------------------
6756 // Setup containers and skeleton of chart
6758 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6759 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6760 var g = wrap.select('g');
6762 gEnter.append('g').attr('class', 'nv-legendWrap');
6764 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6765 focusEnter.append('g').attr('class', 'nv-x nv-axis');
6766 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6767 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6768 focusEnter.append('g').attr('class', 'nv-barsWrap');
6769 focusEnter.append('g').attr('class', 'nv-linesWrap');
6771 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6772 contextEnter.append('g').attr('class', 'nv-x nv-axis');
6773 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6774 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6775 contextEnter.append('g').attr('class', 'nv-barsWrap');
6776 contextEnter.append('g').attr('class', 'nv-linesWrap');
6777 contextEnter.append('g').attr('class', 'nv-brushBackground');
6778 contextEnter.append('g').attr('class', 'nv-x nv-brush');
6781 //------------------------------------------------------------
6784 //------------------------------------------------------------
6788 legend.width( availableWidth / 2 );
6790 g.select('.nv-legendWrap')
6791 .datum(data.map(function(series) {
6792 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6793 series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6798 if ( margin.top != legend.height()) {
6799 margin.top = legend.height();
6800 availableHeight1 = (height || parseInt(container.style('height')) || 400)
6801 - margin.top - margin.bottom - height2;
6804 g.select('.nv-legendWrap')
6805 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6808 //------------------------------------------------------------
6811 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6814 //------------------------------------------------------------
6815 // Context Components
6818 .width(availableWidth)
6819 .height(availableHeight2)
6820 .color(data.map(function(d,i) {
6821 return d.color || color(d, i);
6822 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6825 .width(availableWidth)
6826 .height(availableHeight2)
6827 .color(data.map(function(d,i) {
6828 return d.color || color(d, i);
6829 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6831 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6832 .datum(dataBars.length ? dataBars : [{values:[]}]);
6834 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6835 .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
6837 g.select('.nv-context')
6838 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6840 bars2Wrap.transition().call(bars2);
6841 lines2Wrap.transition().call(lines2);
6843 //------------------------------------------------------------
6847 //------------------------------------------------------------
6852 .on('brush', onBrush);
6854 if (brushExtent) brush.extent(brushExtent);
6856 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6857 .data([brushExtent || brush.extent()]);
6859 var brushBGenter = brushBG.enter()
6862 brushBGenter.append('rect')
6863 .attr('class', 'left')
6866 .attr('height', availableHeight2);
6868 brushBGenter.append('rect')
6869 .attr('class', 'right')
6872 .attr('height', availableHeight2);
6874 var gBrush = g.select('.nv-x.nv-brush')
6876 gBrush.selectAll('rect')
6878 .attr('height', availableHeight2);
6879 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6881 //------------------------------------------------------------
6883 //------------------------------------------------------------
6884 // Setup Secondary (Context) Axes
6887 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6888 .tickSize(-availableHeight2, 0);
6890 g.select('.nv-context .nv-x.nv-axis')
6891 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
6892 g.select('.nv-context .nv-x.nv-axis').transition()
6898 .ticks( availableHeight2 / 36 )
6899 .tickSize( -availableWidth, 0);
6901 g.select('.nv-context .nv-y1.nv-axis')
6902 .style('opacity', dataBars.length ? 1 : 0)
6903 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6905 g.select('.nv-context .nv-y1.nv-axis').transition()
6911 .ticks( availableHeight2 / 36 )
6912 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6914 g.select('.nv-context .nv-y2.nv-axis')
6915 .style('opacity', dataLines.length ? 1 : 0)
6916 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6918 g.select('.nv-context .nv-y2.nv-axis').transition()
6921 //------------------------------------------------------------
6923 //============================================================
6924 // Event Handling/Dispatching (in chart's scope)
6925 //------------------------------------------------------------
6927 legend.dispatch.on('stateChange', function(newState) {
6928 for (var key in newState)
6929 state[key] = newState[key];
6930 dispatch.stateChange(state);
6934 dispatch.on('tooltipShow', function(e) {
6935 if (tooltips) showTooltip(e, that.parentNode);
6938 // Update chart from a state object passed to event handler
6939 dispatch.on('changeState', function(e) {
6940 if (typeof e.disabled !== 'undefined') {
6941 data.forEach(function(series,i) {
6942 series.disabled = e.disabled[i];
6944 state.disabled = e.disabled;
6949 //============================================================
6952 //============================================================
6954 //------------------------------------------------------------
6956 // Taken from crossfilter (http://square.github.com/crossfilter/)
6957 function resizePath(d) {
6958 var e = +(d == 'e'),
6960 y = availableHeight2 / 3;
6961 return 'M' + (.5 * x) + ',' + y
6962 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6964 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6966 + 'M' + (2.5 * x) + ',' + (y + 8)
6968 + 'M' + (4.5 * x) + ',' + (y + 8)
6969 + 'V' + (2 * y - 8);
6973 function updateBrushBG() {
6974 if (!brush.empty()) brush.extent(brushExtent);
6976 .data([brush.empty() ? x2.domain() : brushExtent])
6977 .each(function(d,i) {
6978 var leftWidth = x2(d[0]) - x2.range()[0],
6979 rightWidth = x2.range()[1] - x2(d[1]);
6980 d3.select(this).select('.left')
6981 .attr('width', leftWidth < 0 ? 0 : leftWidth);
6983 d3.select(this).select('.right')
6984 .attr('x', x2(d[1]))
6985 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6990 function onBrush() {
6991 brushExtent = brush.empty() ? null : brush.extent();
6992 extent = brush.empty() ? x2.domain() : brush.extent();
6995 dispatch.brush({extent: extent, brush: brush});
7000 //------------------------------------------------------------
7001 // Prepare Main (Focus) Bars and Lines
7004 .width(availableWidth)
7005 .height(availableHeight1)
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 }));
7012 .width(availableWidth)
7013 .height(availableHeight1)
7014 .color(data.map(function(d,i) {
7015 return d.color || color(d, i);
7016 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7018 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7019 .datum(!dataBars.length ? [{values:[]}] :
7021 .map(function(d,i) {
7024 values: d.values.filter(function(d,i) {
7025 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7031 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7032 .datum(dataLines[0].disabled ? [{values:[]}] :
7034 .map(function(d,i) {
7037 values: d.values.filter(function(d,i) {
7038 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7044 //------------------------------------------------------------
7047 //------------------------------------------------------------
7048 // Update Main (Focus) X Axis
7050 if (dataBars.length) {
7058 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7059 .tickSize(-availableHeight1, 0);
7061 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7063 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7065 //------------------------------------------------------------
7068 //------------------------------------------------------------
7069 // Update Main (Focus) Bars and Lines
7071 focusBarsWrap.transition().duration(transitionDuration).call(bars);
7072 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7074 //------------------------------------------------------------
7077 //------------------------------------------------------------
7078 // Setup and Update Main (Focus) Y Axes
7080 g.select('.nv-focus .nv-x.nv-axis')
7081 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
7086 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7087 .tickSize(-availableWidth, 0);
7089 g.select('.nv-focus .nv-y1.nv-axis')
7090 .style('opacity', dataBars.length ? 1 : 0);
7095 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7096 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7098 g.select('.nv-focus .nv-y2.nv-axis')
7099 .style('opacity', dataLines.length ? 1 : 0)
7100 .attr('transform', 'translate(' + x.range()[1] + ',0)');
7102 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7104 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7108 //============================================================
7118 //============================================================
7119 // Event Handling/Dispatching (out of chart's scope)
7120 //------------------------------------------------------------
7122 lines.dispatch.on('elementMouseover.tooltip', function(e) {
7123 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7124 dispatch.tooltipShow(e);
7127 lines.dispatch.on('elementMouseout.tooltip', function(e) {
7128 dispatch.tooltipHide(e);
7131 bars.dispatch.on('elementMouseover.tooltip', function(e) {
7132 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7133 dispatch.tooltipShow(e);
7136 bars.dispatch.on('elementMouseout.tooltip', function(e) {
7137 dispatch.tooltipHide(e);
7140 dispatch.on('tooltipHide', function() {
7141 if (tooltips) nv.tooltip.cleanup();
7144 //============================================================
7147 //============================================================
7148 // Expose Public Variables
7149 //------------------------------------------------------------
7151 // expose chart's sub-components
7152 chart.dispatch = dispatch;
7153 chart.legend = legend;
7154 chart.lines = lines;
7155 chart.lines2 = lines2;
7157 chart.bars2 = bars2;
7158 chart.xAxis = xAxis;
7159 chart.x2Axis = x2Axis;
7160 chart.y1Axis = y1Axis;
7161 chart.y2Axis = y2Axis;
7162 chart.y3Axis = y3Axis;
7163 chart.y4Axis = y4Axis;
7165 // DO NOT DELETE. This is currently overridden below
7166 // until deprecated portions are removed.
7167 chart.state = state;
7169 d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7170 //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7171 //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7173 chart.options = nv.utils.optionsFunc.bind(chart);
7175 chart.x = function(_) {
7176 if (!arguments.length) return getX;
7183 chart.y = function(_) {
7184 if (!arguments.length) return getY;
7191 chart.margin = function(_) {
7192 if (!arguments.length) return margin;
7193 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7194 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7195 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7196 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7200 chart.width = function(_) {
7201 if (!arguments.length) return width;
7206 chart.height = function(_) {
7207 if (!arguments.length) return height;
7212 chart.color = function(_) {
7213 if (!arguments.length) return color;
7214 color = nv.utils.getColor(_);
7215 legend.color(color);
7219 chart.showLegend = function(_) {
7220 if (!arguments.length) return showLegend;
7225 chart.tooltips = function(_) {
7226 if (!arguments.length) return tooltips;
7231 chart.tooltipContent = function(_) {
7232 if (!arguments.length) return tooltip;
7238 chart.state = function(_) {
7239 nv.deprecated('linePlusBarWithFocusChart.state');
7240 if (!arguments.length) return state;
7244 for (var key in state) {
7245 chart.state[key] = state[key];
7249 chart.noData = function(_) {
7250 if (!arguments.length) return noData;
7255 chart.brushExtent = function(_) {
7256 if (!arguments.length) return brushExtent;
7262 //============================================================
7267 nv.models.lineWithFocusChart = function() {
7269 //============================================================
7270 // Public Variables with Default Settings
7271 //------------------------------------------------------------
7273 var lines = nv.models.line()
7274 , lines2 = nv.models.line()
7275 , xAxis = nv.models.axis()
7276 , yAxis = nv.models.axis()
7277 , x2Axis = nv.models.axis()
7278 , y2Axis = nv.models.axis()
7279 , legend = nv.models.legend()
7280 , brush = d3.svg.brush()
7283 var margin = {top: 30, right: 30, bottom: 30, left: 60}
7284 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
7285 , color = nv.utils.defaultColor()
7294 , brushExtent = null
7296 , tooltip = function(key, x, y, e, graph) {
7297 return '<h3>' + key + '</h3>' +
7298 '<p>' + y + ' at ' + x + '</p>'
7300 , noData = "No Data Available."
7301 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
7302 , transitionDuration = 250
7303 , state = nv.utils.state()
7304 , defaultState = null
7327 //============================================================
7330 //============================================================
7331 // Private Variables
7332 //------------------------------------------------------------
7334 var showTooltip = function(e, offsetElement) {
7335 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7336 top = e.pos[1] + ( offsetElement.offsetTop || 0),
7337 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
7338 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
7339 content = tooltip(e.series.key, x, y, e, chart);
7341 nv.tooltip.show([left, top], content, null, null, offsetElement);
7344 var stateGetter = function(data) {
7347 active: data.map(function(d) { return !d.disabled })
7352 var stateSetter = function(data) {
7353 return function(state) {
7354 if (state.active !== undefined)
7355 data.forEach(function(series,i) {
7356 series.disabled = !state.active[i];
7361 //============================================================
7364 function chart(selection) {
7365 selection.each(function(data) {
7366 var container = d3.select(this),
7368 nv.utils.initSVG(container);
7369 var availableWidth = (width || parseInt(container.style('width')) || 960)
7370 - margin.left - margin.right,
7371 availableHeight1 = (height || parseInt(container.style('height')) || 400)
7372 - margin.top - margin.bottom - height2,
7373 availableHeight2 = height2 - margin2.top - margin2.bottom;
7375 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7376 chart.container = this;
7379 .setter(stateSetter(data), chart.update)
7380 .getter(stateGetter(data))
7383 // DEPRECATED set state.disableddisabled
7384 state.disabled = data.map(function(d) { return !!d.disabled });
7386 if (!defaultState) {
7389 for (key in state) {
7390 if (state[key] instanceof Array)
7391 defaultState[key] = state[key].slice(0);
7393 defaultState[key] = state[key];
7397 //------------------------------------------------------------
7398 // Display No Data message if there's nothing to show.
7400 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7401 var noDataText = container.selectAll('.nv-noData').data([noData]);
7403 noDataText.enter().append('text')
7404 .attr('class', 'nvd3 nv-noData')
7405 .attr('dy', '-.7em')
7406 .style('text-anchor', 'middle');
7409 .attr('x', margin.left + availableWidth / 2)
7410 .attr('y', margin.top + availableHeight1 / 2)
7411 .text(function(d) { return d });
7415 container.selectAll('.nv-noData').remove();
7418 //------------------------------------------------------------
7421 //------------------------------------------------------------
7426 x2 = lines2.xScale();
7427 y2 = lines2.yScale();
7429 //------------------------------------------------------------
7432 //------------------------------------------------------------
7433 // Setup containers and skeleton of chart
7435 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
7436 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
7437 var g = wrap.select('g');
7439 gEnter.append('g').attr('class', 'nv-legendWrap');
7441 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
7442 focusEnter.append('g').attr('class', 'nv-x nv-axis');
7443 focusEnter.append('g').attr('class', 'nv-y nv-axis');
7444 focusEnter.append('g').attr('class', 'nv-linesWrap');
7446 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
7447 contextEnter.append('g').attr('class', 'nv-x nv-axis');
7448 contextEnter.append('g').attr('class', 'nv-y nv-axis');
7449 contextEnter.append('g').attr('class', 'nv-linesWrap');
7450 contextEnter.append('g').attr('class', 'nv-brushBackground');
7451 contextEnter.append('g').attr('class', 'nv-x nv-brush');
7453 //------------------------------------------------------------
7456 //------------------------------------------------------------
7460 legend.width(availableWidth);
7462 g.select('.nv-legendWrap')
7466 if ( margin.top != legend.height()) {
7467 margin.top = legend.height();
7468 availableHeight1 = (height || parseInt(container.style('height')) || 400)
7469 - margin.top - margin.bottom - height2;
7472 g.select('.nv-legendWrap')
7473 .attr('transform', 'translate(0,' + (-margin.top) +')')
7476 //------------------------------------------------------------
7479 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7482 //------------------------------------------------------------
7483 // Main Chart Component(s)
7486 .width(availableWidth)
7487 .height(availableHeight1)
7490 .map(function(d,i) {
7491 return d.color || color(d, i);
7493 .filter(function(d,i) {
7494 return !data[i].disabled;
7499 .defined(lines.defined())
7500 .width(availableWidth)
7501 .height(availableHeight2)
7504 .map(function(d,i) {
7505 return d.color || color(d, i);
7507 .filter(function(d,i) {
7508 return !data[i].disabled;
7512 g.select('.nv-context')
7513 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7515 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
7516 .datum(data.filter(function(d) { return !d.disabled }))
7518 d3.transition(contextLinesWrap).call(lines2);
7520 //------------------------------------------------------------
7524 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7525 .datum(data.filter(function(d) { return !d.disabled }))
7527 d3.transition(focusLinesWrap).call(lines);
7531 //------------------------------------------------------------
7532 // Setup Main (Focus) Axes
7536 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7537 .tickSize(-availableHeight1, 0);
7541 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7542 .tickSize( -availableWidth, 0);
7544 g.select('.nv-focus .nv-x.nv-axis')
7545 .attr('transform', 'translate(0,' + availableHeight1 + ')');
7547 //------------------------------------------------------------
7550 //------------------------------------------------------------
7555 .on('brush', function() {
7556 //When brushing, turn off transitions because chart needs to change immediately.
7557 var oldTransition = chart.transitionDuration();
7558 chart.transitionDuration(0);
7560 chart.transitionDuration(oldTransition);
7563 if (brushExtent) brush.extent(brushExtent);
7565 var brushBG = g.select('.nv-brushBackground').selectAll('g')
7566 .data([brushExtent || brush.extent()])
7568 var brushBGenter = brushBG.enter()
7571 brushBGenter.append('rect')
7572 .attr('class', 'left')
7575 .attr('height', availableHeight2);
7577 brushBGenter.append('rect')
7578 .attr('class', 'right')
7581 .attr('height', availableHeight2);
7583 var gBrush = g.select('.nv-x.nv-brush')
7585 gBrush.selectAll('rect')
7587 .attr('height', availableHeight2);
7588 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7592 //------------------------------------------------------------
7595 //------------------------------------------------------------
7596 // Setup Secondary (Context) Axes
7600 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7601 .tickSize(-availableHeight2, 0);
7603 g.select('.nv-context .nv-x.nv-axis')
7604 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7605 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
7610 .ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7611 .tickSize( -availableWidth, 0);
7613 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7616 g.select('.nv-context .nv-x.nv-axis')
7617 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7619 //------------------------------------------------------------
7622 //============================================================
7623 // Event Handling/Dispatching (in chart's scope)
7624 //------------------------------------------------------------
7626 legend.dispatch.on('stateChange', function(newState) {
7627 for (var key in newState)
7628 state[key] = newState[key];
7629 dispatch.stateChange(state);
7633 dispatch.on('tooltipShow', function(e) {
7634 if (tooltips) showTooltip(e, that.parentNode);
7637 dispatch.on('changeState', function(e) {
7638 if (typeof e.disabled !== 'undefined') {
7639 data.forEach(function(series,i) {
7640 series.disabled = e.disabled[i];
7646 //============================================================
7649 //============================================================
7651 //------------------------------------------------------------
7653 // Taken from crossfilter (http://square.github.com/crossfilter/)
7654 function resizePath(d) {
7655 var e = +(d == 'e'),
7657 y = availableHeight2 / 3;
7658 return 'M' + (.5 * x) + ',' + y
7659 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7661 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7663 + 'M' + (2.5 * x) + ',' + (y + 8)
7665 + 'M' + (4.5 * x) + ',' + (y + 8)
7666 + 'V' + (2 * y - 8);
7670 function updateBrushBG() {
7671 if (!brush.empty()) brush.extent(brushExtent);
7673 .data([brush.empty() ? x2.domain() : brushExtent])
7674 .each(function(d,i) {
7675 var leftWidth = x2(d[0]) - x.range()[0],
7676 rightWidth = x.range()[1] - x2(d[1]);
7677 d3.select(this).select('.left')
7678 .attr('width', leftWidth < 0 ? 0 : leftWidth);
7680 d3.select(this).select('.right')
7681 .attr('x', x2(d[1]))
7682 .attr('width', rightWidth < 0 ? 0 : rightWidth);
7687 function onBrush() {
7688 brushExtent = brush.empty() ? null : brush.extent();
7689 var extent = brush.empty() ? x2.domain() : brush.extent();
7691 //The brush extent cannot be less than one. If it is, don't update the line chart.
7692 if (Math.abs(extent[0] - extent[1]) <= 1) {
7696 dispatch.brush({extent: extent, brush: brush});
7701 // Update Main (Focus)
7702 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7705 .filter(function(d) { return !d.disabled })
7706 .map(function(d,i) {
7710 values: d.values.filter(function(d,i) {
7711 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7716 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7719 // Update Main (Focus) Axes
7720 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
7722 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
7726 //============================================================
7735 //============================================================
7736 // Event Handling/Dispatching (out of chart's scope)
7737 //------------------------------------------------------------
7739 lines.dispatch.on('elementMouseover.tooltip', function(e) {
7740 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7741 dispatch.tooltipShow(e);
7744 lines.dispatch.on('elementMouseout.tooltip', function(e) {
7745 dispatch.tooltipHide(e);
7748 dispatch.on('tooltipHide', function() {
7749 if (tooltips) nv.tooltip.cleanup();
7752 //============================================================
7755 //============================================================
7756 // Expose Public Variables
7757 //------------------------------------------------------------
7759 // expose chart's sub-components
7760 chart.dispatch = dispatch;
7761 chart.legend = legend;
7762 chart.lines = lines;
7763 chart.lines2 = lines2;
7764 chart.xAxis = xAxis;
7765 chart.yAxis = yAxis;
7766 chart.x2Axis = x2Axis;
7767 chart.y2Axis = y2Axis;
7769 // DO NOT DELETE. This is currently overridden below
7770 // until deprecated portions are removed.
7771 chart.state = state;
7773 d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7775 chart.options = nv.utils.optionsFunc.bind(chart);
7777 chart.x = function(_) {
7778 if (!arguments.length) return lines.x;
7784 chart.y = function(_) {
7785 if (!arguments.length) return lines.y;
7791 chart.margin = function(_) {
7792 if (!arguments.length) return margin;
7793 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7794 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7795 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7796 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7800 chart.margin2 = function(_) {
7801 if (!arguments.length) return margin2;
7806 chart.width = function(_) {
7807 if (!arguments.length) return width;
7812 chart.height = function(_) {
7813 if (!arguments.length) return height;
7818 chart.height2 = function(_) {
7819 if (!arguments.length) return height2;
7824 chart.color = function(_) {
7825 if (!arguments.length) return color;
7826 color =nv.utils.getColor(_);
7827 legend.color(color);
7831 chart.showLegend = function(_) {
7832 if (!arguments.length) return showLegend;
7837 chart.tooltips = function(_) {
7838 if (!arguments.length) return tooltips;
7843 chart.tooltipContent = function(_) {
7844 if (!arguments.length) return tooltip;
7850 chart.state = function(_) {
7851 nv.deprecated('lineWithFocusChart.state');
7852 if (!arguments.length) return state;
7856 for (var key in state) {
7857 chart.state[key] = state[key];
7861 chart.defaultState = function(_) {
7862 if (!arguments.length) return defaultState;
7867 chart.interpolate = function(_) {
7868 if (!arguments.length) return lines.interpolate();
7869 lines.interpolate(_);
7870 lines2.interpolate(_);
7874 chart.noData = function(_) {
7875 if (!arguments.length) return noData;
7880 // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
7881 chart.xTickFormat = function(_) {
7882 if (!arguments.length) return xAxis.tickFormat();
7883 xAxis.tickFormat(_);
7884 x2Axis.tickFormat(_);
7888 chart.yTickFormat = function(_) {
7889 if (!arguments.length) return yAxis.tickFormat();
7890 yAxis.tickFormat(_);
7891 y2Axis.tickFormat(_);
7895 chart.brushExtent = function(_) {
7896 if (!arguments.length) return brushExtent;
7901 chart.transitionDuration = function(_) {
7902 if (!arguments.length) return transitionDuration;
7903 transitionDuration = _;
7907 //============================================================
7913 nv.models.multiBar = function() {
7915 //============================================================
7916 // Public Variables with Default Settings
7917 //------------------------------------------------------------
7919 var margin = {top: 0, right: 0, bottom: 0, left: 0}
7922 , x = d3.scale.ordinal()
7923 , y = d3.scale.linear()
7924 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7925 , getX = function(d) { return d.x }
7926 , getY = function(d) { return d.y }
7927 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7930 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7931 , color = nv.utils.defaultColor()
7933 , barColor = null // adding the ability to set the color for each rather than the whole group
7934 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7940 , groupSpacing = 0.1
7941 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
7944 //============================================================
7947 //============================================================
7948 // Private Variables
7949 //------------------------------------------------------------
7951 var x0, y0 //used to store previous scales
7952 , renderWatch = nv.utils.renderWatch(dispatch, duration)
7956 //============================================================
7960 function chart(selection) {
7961 renderWatch.reset();
7962 selection.each(function(data) {
7963 var availableWidth = width - margin.left - margin.right,
7964 availableHeight = height - margin.top - margin.bottom,
7965 container = d3.select(this);
7966 nv.utils.initSVG(container);
7968 // This function defines the requirements for render complete
7969 var endFn = function(d, i) {
7970 if (d.series === data.length - 1 && i === data[0].values.length - 1)
7976 if(hideable && data.length) hideable = [{
7977 values: data[0].values.map(function(d) {
7987 data = d3.layout.stack()
7988 .offset(stackOffset)
7989 .values(function(d){ return d.values })
7991 (!data.length && hideable ? hideable : data);
7994 //add series index to each data point for reference
7995 data.forEach(function(series, i) {
7996 series.values.forEach(function(point) {
8002 //------------------------------------------------------------
8003 // HACK for negative value stacking
8005 data[0].values.map(function(d,i) {
8006 var posBase = 0, negBase = 0;
8007 data.map(function(d) {
8009 f.size = Math.abs(f.y);
8012 negBase = negBase - f.size;
8015 f.y1 = f.size + posBase;
8016 posBase = posBase + f.size;
8021 //------------------------------------------------------------
8024 // remap and flatten the data for use in calculating the scales' domains
8025 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8026 data.map(function(d) {
8027 return d.values.map(function(d,i) {
8028 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8032 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8033 .rangeBands(xRange || [0, availableWidth], groupSpacing);
8035 //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
8036 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)))
8037 .range(yRange || [availableHeight, 0]);
8039 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
8040 if (x.domain()[0] === x.domain()[1])
8042 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8045 if (y.domain()[0] === y.domain()[1])
8047 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8054 //------------------------------------------------------------
8057 //------------------------------------------------------------
8058 // Setup containers and skeleton of chart
8060 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
8061 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
8062 var defsEnter = wrapEnter.append('defs');
8063 var gEnter = wrapEnter.append('g');
8064 var g = wrap.select('g')
8066 gEnter.append('g').attr('class', 'nv-groups');
8068 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8070 //------------------------------------------------------------
8074 defsEnter.append('clipPath')
8075 .attr('id', 'nv-edge-clip-' + id)
8077 wrap.select('#nv-edge-clip-' + id + ' rect')
8078 .attr('width', availableWidth)
8079 .attr('height', availableHeight);
8081 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
8083 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8084 .data(function(d) { return d }, function(d,i) { return i });
8085 groups.enter().append('g')
8086 .style('stroke-opacity', 1e-6)
8087 .style('fill-opacity', 1e-6);
8089 var exitTransition = renderWatch
8090 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(250, duration))
8091 .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
8094 if (exitTransition.delay)
8095 exitTransition.delay(function(d,i) {
8096 return i * duration / data[0].values.length;
8099 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8100 .classed('hover', function(d) { return d.hover })
8101 .style('fill', function(d,i){ return color(d, i) })
8102 .style('stroke', function(d,i){ return color(d, i) });
8104 .style('stroke-opacity', 1)
8105 .style('fill-opacity', 0.75);
8108 var bars = groups.selectAll('rect.nv-bar')
8109 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
8111 bars.exit().remove();
8114 var barsEnter = bars.enter().append('rect')
8115 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8116 .attr('x', function(d,i,j) {
8117 return stacked ? 0 : (j * x.rangeBand() / data.length )
8119 .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
8121 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
8122 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
8125 .style('fill', function(d,i,j){ return color(d, j, i); })
8126 .style('stroke', function(d,i,j){ return color(d, j, i); })
8127 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8128 d3.select(this).classed('hover', true);
8129 dispatch.elementMouseover({
8132 series: data[d.series],
8133 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
8135 seriesIndex: d.series,
8139 .on('mouseout', function(d,i) {
8140 d3.select(this).classed('hover', false);
8141 dispatch.elementMouseout({
8144 series: data[d.series],
8146 seriesIndex: d.series,
8150 .on('click', function(d,i) {
8151 dispatch.elementClick({
8154 series: data[d.series],
8155 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
8157 seriesIndex: d.series,
8160 d3.event.stopPropagation();
8162 .on('dblclick', function(d,i) {
8163 dispatch.elementDblClick({
8166 series: data[d.series],
8167 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
8169 seriesIndex: d.series,
8172 d3.event.stopPropagation();
8175 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8176 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
8179 if (!disabled) disabled = data.map(function() { return true });
8181 .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(); })
8182 .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(); });
8186 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
8187 .delay(function(d,i) {
8188 return i * duration / data[0].values.length;
8192 .attr('y', function(d,i) {
8193 return y((stacked ? d.y1 : 0));
8195 .attr('height', function(d,i) {
8196 return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
8198 .attr('x', function(d,i) {
8199 return stacked ? 0 : (d.series * x.rangeBand() / data.length )
8201 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
8204 .attr('x', function(d,i) {
8205 return d.series * x.rangeBand() / data.length
8207 .attr('width', x.rangeBand() / data.length)
8208 .attr('y', function(d,i) {
8209 return getY(d,i) < 0 ?
8211 y(0) - y(getY(d,i)) < 1 ?
8215 .attr('height', function(d,i) {
8216 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
8219 //store old scales for use in transitions on update
8225 renderWatch.renderEnd('multibar immediate');
8230 //============================================================
8231 // Expose Public Variables
8232 //------------------------------------------------------------
8234 // As a getter, returns a new instance of d3 dispatch and sets appropriate vars.
8235 // As a setter, sets dispatch.
8236 // Useful when same chart instance is used to render several data models.
8237 // Since dispatch is instance-specific, it cannot be contained inside chart model.
8239 chart.dispatch = dispatch;
8241 chart.x = function(_) {
8242 if (!arguments.length) return getX;
8247 chart.y = function(_) {
8248 if (!arguments.length) return getY;
8253 chart.margin = function(_) {
8254 if (!arguments.length) return margin;
8255 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8256 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8257 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8258 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8262 chart.width = function(_) {
8263 if (!arguments.length) return width;
8268 chart.height = function(_) {
8269 if (!arguments.length) return height;
8274 chart.xScale = function(_) {
8275 if (!arguments.length) return x;
8280 chart.yScale = function(_) {
8281 if (!arguments.length) return y;
8286 chart.xDomain = function(_) {
8287 if (!arguments.length) return xDomain;
8292 chart.yDomain = function(_) {
8293 if (!arguments.length) return yDomain;
8298 chart.xRange = function(_) {
8299 if (!arguments.length) return xRange;
8304 chart.yRange = function(_) {
8305 if (!arguments.length) return yRange;
8310 chart.forceY = function(_) {
8311 if (!arguments.length) return forceY;
8316 chart.stacked = function(_) {
8317 if (!arguments.length) return stacked;
8322 chart.stackOffset = function(_) {
8323 if (!arguments.length) return stackOffset;
8328 chart.clipEdge = function(_) {
8329 if (!arguments.length) return clipEdge;
8334 chart.color = function(_) {
8335 if (!arguments.length) return color;
8336 color = nv.utils.getColor(_);
8340 chart.barColor = function(_) {
8341 if (!arguments.length) return barColor;
8342 barColor = nv.utils.getColor(_);
8346 chart.disabled = function(_) {
8347 if (!arguments.length) return disabled;
8352 chart.id = function(_) {
8353 if (!arguments.length) return id;
8358 chart.hideable = function(_) {
8359 if (!arguments.length) return hideable;
8364 chart.groupSpacing = function(_) {
8365 if (!arguments.length) return groupSpacing;
8370 chart.duration = function(_) {
8371 if (!arguments.length) return duration;
8373 renderWatch.reset(duration);
8379 //============================================================
8380 // Deprecated Methods
8381 //------------------------------------------------------------
8383 chart.delay = function(_) {
8384 nv.deprecated('multiBar.delay');
8385 return chart.duration(_);
8388 chart.options = nv.utils.optionsFunc.bind(chart);
8390 //============================================================
8395 nv.models.multiBarChart = function() {
8397 //============================================================
8398 // Public Variables with Default Settings
8399 //------------------------------------------------------------
8401 var multibar = nv.models.multiBar()
8402 , xAxis = nv.models.axis()
8403 , yAxis = nv.models.axis()
8404 , legend = nv.models.legend()
8405 , controls = nv.models.legend()
8408 var margin = {top: 30, right: 20, bottom: 50, left: 60}
8411 , color = nv.utils.defaultColor()
8412 , showControls = true
8416 , rightAlignYAxis = false
8417 , reduceXTicks = true // if false a tick will show for every data point
8418 , staggerLabels = false
8421 , tooltip = function(key, x, y, e, graph) {
8422 return '<h3>' + key + '</h3>' +
8423 '<p>' + y + ' on ' + x + '</p>'
8425 , x //can be accessed via chart.xScale()
8426 , y //can be accessed via chart.yScale()
8427 , state = nv.utils.state()
8428 , defaultState = null
8429 , noData = "No Data Available."
8430 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
8431 , controlWidth = function() { return showControls ? 180 : 0 }
8435 state.stacked = false // DEPRECATED Maintained for backward compatibility
8443 .highlightZero(true)
8445 .tickFormat(function(d) { return d })
8448 .orient((rightAlignYAxis) ? 'right' : 'left')
8449 .tickFormat(d3.format(',.1f'))
8452 controls.updateState(false);
8453 //============================================================
8456 //============================================================
8457 // Private Variables
8458 //------------------------------------------------------------
8459 var renderWatch = nv.utils.renderWatch(dispatch);
8460 var stacked = false;
8462 var showTooltip = function(e, offsetElement) {
8463 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8464 top = e.pos[1] + ( offsetElement.offsetTop || 0),
8465 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8466 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8467 content = tooltip(e.series.key, x, y, e, chart);
8469 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
8472 var stateGetter = function(data) {
8475 active: data.map(function(d) { return !d.disabled }),
8481 var stateSetter = function(data) {
8482 return function(state) {
8483 if (state.stacked !== undefined)
8484 stacked = state.stacked;
8485 if (state.active !== undefined)
8486 data.forEach(function(series,i) {
8487 series.disabled = !state.active[i];
8492 //============================================================
8495 function chart(selection) {
8496 renderWatch.reset();
8497 renderWatch.models(multibar);
8498 if (showXAxis) renderWatch.models(xAxis);
8499 if (showYAxis) renderWatch.models(yAxis);
8501 selection.each(function(data) {
8502 var container = d3.select(this),
8504 nv.utils.initSVG(container);
8505 var availableWidth = (width || parseInt(container.style('width')) || 960)
8506 - margin.left - margin.right,
8507 availableHeight = (height || parseInt(container.style('height')) || 400)
8508 - margin.top - margin.bottom;
8510 chart.update = function() {
8512 container.call(chart);
8514 container.transition()
8518 chart.container = this;
8521 .setter(stateSetter(data), chart.update)
8522 .getter(stateGetter(data))
8525 // DEPRECATED set state.disableddisabled
8526 state.disabled = data.map(function(d) { return !!d.disabled });
8528 if (!defaultState) {
8531 for (key in state) {
8532 if (state[key] instanceof Array)
8533 defaultState[key] = state[key].slice(0);
8535 defaultState[key] = state[key];
8538 //------------------------------------------------------------
8539 // Display noData message if there's nothing to show.
8541 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8542 var noDataText = container.selectAll('.nv-noData').data([noData]);
8544 noDataText.enter().append('text')
8545 .attr('class', 'nvd3 nv-noData')
8546 .attr('dy', '-.7em')
8547 .style('text-anchor', 'middle');
8550 .attr('x', margin.left + availableWidth / 2)
8551 .attr('y', margin.top + availableHeight / 2)
8552 .text(function(d) { return d });
8556 container.selectAll('.nv-noData').remove();
8559 //------------------------------------------------------------
8562 //------------------------------------------------------------
8565 x = multibar.xScale();
8566 y = multibar.yScale();
8568 //------------------------------------------------------------
8571 //------------------------------------------------------------
8572 // Setup containers and skeleton of chart
8574 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8575 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8576 var g = wrap.select('g');
8578 gEnter.append('g').attr('class', 'nv-x nv-axis');
8579 gEnter.append('g').attr('class', 'nv-y nv-axis');
8580 gEnter.append('g').attr('class', 'nv-barsWrap');
8581 gEnter.append('g').attr('class', 'nv-legendWrap');
8582 gEnter.append('g').attr('class', 'nv-controlsWrap');
8584 //------------------------------------------------------------
8587 //------------------------------------------------------------
8591 legend.width(availableWidth - controlWidth());
8593 if (multibar.barColor())
8594 data.forEach(function(series,i) {
8595 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8598 g.select('.nv-legendWrap')
8602 if ( margin.top != legend.height()) {
8603 margin.top = legend.height();
8604 availableHeight = (height || parseInt(container.style('height')) || 400)
8605 - margin.top - margin.bottom;
8608 g.select('.nv-legendWrap')
8609 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8612 //------------------------------------------------------------
8615 //------------------------------------------------------------
8619 var controlsData = [
8620 { key: 'Grouped', disabled: multibar.stacked() },
8621 { key: 'Stacked', disabled: !multibar.stacked() }
8624 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8625 g.select('.nv-controlsWrap')
8626 .datum(controlsData)
8627 .attr('transform', 'translate(0,' + (-margin.top) +')')
8631 //------------------------------------------------------------
8634 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8636 if (rightAlignYAxis) {
8637 g.select(".nv-y.nv-axis")
8638 .attr("transform", "translate(" + availableWidth + ",0)");
8641 //------------------------------------------------------------
8642 // Main Chart Component(s)
8645 .disabled(data.map(function(series) { return series.disabled }))
8646 .width(availableWidth)
8647 .height(availableHeight)
8648 .color(data.map(function(d,i) {
8649 return d.color || color(d, i);
8650 }).filter(function(d,i) { return !data[i].disabled }))
8653 var barsWrap = g.select('.nv-barsWrap')
8654 .datum(data.filter(function(d) { return !d.disabled }))
8656 barsWrap.call(multibar);
8658 //------------------------------------------------------------
8661 //------------------------------------------------------------
8667 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8668 .tickSize(-availableHeight, 0);
8670 g.select('.nv-x.nv-axis')
8671 .attr('transform', 'translate(0,' + y.range()[0] + ')');
8672 g.select('.nv-x.nv-axis')
8675 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8678 .selectAll('line, text')
8679 .style('opacity', 1)
8681 if (staggerLabels) {
8682 var getTranslate = function(x,y) {
8683 return "translate(" + x + "," + y + ")";
8686 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8690 .attr('transform', function(d,i,j) {
8691 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8694 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8695 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8696 .attr("transform", function(d,i) {
8697 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8703 .filter(function(d,i) {
8704 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8706 .selectAll('text, line')
8707 .style('opacity', 0);
8711 .selectAll('.tick text')
8712 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8713 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8715 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8716 .style('opacity', 1);
8723 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8724 .tickSize( -availableWidth, 0);
8726 g.select('.nv-y.nv-axis')
8731 //------------------------------------------------------------
8735 //============================================================
8736 // Event Handling/Dispatching (in chart's scope)
8737 //------------------------------------------------------------
8739 legend.dispatch.on('stateChange', function(newState) {
8740 for (var key in newState)
8741 state[key] = newState[key];
8742 dispatch.stateChange(state);
8746 controls.dispatch.on('legendClick', function(d,i) {
8747 if (!d.disabled) return;
8748 controlsData = controlsData.map(function(s) {
8756 multibar.stacked(false);
8759 multibar.stacked(true);
8763 state.stacked = multibar.stacked();
8764 dispatch.stateChange(state);
8769 dispatch.on('tooltipShow', function(e) {
8770 if (tooltips) showTooltip(e, that.parentNode)
8773 // Update chart from a state object passed to event handler
8774 dispatch.on('changeState', function(e) {
8776 if (typeof e.disabled !== 'undefined') {
8777 data.forEach(function(series,i) {
8778 series.disabled = e.disabled[i];
8781 state.disabled = e.disabled;
8784 if (typeof e.stacked !== 'undefined') {
8785 multibar.stacked(e.stacked);
8786 state.stacked = e.stacked;
8787 stacked = e.stacked;
8793 //============================================================
8798 renderWatch.renderEnd('multibarchart immediate');
8804 //============================================================
8805 // Event Handling/Dispatching (out of chart's scope)
8806 //------------------------------------------------------------
8808 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8809 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8810 dispatch.tooltipShow(e);
8813 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8814 dispatch.tooltipHide(e);
8816 dispatch.on('tooltipHide', function() {
8817 if (tooltips) nv.tooltip.cleanup();
8821 //============================================================
8824 //============================================================
8825 // Expose Public Variables
8826 //------------------------------------------------------------
8828 // expose chart's sub-components
8829 chart.dispatch = dispatch;
8830 chart.multibar = multibar;
8831 chart.legend = legend;
8832 chart.xAxis = xAxis;
8833 chart.yAxis = yAxis;
8834 // DO NOT DELETE. This is currently overridden below
8835 // until deprecated portions are removed.
8836 chart.state = state;
8838 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8839 'id', 'stacked', 'stackOffset', 'delay', 'barColor','groupSpacing');
8841 chart.options = nv.utils.optionsFunc.bind(chart);
8843 chart.margin = function(_) {
8844 if (!arguments.length) return margin;
8845 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8846 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8847 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8848 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8852 chart.width = function(_) {
8853 if (!arguments.length) return width;
8858 chart.height = function(_) {
8860 if (!arguments.length) return height;
8865 chart.color = function(_) {
8867 if (!arguments.length) return color;
8868 color = nv.utils.getColor(_);
8869 legend.color(color);
8873 chart.showControls = function(_) {
8874 if (!arguments.length) return showControls;
8879 chart.showLegend = function(_) {
8880 if (!arguments.length) return showLegend;
8885 chart.showXAxis = function(_) {
8886 if (!arguments.length) return showXAxis;
8891 chart.showYAxis = function(_) {
8892 if (!arguments.length) return showYAxis;
8897 chart.rightAlignYAxis = function(_) {
8898 if(!arguments.length) return rightAlignYAxis;
8899 rightAlignYAxis = _;
8900 yAxis.orient( (_) ? 'right' : 'left');
8904 chart.reduceXTicks= function(_) {
8905 if (!arguments.length) return reduceXTicks;
8910 chart.rotateLabels = function(_) {
8911 if (!arguments.length) return rotateLabels;
8916 chart.staggerLabels = function(_) {
8917 if (!arguments.length) return staggerLabels;
8922 chart.tooltip = function(_) {
8923 if (!arguments.length) return tooltip;
8928 chart.tooltips = function(_) {
8929 if (!arguments.length) return tooltips;
8934 chart.tooltipContent = function(_) {
8935 if (!arguments.length) return tooltip;
8941 chart.state = function(_) {
8942 nv.deprecated('multiBarChart.state');
8943 if (!arguments.length) return state;
8947 for (var key in state) {
8948 chart.state[key] = state[key];
8952 chart.defaultState = function(_) {
8953 if (!arguments.length) return defaultState;
8958 chart.noData = function(_) {
8959 if (!arguments.length) return noData;
8964 chart.transitionDuration = function(_) {
8965 nv.deprecated('multiBarChart.transitionDuration');
8966 return chart.duration(_);
8969 chart.duration = function(_) {
8970 if (!arguments.length) return duration;
8972 multibar.duration(duration);
8973 xAxis.duration(duration);
8974 yAxis.duration(duration);
8975 renderWatch.reset(duration);
8979 //============================================================
8985 nv.models.multiBarHorizontal = function() {
8987 //============================================================
8988 // Public Variables with Default Settings
8989 //------------------------------------------------------------
8991 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8994 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8995 , x = d3.scale.ordinal()
8996 , y = d3.scale.linear()
8997 , getX = function(d) { return d.x }
8998 , getY = function(d) { return d.y }
8999 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
9000 , color = nv.utils.defaultColor()
9001 , barColor = null // adding the ability to set the color for each rather than the whole group
9002 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
9004 , showValues = false
9005 , showBarLabels = false
9007 , valueFormat = d3.format(',.2f')
9014 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
9017 //============================================================
9020 //============================================================
9021 // Private Variables
9022 //------------------------------------------------------------
9024 var x0, y0; //used to store previous scales
9025 var renderWatch = nv.utils.renderWatch(dispatch, duration);
9028 //============================================================
9031 function chart(selection) {
9032 renderWatch.reset();
9033 selection.each(function(data) {
9034 var availableWidth = width - margin.left - margin.right,
9035 availableHeight = height - margin.top - margin.bottom,
9036 container = d3.select(this);
9037 nv.utils.initSVG(container);
9040 data = d3.layout.stack()
9042 .values(function(d){ return d.values })
9047 //add series index to each data point for reference
9048 data.forEach(function(series, i) {
9049 series.values.forEach(function(point) {
9056 //------------------------------------------------------------
9057 // HACK for negative value stacking
9059 data[0].values.map(function(d,i) {
9060 var posBase = 0, negBase = 0;
9061 data.map(function(d) {
9063 f.size = Math.abs(f.y);
9065 f.y1 = negBase - f.size;
9066 negBase = negBase - f.size;
9070 posBase = posBase + f.size;
9077 //------------------------------------------------------------
9080 // remap and flatten the data for use in calculating the scales' domains
9081 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
9082 data.map(function(d) {
9083 return d.values.map(function(d,i) {
9084 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
9088 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
9089 .rangeBands(xRange || [0, availableHeight], .1);
9091 //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
9092 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)))
9094 if (showValues && !stacked)
9095 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
9097 y.range(yRange || [0, availableWidth]);
9100 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
9102 //------------------------------------------------------------
9105 //------------------------------------------------------------
9106 // Setup containers and skeleton of chart
9108 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
9109 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
9110 var defsEnter = wrapEnter.append('defs');
9111 var gEnter = wrapEnter.append('g');
9112 var g = wrap.select('g');
9114 gEnter.append('g').attr('class', 'nv-groups');
9116 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9118 //------------------------------------------------------------
9122 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
9123 .data(function(d) { return d }, function(d,i) { return i });
9124 groups.enter().append('g')
9125 .style('stroke-opacity', 1e-6)
9126 .style('fill-opacity', 1e-6);
9127 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
9128 .style('stroke-opacity', 1e-6)
9129 .style('fill-opacity', 1e-6)
9132 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
9133 .classed('hover', function(d) { return d.hover })
9134 .style('fill', function(d,i){ return color(d, i) })
9135 .style('stroke', function(d,i){ return color(d, i) });
9136 groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
9137 .style('stroke-opacity', 1)
9138 .style('fill-opacity', .75);
9141 var bars = groups.selectAll('g.nv-bar')
9142 .data(function(d) { return d.values });
9144 bars.exit().remove();
9147 var barsEnter = bars.enter().append('g')
9148 .attr('transform', function(d,i,j) {
9149 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
9152 barsEnter.append('rect')
9154 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
9157 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
9158 d3.select(this).classed('hover', true);
9159 dispatch.elementMouseover({
9162 series: data[d.series],
9163 pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
9165 seriesIndex: d.series,
9169 .on('mouseout', function(d,i) {
9170 d3.select(this).classed('hover', false);
9171 dispatch.elementMouseout({
9174 series: data[d.series],
9176 seriesIndex: d.series,
9180 .on('click', function(d,i) {
9181 dispatch.elementClick({
9184 series: data[d.series],
9185 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
9187 seriesIndex: d.series,
9190 d3.event.stopPropagation();
9192 .on('dblclick', function(d,i) {
9193 dispatch.elementDblClick({
9196 series: data[d.series],
9197 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
9199 seriesIndex: d.series,
9202 d3.event.stopPropagation();
9206 barsEnter.append('text');
9208 if (showValues && !stacked) {
9210 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
9211 .attr('y', x.rangeBand() / (data.length * 2))
9212 .attr('dy', '.32em')
9213 .text(function(d,i) { return valueFormat(getY(d,i)) })
9214 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9216 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
9218 bars.selectAll('text').text('');
9221 if (showBarLabels && !stacked) {
9222 barsEnter.append('text').classed('nv-bar-label',true);
9223 bars.select('text.nv-bar-label')
9224 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
9225 .attr('y', x.rangeBand() / (data.length * 2))
9226 .attr('dy', '.32em')
9227 .text(function(d,i) { return getX(d,i) });
9228 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9229 .select('text.nv-bar-label')
9230 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
9233 bars.selectAll('text.nv-bar-label').text('');
9237 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
9240 if (!disabled) disabled = data.map(function() { return true });
9242 .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(); })
9243 .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(); });
9247 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9248 .attr('transform', function(d,i) {
9249 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
9252 .attr('width', function(d,i) {
9253 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
9255 .attr('height', x.rangeBand() );
9257 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
9258 .attr('transform', function(d,i) {
9259 //TODO: stacked must be all positive or all negative, not both?
9260 return 'translate(' +
9261 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
9263 (d.series * x.rangeBand() / data.length
9269 .attr('height', x.rangeBand() / data.length )
9270 .attr('width', function(d,i) {
9271 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
9275 //store old scales for use in transitions on update
9281 renderWatch.renderEnd('multibarHorizontal immediate');
9286 //============================================================
9287 // Expose Public Variables
9288 //------------------------------------------------------------
9290 chart.dispatch = dispatch;
9292 chart.options = nv.utils.optionsFunc.bind(chart);
9294 chart.x = function(_) {
9295 if (!arguments.length) return getX;
9300 chart.y = function(_) {
9301 if (!arguments.length) return getY;
9306 chart.margin = function(_) {
9307 if (!arguments.length) return margin;
9308 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
9309 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
9310 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9311 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
9315 chart.width = function(_) {
9316 if (!arguments.length) return width;
9321 chart.height = function(_) {
9322 if (!arguments.length) return height;
9327 chart.xScale = function(_) {
9328 if (!arguments.length) return x;
9333 chart.yScale = function(_) {
9334 if (!arguments.length) return y;
9339 chart.xDomain = function(_) {
9340 if (!arguments.length) return xDomain;
9345 chart.yDomain = function(_) {
9346 if (!arguments.length) return yDomain;
9351 chart.xRange = function(_) {
9352 if (!arguments.length) return xRange;
9357 chart.yRange = function(_) {
9358 if (!arguments.length) return yRange;
9363 chart.forceY = function(_) {
9364 if (!arguments.length) return forceY;
9369 chart.stacked = function(_) {
9370 if (!arguments.length) return stacked;
9375 chart.color = function(_) {
9376 if (!arguments.length) return color;
9377 color = nv.utils.getColor(_);
9381 chart.barColor = function(_) {
9382 if (!arguments.length) return barColor;
9383 barColor = nv.utils.getColor(_);
9387 chart.disabled = function(_) {
9388 if (!arguments.length) return disabled;
9393 chart.id = function(_) {
9394 if (!arguments.length) return id;
9399 chart.delay = function(_) {
9400 if (!arguments.length) return delay;
9405 chart.showValues = function(_) {
9406 if (!arguments.length) return showValues;
9411 chart.showBarLabels = function(_) {
9412 if (!arguments.length) return showBarLabels;
9418 chart.valueFormat= function(_) {
9419 if (!arguments.length) return valueFormat;
9424 chart.valuePadding = function(_) {
9425 if (!arguments.length) return valuePadding;
9430 chart.duration = function(_) {
9431 if (!arguments.length) return duration;
9433 renderWatch.reset(duration);
9437 //============================================================
9443 nv.models.multiBarHorizontalChart = function() {
9445 //============================================================
9446 // Public Variables with Default Settings
9447 //------------------------------------------------------------
9449 var multibar = nv.models.multiBarHorizontal()
9450 , xAxis = nv.models.axis()
9451 , yAxis = nv.models.axis()
9452 , legend = nv.models.legend().height(30)
9453 , controls = nv.models.legend().height(30)
9456 var margin = {top: 30, right: 20, bottom: 50, left: 60}
9459 , color = nv.utils.defaultColor()
9460 , showControls = true
9466 , tooltip = function(key, x, y, e, graph) {
9467 return '<h3>' + key + ' - ' + x + '</h3>' +
9470 , x //can be accessed via chart.xScale()
9471 , y //can be accessed via chart.yScale()
9472 , state = nv.utils.state()
9473 , defaultState = null
9474 , noData = 'No Data Available.'
9475 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
9476 , controlWidth = function() { return showControls ? 180 : 0 }
9480 state.stacked = false; // DEPRECATED Maintained for backward compatibility
9488 .highlightZero(false)
9490 .tickFormat(function(d) { return d })
9494 .tickFormat(d3.format(',.1f'))
9497 controls.updateState(false);
9498 //============================================================
9501 //============================================================
9502 // Private Variables
9503 //------------------------------------------------------------
9506 var showTooltip = function(e, offsetElement) {
9507 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9508 top = e.pos[1] + ( offsetElement.offsetTop || 0),
9509 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
9510 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
9511 content = tooltip(e.series.key, x, y, e, chart);
9513 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
9516 var stateGetter = function(data) {
9519 active: data.map(function(d) { return !d.disabled }),
9525 var stateSetter = function(data) {
9526 return function(state) {
9527 if (state.stacked !== undefined)
9528 stacked = state.stacked;
9529 if (state.active !== undefined)
9530 data.forEach(function(series,i) {
9531 series.disabled = !state.active[i];
9536 //============================================================
9537 var renderWatch = nv.utils.renderWatch(dispatch, duration);
9539 function chart(selection) {
9540 renderWatch.reset();
9541 renderWatch.models(multibar);
9542 if (showXAxis) renderWatch.models(xAxis);
9543 if (showYAxis) renderWatch.models(yAxis);
9545 selection.each(function(data) {
9546 var container = d3.select(this),
9548 nv.utils.initSVG(container);
9549 var availableWidth = (width || parseInt(container.style('width')) || 960)
9550 - margin.left - margin.right,
9551 availableHeight = (height || parseInt(container.style('height')) || 400)
9552 - margin.top - margin.bottom;
9554 chart.update = function() { container.transition().duration(duration).call(chart) };
9555 chart.container = this;
9557 stacked = multibar.stacked();
9560 .setter(stateSetter(data), chart.update)
9561 .getter(stateGetter(data))
9564 // DEPRECATED set state.disableddisabled
9565 state.disabled = data.map(function(d) { return !!d.disabled });
9567 if (!defaultState) {
9570 for (key in state) {
9571 if (state[key] instanceof Array)
9572 defaultState[key] = state[key].slice(0);
9574 defaultState[key] = state[key];
9578 //------------------------------------------------------------
9579 // Display No Data message if there's nothing to show.
9581 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9582 var noDataText = container.selectAll('.nv-noData').data([noData]);
9584 noDataText.enter().append('text')
9585 .attr('class', 'nvd3 nv-noData')
9586 .attr('dy', '-.7em')
9587 .style('text-anchor', 'middle');
9590 .attr('x', margin.left + availableWidth / 2)
9591 .attr('y', margin.top + availableHeight / 2)
9592 .text(function(d) { return d });
9596 container.selectAll('.nv-noData').remove();
9599 //------------------------------------------------------------
9602 //------------------------------------------------------------
9605 x = multibar.xScale();
9606 y = multibar.yScale();
9608 //------------------------------------------------------------
9611 //------------------------------------------------------------
9612 // Setup containers and skeleton of chart
9614 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
9615 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
9616 var g = wrap.select('g');
9618 gEnter.append('g').attr('class', 'nv-x nv-axis');
9619 gEnter.append('g').attr('class', 'nv-y nv-axis')
9620 .append('g').attr('class', 'nv-zeroLine')
9622 gEnter.append('g').attr('class', 'nv-barsWrap');
9623 gEnter.append('g').attr('class', 'nv-legendWrap');
9624 gEnter.append('g').attr('class', 'nv-controlsWrap');
9626 //------------------------------------------------------------
9629 //------------------------------------------------------------
9633 legend.width(availableWidth - controlWidth());
9635 if (multibar.barColor())
9636 data.forEach(function(series,i) {
9637 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
9640 g.select('.nv-legendWrap')
9644 if ( margin.top != legend.height()) {
9645 margin.top = legend.height();
9646 availableHeight = (height || parseInt(container.style('height')) || 400)
9647 - margin.top - margin.bottom;
9650 g.select('.nv-legendWrap')
9651 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9654 //------------------------------------------------------------
9657 //------------------------------------------------------------
9661 var controlsData = [
9662 { key: 'Grouped', disabled: multibar.stacked() },
9663 { key: 'Stacked', disabled: !multibar.stacked() }
9666 controls.width(controlWidth()).color(['#444', '#444', '#444']);
9667 g.select('.nv-controlsWrap')
9668 .datum(controlsData)
9669 .attr('transform', 'translate(0,' + (-margin.top) +')')
9673 //------------------------------------------------------------
9676 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9679 //------------------------------------------------------------
9680 // Main Chart Component(s)
9683 .disabled(data.map(function(series) { return series.disabled }))
9684 .width(availableWidth)
9685 .height(availableHeight)
9686 .color(data.map(function(d,i) {
9687 return d.color || color(d, i);
9688 }).filter(function(d,i) { return !data[i].disabled }));
9690 var barsWrap = g.select('.nv-barsWrap')
9691 .datum(data.filter(function(d) { return !d.disabled }));
9693 barsWrap.transition().call(multibar);
9695 //------------------------------------------------------------
9698 //------------------------------------------------------------
9704 .ticks( nv.utils.calcTicksY(availableHeight/24, data) )
9705 .tickSize(-availableWidth, 0);
9707 g.select('.nv-x.nv-axis').call(xAxis);
9709 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9712 .selectAll('line, text');
9718 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9719 .tickSize( -availableHeight, 0);
9721 g.select('.nv-y.nv-axis')
9722 .attr('transform', 'translate(0,' + availableHeight + ')');
9723 g.select('.nv-y.nv-axis').call(yAxis);
9727 g.select(".nv-zeroLine line")
9731 .attr("y2", -availableHeight)
9734 //------------------------------------------------------------
9738 //============================================================
9739 // Event Handling/Dispatching (in chart's scope)
9740 //------------------------------------------------------------
9742 legend.dispatch.on('stateChange', function(newState) {
9743 for (var key in newState)
9744 state[key] = newState[key];
9745 dispatch.stateChange(state);
9749 controls.dispatch.on('legendClick', function(d,i) {
9750 if (!d.disabled) return;
9751 controlsData = controlsData.map(function(s) {
9759 multibar.stacked(false);
9762 multibar.stacked(true);
9766 state.stacked = multibar.stacked();
9767 dispatch.stateChange(state);
9768 stacked = multibar.stacked();
9773 dispatch.on('tooltipShow', function(e) {
9774 if (tooltips) showTooltip(e, that.parentNode);
9777 // Update chart from a state object passed to event handler
9778 dispatch.on('changeState', function(e) {
9780 if (typeof e.disabled !== 'undefined') {
9781 data.forEach(function(series,i) {
9782 series.disabled = e.disabled[i];
9785 state.disabled = e.disabled;
9788 if (typeof e.stacked !== 'undefined') {
9789 multibar.stacked(e.stacked);
9790 state.stacked = e.stacked;
9791 stacked = e.stacked;
9796 //============================================================
9799 renderWatch.renderEnd('multibar horizontal chart immediate');
9804 //============================================================
9805 // Event Handling/Dispatching (out of chart's scope)
9806 //------------------------------------------------------------
9808 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9809 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9810 dispatch.tooltipShow(e);
9813 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9814 dispatch.tooltipHide(e);
9816 dispatch.on('tooltipHide', function() {
9817 if (tooltips) nv.tooltip.cleanup();
9820 //============================================================
9823 //============================================================
9824 // Expose Public Variables
9825 //------------------------------------------------------------
9827 // expose chart's sub-components
9828 chart.dispatch = dispatch;
9829 chart.multibar = multibar;
9830 chart.legend = legend;
9831 chart.xAxis = xAxis;
9832 chart.yAxis = yAxis;
9834 // DO NOT DELETE. This is currently overridden below
9835 // until deprecated portions are removed.
9836 chart.state = state;
9838 d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY',
9839 'clipEdge', 'id', 'delay', 'showValues','showBarLabels', 'valueFormat', 'stacked', 'barColor');
9841 chart.options = nv.utils.optionsFunc.bind(chart);
9843 chart.margin = function(_) {
9844 if (!arguments.length) return margin;
9845 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
9846 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
9847 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9848 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
9852 chart.width = function(_) {
9853 if (!arguments.length) return width;
9858 chart.height = function(_) {
9859 if (!arguments.length) return height;
9864 chart.color = function(_) {
9865 if (!arguments.length) return color;
9866 color = nv.utils.getColor(_);
9867 legend.color(color);
9871 chart.showControls = function(_) {
9872 if (!arguments.length) return showControls;
9877 chart.showLegend = function(_) {
9878 if (!arguments.length) return showLegend;
9883 chart.showXAxis = function(_) {
9884 if (!arguments.length) return showXAxis;
9889 chart.showYAxis = function(_) {
9890 if (!arguments.length) return showYAxis;
9895 chart.tooltip = function(_) {
9896 if (!arguments.length) return tooltip;
9901 chart.tooltips = function(_) {
9902 if (!arguments.length) return tooltips;
9907 chart.tooltipContent = function(_) {
9908 if (!arguments.length) return tooltip;
9914 chart.state = function(_) {
9915 nv.deprecated('multiBarHorizontalChart.state');
9916 if (!arguments.length) return state;
9920 for (var key in state) {
9921 chart.state[key] = state[key];
9925 chart.defaultState = function(_) {
9926 if (!arguments.length) return defaultState;
9931 chart.noData = function(_) {
9932 if (!arguments.length) return noData;
9937 chart.transitionDuration = function(_) {
9938 nv.deprecated('multiBarHorizontalChart.transitionDuration');
9939 return chart.duration(_);
9942 chart.duration = function(_) {
9943 if (!arguments.length) return duration;
9945 renderWatch.reset(duration);
9946 multibar.duration(duration);
9947 xAxis.duration(duration);
9948 yAxis.duration(duration);
9951 //============================================================
9955 nv.models.multiChart = function() {
9957 //============================================================
9958 // Public Variables with Default Settings
9959 //------------------------------------------------------------
9961 var margin = {top: 30, right: 20, bottom: 50, left: 60},
9962 color = nv.utils.defaultColor(),
9967 tooltip = function(key, x, y, e, graph) {
9968 return '<h3>' + key + '</h3>' +
9969 '<p>' + y + ' at ' + x + '</p>'
9975 getX = function(d) { return d.x },
9976 getY = function(d) { return d.y},
9977 interpolate = 'monotone'
9980 //============================================================
9981 // Private Variables
9982 //------------------------------------------------------------
9984 var x = d3.scale.linear(),
9985 yScale1 = d3.scale.linear(),
9986 yScale2 = d3.scale.linear(),
9988 lines1 = nv.models.line().yScale(yScale1),
9989 lines2 = nv.models.line().yScale(yScale2),
9991 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9992 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9994 stack1 = nv.models.stackedArea().yScale(yScale1),
9995 stack2 = nv.models.stackedArea().yScale(yScale2),
9997 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9998 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9999 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
10001 legend = nv.models.legend().height(30),
10002 dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
10004 var showTooltip = function(e, offsetElement) {
10005 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10006 top = e.pos[1] + ( offsetElement.offsetTop || 0),
10007 x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
10008 y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
10009 content = tooltip(e.series.key, x, y, e, chart);
10011 nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
10014 function chart(selection) {
10015 selection.each(function(data) {
10016 var container = d3.select(this),
10018 nv.utils.initSVG(container);
10020 chart.update = function() { container.transition().call(chart); };
10021 chart.container = this;
10023 var availableWidth = (width || parseInt(container.style('width')) || 960)
10024 - margin.left - margin.right,
10025 availableHeight = (height || parseInt(container.style('height')) || 400)
10026 - margin.top - margin.bottom;
10028 var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
10029 var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
10030 var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
10031 var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
10032 var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
10033 var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
10035 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
10037 return d.values.map(function(d,i) {
10038 return { x: d.x, y: d.y }
10042 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
10044 return d.values.map(function(d,i) {
10045 return { x: d.x, y: d.y }
10049 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
10050 .range([0, availableWidth]);
10052 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
10053 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
10055 gEnter.append('g').attr('class', 'x axis');
10056 gEnter.append('g').attr('class', 'y1 axis');
10057 gEnter.append('g').attr('class', 'y2 axis');
10058 gEnter.append('g').attr('class', 'lines1Wrap');
10059 gEnter.append('g').attr('class', 'lines2Wrap');
10060 gEnter.append('g').attr('class', 'bars1Wrap');
10061 gEnter.append('g').attr('class', 'bars2Wrap');
10062 gEnter.append('g').attr('class', 'stack1Wrap');
10063 gEnter.append('g').attr('class', 'stack2Wrap');
10064 gEnter.append('g').attr('class', 'legendWrap');
10066 var g = wrap.select('g');
10069 legend.width( availableWidth / 2 );
10071 g.select('.legendWrap')
10072 .datum(data.map(function(series) {
10073 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
10074 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
10079 if ( margin.top != legend.height()) {
10080 margin.top = legend.height();
10081 availableHeight = (height || parseInt(container.style('height')) || 400)
10082 - margin.top - margin.bottom;
10085 g.select('.legendWrap')
10086 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
10091 .width(availableWidth)
10092 .height(availableHeight)
10093 .interpolate(interpolate)
10094 .color(data.map(function(d, i) {
10095 return d.color || color[i % color.length];
10096 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
10099 .width(availableWidth)
10100 .height(availableHeight)
10101 .interpolate(interpolate)
10102 .color(data.map(function(d,i) {
10103 return d.color || color[i % color.length];
10104 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
10107 .width(availableWidth)
10108 .height(availableHeight)
10109 .color(data.map(function(d,i) {
10110 return d.color || color[i % color.length];
10111 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
10114 .width(availableWidth)
10115 .height(availableHeight)
10116 .color(data.map(function(d,i) {
10117 return d.color || color[i % color.length];
10118 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
10121 .width(availableWidth)
10122 .height(availableHeight)
10123 .color(data.map(function(d,i) {
10124 return d.color || color[i % color.length];
10125 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
10128 .width(availableWidth)
10129 .height(availableHeight)
10130 .color(data.map(function(d,i) {
10131 return d.color || color[i % color.length];
10132 }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
10134 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10137 var lines1Wrap = g.select('.lines1Wrap')
10139 var bars1Wrap = g.select('.bars1Wrap')
10141 var stack1Wrap = g.select('.stack1Wrap')
10144 var lines2Wrap = g.select('.lines2Wrap')
10146 var bars2Wrap = g.select('.bars2Wrap')
10148 var stack2Wrap = g.select('.stack2Wrap')
10151 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
10152 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
10153 }).concat([{x:0, y:0}]) : []
10154 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
10155 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
10156 }).concat([{x:0, y:0}]) : []
10158 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
10159 .range([0, availableHeight])
10161 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
10162 .range([0, availableHeight])
10164 lines1.yDomain(yScale1.domain())
10165 bars1.yDomain(yScale1.domain())
10166 stack1.yDomain(yScale1.domain())
10168 lines2.yDomain(yScale2.domain())
10169 bars2.yDomain(yScale2.domain())
10170 stack2.yDomain(yScale2.domain())
10172 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
10173 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
10175 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
10176 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
10178 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
10179 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
10184 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
10185 .tickSize(-availableHeight, 0);
10187 g.select('.x.axis')
10188 .attr('transform', 'translate(0,' + availableHeight + ')');
10189 d3.transition(g.select('.x.axis'))
10193 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
10194 .tickSize( -availableWidth, 0);
10197 d3.transition(g.select('.y1.axis'))
10201 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
10202 .tickSize( -availableWidth, 0);
10204 d3.transition(g.select('.y2.axis'))
10207 g.select('.y2.axis')
10208 .style('opacity', series2.length ? 1 : 0)
10209 .attr('transform', 'translate(' + x.range()[1] + ',0)');
10211 legend.dispatch.on('stateChange', function(newState) {
10215 dispatch.on('tooltipShow', function(e) {
10216 if (tooltips) showTooltip(e, that.parentNode);
10225 //============================================================
10226 // Event Handling/Dispatching (out of chart's scope)
10227 //------------------------------------------------------------
10229 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
10230 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10231 dispatch.tooltipShow(e);
10234 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
10235 dispatch.tooltipHide(e);
10238 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
10239 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10240 dispatch.tooltipShow(e);
10243 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
10244 dispatch.tooltipHide(e);
10247 bars1.dispatch.on('elementMouseover.tooltip', function(e) {
10248 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10249 dispatch.tooltipShow(e);
10252 bars1.dispatch.on('elementMouseout.tooltip', function(e) {
10253 dispatch.tooltipHide(e);
10256 bars2.dispatch.on('elementMouseover.tooltip', function(e) {
10257 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10258 dispatch.tooltipShow(e);
10261 bars2.dispatch.on('elementMouseout.tooltip', function(e) {
10262 dispatch.tooltipHide(e);
10265 stack1.dispatch.on('tooltipShow', function(e) {
10266 //disable tooltips when value ~= 0
10267 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
10268 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
10269 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
10273 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10274 dispatch.tooltipShow(e);
10277 stack1.dispatch.on('tooltipHide', function(e) {
10278 dispatch.tooltipHide(e);
10281 stack2.dispatch.on('tooltipShow', function(e) {
10282 //disable tooltips when value ~= 0
10283 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
10284 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
10285 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
10289 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10290 dispatch.tooltipShow(e);
10293 stack2.dispatch.on('tooltipHide', function(e) {
10294 dispatch.tooltipHide(e);
10297 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
10298 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10299 dispatch.tooltipShow(e);
10302 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
10303 dispatch.tooltipHide(e);
10306 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
10307 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10308 dispatch.tooltipShow(e);
10311 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
10312 dispatch.tooltipHide(e);
10315 dispatch.on('tooltipHide', function() {
10316 if (tooltips) nv.tooltip.cleanup();
10321 //============================================================
10322 // Global getters and setters
10323 //------------------------------------------------------------
10325 chart.dispatch = dispatch;
10326 chart.lines1 = lines1;
10327 chart.lines2 = lines2;
10328 chart.bars1 = bars1;
10329 chart.bars2 = bars2;
10330 chart.stack1 = stack1;
10331 chart.stack2 = stack2;
10332 chart.xAxis = xAxis;
10333 chart.yAxis1 = yAxis1;
10334 chart.yAxis2 = yAxis2;
10335 chart.options = nv.utils.optionsFunc.bind(chart);
10337 chart.x = function(_) {
10338 if (!arguments.length) return getX;
10345 chart.y = function(_) {
10346 if (!arguments.length) return getY;
10353 chart.yDomain1 = function(_) {
10354 if (!arguments.length) return yDomain1;
10359 chart.yDomain2 = function(_) {
10360 if (!arguments.length) return yDomain2;
10365 chart.margin = function(_) {
10366 if (!arguments.length) return margin;
10371 chart.width = function(_) {
10372 if (!arguments.length) return width;
10377 chart.height = function(_) {
10378 if (!arguments.length) return height;
10383 chart.color = function(_) {
10384 if (!arguments.length) return color;
10390 chart.showLegend = function(_) {
10391 if (!arguments.length) return showLegend;
10396 chart.tooltips = function(_) {
10397 if (!arguments.length) return tooltips;
10402 chart.tooltipContent = function(_) {
10403 if (!arguments.length) return tooltip;
10408 chart.interpolate = function(_) {
10409 if(!arguments.length) {
10410 return interpolate;
10420 nv.models.ohlcBar = function() {
10422 //============================================================
10423 // Public Variables with Default Settings
10424 //------------------------------------------------------------
10426 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10429 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10430 , x = d3.scale.linear()
10431 , y = d3.scale.linear()
10432 , getX = function(d) { return d.x }
10433 , getY = function(d) { return d.y }
10434 , getOpen = function(d) { return d.open }
10435 , getClose = function(d) { return d.close }
10436 , getHigh = function(d) { return d.high }
10437 , getLow = function(d) { return d.low }
10440 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10442 , color = nv.utils.defaultColor()
10447 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10450 //============================================================
10452 //============================================================
10453 // Private Variables
10454 //------------------------------------------------------------
10456 //TODO: store old scales for transitions
10458 //============================================================
10461 function chart(selection) {
10462 selection.each(function(data) {
10463 var availableWidth = width - margin.left - margin.right,
10464 availableHeight = height - margin.top - margin.bottom,
10465 container = d3.select(this);
10466 nv.utils.initSVG(container);
10468 //------------------------------------------------------------
10471 x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
10474 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
10476 x.range(xRange || [0, availableWidth]);
10478 y .domain(yDomain || [
10479 d3.min(data[0].values.map(getLow).concat(forceY)),
10480 d3.max(data[0].values.map(getHigh).concat(forceY))
10482 .range(yRange || [availableHeight, 0]);
10484 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10485 if (x.domain()[0] === x.domain()[1])
10487 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10488 : x.domain([-1,1]);
10490 if (y.domain()[0] === y.domain()[1])
10492 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
10493 : y.domain([-1,1]);
10495 //------------------------------------------------------------
10498 //------------------------------------------------------------
10499 // Setup containers and skeleton of chart
10501 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
10502 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
10503 var defsEnter = wrapEnter.append('defs');
10504 var gEnter = wrapEnter.append('g');
10505 var g = wrap.select('g');
10507 gEnter.append('g').attr('class', 'nv-ticks');
10509 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10511 //------------------------------------------------------------
10515 .on('click', function(d,i) {
10516 dispatch.chartClick({
10525 defsEnter.append('clipPath')
10526 .attr('id', 'nv-chart-clip-path-' + id)
10529 wrap.select('#nv-chart-clip-path-' + id + ' rect')
10530 .attr('width', availableWidth)
10531 .attr('height', availableHeight);
10533 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
10537 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
10538 .data(function(d) { return d });
10540 ticks.exit().remove();
10543 var ticksEnter = ticks.enter().append('path')
10544 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
10545 .attr('d', function(d,i) {
10546 var w = (availableWidth / data[0].values.length) * .9;
10555 + (y(getLow(d,i)) - y(getOpen(d,i)))
10557 + (y(getClose(d,i))
10565 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10566 //.attr('fill', function(d,i) { return color[0]; })
10567 //.attr('stroke', function(d,i) { return color[0]; })
10569 //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
10570 //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
10571 .on('mouseover', function(d,i) {
10572 d3.select(this).classed('hover', true);
10573 dispatch.elementMouseover({
10576 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
10583 .on('mouseout', function(d,i) {
10584 d3.select(this).classed('hover', false);
10585 dispatch.elementMouseout({
10593 .on('click', function(d,i) {
10594 dispatch.elementClick({
10599 pos: [x(getX(d,i)), y(getY(d,i))],
10603 d3.event.stopPropagation();
10605 .on('dblclick', function(d,i) {
10606 dispatch.elementDblClick({
10611 pos: [x(getX(d,i)), y(getY(d,i))],
10615 d3.event.stopPropagation();
10619 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
10620 d3.transition(ticks)
10621 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10622 .attr('d', function(d,i) {
10623 var w = (availableWidth / data[0].values.length) * .9;
10635 + (y(getClose(d,i))
10643 //.attr('width', (availableWidth / data[0].values.length) * .9 )
10646 //d3.transition(ticks)
10647 //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
10648 //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
10649 //.order(); // not sure if this makes any sense for this model
10657 //============================================================
10658 // Expose Public Variables
10659 //------------------------------------------------------------
10661 chart.dispatch = dispatch;
10663 chart.options = nv.utils.optionsFunc.bind(chart);
10665 chart.x = function(_) {
10666 if (!arguments.length) return getX;
10671 chart.y = function(_) {
10672 if (!arguments.length) return getY;
10677 chart.open = function(_) {
10678 if (!arguments.length) return getOpen;
10683 chart.close = function(_) {
10684 if (!arguments.length) return getClose;
10689 chart.high = function(_) {
10690 if (!arguments.length) return getHigh;
10695 chart.low = function(_) {
10696 if (!arguments.length) return getLow;
10701 chart.margin = function(_) {
10702 if (!arguments.length) return margin;
10703 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10704 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10705 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10706 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10710 chart.width = function(_) {
10711 if (!arguments.length) return width;
10716 chart.height = function(_) {
10717 if (!arguments.length) return height;
10722 chart.xScale = function(_) {
10723 if (!arguments.length) return x;
10728 chart.yScale = function(_) {
10729 if (!arguments.length) return y;
10734 chart.xDomain = function(_) {
10735 if (!arguments.length) return xDomain;
10740 chart.yDomain = function(_) {
10741 if (!arguments.length) return yDomain;
10746 chart.xRange = function(_) {
10747 if (!arguments.length) return xRange;
10752 chart.yRange = function(_) {
10753 if (!arguments.length) return yRange;
10758 chart.forceX = function(_) {
10759 if (!arguments.length) return forceX;
10764 chart.forceY = function(_) {
10765 if (!arguments.length) return forceY;
10770 chart.padData = function(_) {
10771 if (!arguments.length) return padData;
10776 chart.clipEdge = function(_) {
10777 if (!arguments.length) return clipEdge;
10782 chart.color = function(_) {
10783 if (!arguments.length) return color;
10784 color = nv.utils.getColor(_);
10788 chart.id = function(_) {
10789 if (!arguments.length) return id;
10794 //============================================================
10799 nv.models.pie = function() {
10802 //============================================================
10803 // Public Variables with Default Settings
10804 //------------------------------------------------------------
10806 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10809 , getX = function(d) { return d.x }
10810 , getY = function(d) { return d.y }
10811 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10812 , color = nv.utils.defaultColor()
10813 , valueFormat = d3.format(',.2f')
10814 , labelFormat = d3.format('%')
10815 , showLabels = true
10816 , pieLabelsOutside = true
10817 , donutLabelsOutside = false
10818 , labelType = "key"
10819 , labelThreshold = .02 //if slice percentage is under this, don't show label
10822 , growOnHover = true
10824 , labelSunbeamLayout = false
10825 , startAngle = false
10829 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
10833 //============================================================
10835 //------------------------------------------------------------
10837 var renderWatch = nv.utils.renderWatch(dispatch);
10839 function chart(selection) {
10840 renderWatch.reset();
10841 selection.each(function(data) {
10842 var availableWidth = width - margin.left - margin.right
10843 ,availableHeight = height - margin.top - margin.bottom
10844 ,radius = Math.min(availableWidth, availableHeight) / 2
10845 ,arcRadius = radius-(radius / 5)
10846 ,container = d3.select(this)
10848 nv.utils.initSVG(container);
10850 // Setup containers and skeleton of chart
10851 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10852 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10853 var gEnter = wrapEnter.append('g');
10854 var g = wrap.select('g');
10855 var g_pie = gEnter.append('g').attr('class', 'nv-pie');
10856 gEnter.append('g').attr('class', 'nv-pieLabels');
10858 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10859 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10860 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10863 container.on('click', function(d,i) {
10864 dispatch.chartClick({
10873 var arc = d3.svg.arc().outerRadius(arcRadius);
10874 var arcOver = d3.svg.arc().outerRadius(arcRadius + 5);
10877 arc.startAngle(startAngle);
10878 arcOver.startAngle(startAngle);
10881 arc.endAngle(endAngle);
10882 arcOver.endAngle(endAngle);
10885 arc.innerRadius(radius * donutRatio);
10886 arcOver.innerRadius(radius * donutRatio);
10889 // Setup the Pie chart and choose the data element
10890 var pie = d3.layout.pie()
10892 .value(function(d) { return d.disabled ? 0 : getY(d) });
10894 // if title is specified and donut, put it in the middle
10895 if (donut && title) {
10896 var title_g = g_pie.append('g').attr('class', 'nv-pie');
10898 title_g.append("text")
10899 .style("text-anchor", "middle")
10900 .attr('class', 'nv-pie-title')
10901 .text(function (d) {
10904 .attr("dy", "0.35em") // trick to vertically center text
10905 .attr('transform', function(d, i) {
10906 return 'translate(0, '+ titleOffset + ')';
10910 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
10911 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
10913 slices.exit().remove();
10914 pieLabels.exit().remove();
10916 var ae = slices.enter().append('g')
10917 ae.attr('class', 'nv-slice')
10918 ae.on('mouseover', function(d,i){
10919 d3.select(this).classed('hover', true);
10921 d3.select(this).select("path").transition()
10923 .attr("d", arcOver);
10925 dispatch.elementMouseover({
10926 label: getX(d.data),
10927 value: getY(d.data),
10930 pos: [d3.event.pageX, d3.event.pageY],
10932 color: d3.select(this).style("fill")
10935 ae.on('mouseout', function(d,i){
10936 d3.select(this).classed('hover', false);
10938 d3.select(this).select("path").transition()
10942 dispatch.elementMouseout({
10943 label: getX(d.data),
10944 value: getY(d.data),
10950 ae.on('click', function(d,i) {
10951 dispatch.elementClick({
10952 label: getX(d.data),
10953 value: getY(d.data),
10959 d3.event.stopPropagation();
10961 ae.on('dblclick', function(d,i) {
10962 dispatch.elementDblClick({
10963 label: getX(d.data),
10964 value: getY(d.data),
10970 d3.event.stopPropagation();
10973 slices.attr('fill', function(d,i) { return color(d, i); })
10974 slices.attr('stroke', function(d,i) { return color(d, i); });
10976 var paths = ae.append('path').each(function(d) {
10980 slices.select('path')
10983 .attrTween('d', arcTween);
10986 // This does the normal label
10987 var labelsArc = d3.svg.arc().innerRadius(0);
10989 if (pieLabelsOutside){
10990 var labelsArc = arc;
10993 if (donutLabelsOutside) {
10994 labelsArc = d3.svg.arc().outerRadius(arc.outerRadius());
10997 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
10998 var group = d3.select(this);
11000 group.attr('transform', function(d) {
11001 if (labelSunbeamLayout) {
11002 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
11003 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
11004 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
11005 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
11010 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
11012 d.outerRadius = radius + 10; // Set Outer Coordinate
11013 d.innerRadius = radius + 15; // Set Inner Coordinate
11014 return 'translate(' + labelsArc.centroid(d) + ')'
11018 group.append('rect')
11019 .style('stroke', '#fff')
11020 .style('fill', '#fff')
11024 group.append('text')
11025 .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
11026 .style('fill', '#000')
11030 var labelLocationHash = {};
11031 var avgHeight = 14;
11032 var avgWidth = 140;
11033 var createHashKey = function(coordinates) {
11034 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
11037 pieLabels.watchTransition(renderWatch,'pie labels').attr('transform', function(d) {
11038 if (labelSunbeamLayout) {
11039 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
11040 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
11041 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
11042 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
11047 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
11049 d.outerRadius = radius + 10; // Set Outer Coordinate
11050 d.innerRadius = radius + 15; // Set Inner Coordinate
11053 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
11054 Each label location is hashed, and if a hash collision occurs, we assume an overlap.
11055 Adjust the label's y-position to remove the overlap.
11057 var center = labelsArc.centroid(d);
11059 var hashKey = createHashKey(center);
11060 if (labelLocationHash[hashKey]) {
11061 center[1] -= avgHeight;
11063 labelLocationHash[createHashKey(center)] = true;
11065 return 'translate(' + center + ')'
11069 pieLabels.select(".nv-label text")
11070 .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
11071 .text(function(d, i) {
11072 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
11074 "key" : getX(d.data),
11075 "value": getY(d.data),
11076 "percent": labelFormat(percent)
11078 return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
11084 // Computes the angle of an arc, converting from radians to degrees.
11085 function angle(d) {
11086 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
11087 return a > 90 ? a - 180 : a;
11090 function arcTween(a) {
11091 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
11092 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
11093 if (!donut) a.innerRadius = 0;
11094 var i = d3.interpolate(this._current, a);
11095 this._current = i(0);
11096 return function(t) {
11102 renderWatch.renderEnd('pie immediate');
11106 //============================================================
11107 // Expose Public Variables
11108 //------------------------------------------------------------
11110 chart.dispatch = dispatch;
11111 chart.options = nv.utils.optionsFunc.bind(chart);
11113 chart._options = Object.create({}, {
11114 // simple options, just get/set the necessary values
11115 width: {get: function(){return width;}, set: function(_){width=_;}},
11116 height: {get: function(){return height;}, set: function(_){height=_;}},
11117 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
11118 title: {get: function(){return title;}, set: function(_){title=_;}},
11119 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
11120 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
11121 labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_;}},
11122 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
11123 x: {get: function(){return getX;}, set: function(_){getX=_;}},
11124 id: {get: function(){return id;}, set: function(_){id=_;}},
11125 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
11126 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
11127 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
11128 pieLabelsOutside: {get: function(){return pieLabelsOutside;}, set: function(_){pieLabelsOutside=_;}},
11129 donutLabelsOutside: {get: function(){return donutLabelsOutside;}, set: function(_){donutLabelsOutside=_;}},
11130 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
11131 donut: {get: function(){return donut;}, set: function(_){donut=_;}},
11132 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
11134 // options that require extra logic in the setter
11135 margin: {get: function(){return margin;}, set: function(_){
11136 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11137 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11138 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11139 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11141 y: {get: function(){return getY;}, set: function(_){
11142 getY=d3.functor(_);
11144 color: {get: function(){return color;}, set: function(_){
11145 color=nv.utils.getColor(_);
11147 labelType: {get: function(){return labelType;}, set: function(_){
11148 labelType= _ || 'key';
11152 nv.utils.initOptions(chart);
11155 nv.models.pieChart = function() {
11158 //============================================================
11159 // Public Variables with Default Settings
11160 //------------------------------------------------------------
11162 var pie = nv.models.pie();
11163 var legend = nv.models.legend();
11165 var margin = {top: 30, right: 20, bottom: 20, left: 20}
11168 , showLegend = true
11169 , color = nv.utils.defaultColor()
11171 , tooltip = function(key, y, e, graph) {
11172 return '<h3 style="background-color: '
11173 + e.color + '">' + key + '</h3>'
11174 + '<p>' + y + '</p>';
11176 , state = nv.utils.state()
11177 , defaultState = null
11178 , noData = "No Data Available."
11180 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
11183 //============================================================
11184 // Private Variables
11185 //------------------------------------------------------------
11187 var showTooltip = function(e, offsetElement) {
11188 var tooltipLabel = pie.x()(e.point);
11189 var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
11190 top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
11191 y = pie.valueFormat()(pie.y()(e.point)),
11192 content = tooltip(tooltipLabel, y, e, chart)
11194 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
11197 var renderWatch = nv.utils.renderWatch(dispatch);
11199 var stateGetter = function(data) {
11202 active: data.map(function(d) { return !d.disabled })
11207 var stateSetter = function(data) {
11208 return function(state) {
11209 if (state.active !== undefined) {
11210 data.forEach(function (series, i) {
11211 series.disabled = !state.active[i];
11217 //============================================================
11219 //------------------------------------------------------------
11221 function chart(selection) {
11222 renderWatch.reset();
11223 renderWatch.models(pie);
11225 selection.each(function(data) {
11226 var container = d3.select(this);
11227 nv.utils.initSVG(container);
11230 var availableWidth = (width || parseInt(container.style('width'), 10) || 960)
11231 - margin.left - margin.right,
11232 availableHeight = (height || parseInt(container.style('height'), 10) || 400)
11233 - margin.top - margin.bottom
11236 chart.update = function() { container.transition().call(chart); };
11237 chart.container = this;
11239 state.setter(stateSetter(data), chart.update)
11240 .getter(stateGetter(data))
11243 //set state.disabled
11244 state.disabled = data.map(function(d) { return !!d.disabled });
11246 if (!defaultState) {
11249 for (key in state) {
11250 if (state[key] instanceof Array)
11251 defaultState[key] = state[key].slice(0);
11253 defaultState[key] = state[key];
11257 // Display No Data message if there's nothing to show.
11258 if (!data || !data.length) {
11259 var noDataText = container.selectAll('.nv-noData').data([noData]);
11261 noDataText.enter().append('text')
11262 .attr('class', 'nvd3 nv-noData')
11263 .attr('dy', '-.7em')
11264 .style('text-anchor', 'middle');
11267 .attr('x', margin.left + availableWidth / 2)
11268 .attr('y', margin.top + availableHeight / 2)
11269 .text(function(d) { return d });
11273 container.selectAll('.nv-noData').remove();
11276 // Setup containers and skeleton of chart
11277 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
11278 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
11279 var g = wrap.select('g');
11281 gEnter.append('g').attr('class', 'nv-pieWrap');
11282 gEnter.append('g').attr('class', 'nv-legendWrap');
11286 legend.width( availableWidth ).key(pie.x());
11288 wrap.select('.nv-legendWrap')
11292 if ( margin.top != legend.height()) {
11293 margin.top = legend.height();
11294 availableHeight = (height || parseInt(container.style('height')) || 400)
11295 - margin.top - margin.bottom;
11298 wrap.select('.nv-legendWrap')
11299 .attr('transform', 'translate(0,' + (-margin.top) +')');
11301 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11303 // Main Chart Component(s)
11304 pie.width(availableWidth).height(availableHeight);
11305 var pieWrap = g.select('.nv-pieWrap').datum([data]);
11306 d3.transition(pieWrap).call(pie);
11308 // Event Handling/Dispatching (in chart's scope)
11309 legend.dispatch.on('stateChange', function(newState) {
11310 for (var key in newState) {
11311 state[key] = newState[key];
11313 dispatch.stateChange(state);
11317 pie.dispatch.on('elementMouseout.tooltip', function(e) {
11318 dispatch.tooltipHide(e);
11321 // Update chart from a state object passed to event handler
11322 dispatch.on('changeState', function(e) {
11323 if (typeof e.disabled !== 'undefined') {
11324 data.forEach(function(series,i) {
11325 series.disabled = e.disabled[i];
11327 state.disabled = e.disabled;
11334 renderWatch.renderEnd('pieChart immediate');
11338 //============================================================
11339 // Event Handling/Dispatching (out of chart's scope)
11340 //------------------------------------------------------------
11342 pie.dispatch.on('elementMouseover.tooltip', function(e) {
11343 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11344 dispatch.tooltipShow(e);
11347 dispatch.on('tooltipShow', function(e) {
11348 if (tooltips) showTooltip(e);
11351 dispatch.on('tooltipHide', function() {
11352 if (tooltips) nv.tooltip.cleanup();
11355 //============================================================
11356 // Expose Public Variables
11357 //------------------------------------------------------------
11359 // expose chart's sub-components
11360 chart.legend = legend;
11361 chart.dispatch = dispatch;
11363 chart.options = nv.utils.optionsFunc.bind(chart);
11365 // use Object get/set functionality to map between vars and chart functions
11366 chart._options = Object.create({}, {
11367 // simple options, just get/set the necessary values
11368 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
11369 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
11370 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
11371 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11372 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
11373 // options that require extra logic in the setter
11374 color: {get: function(){return color;}, set: function(_){
11376 legend.color(color);
11379 duration: {get: function(){return duration;}, set: function(_){
11381 renderWatch.reset(duration);
11384 nv.utils.inheritOptions(chart, pie);
11385 nv.utils.initOptions(chart);
11389 nv.models.scatter = function() {
11391 //============================================================
11392 // Public Variables with Default Settings
11393 //------------------------------------------------------------
11395 var margin = {top: 0, right: 0, bottom: 0, left: 0}
11398 , color = nv.utils.defaultColor() // chooses color
11399 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
11400 , x = d3.scale.linear()
11401 , y = d3.scale.linear()
11402 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
11403 , getX = function(d) { return d.x } // accessor to get the x value
11404 , getY = function(d) { return d.y } // accessor to get the y value
11405 , getSize = function(d) { return d.size || 1} // accessor to get the point size
11406 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
11407 , onlyCircles = true // Set to false to use shapes
11408 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
11409 , forceY = [] // List of numbers to Force into the Y scale
11410 , forceSize = [] // List of numbers to Force into the Size scale
11411 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
11413 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
11414 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
11415 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
11416 , clipEdge = false // if true, masks points within x and y scale
11417 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
11418 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
11419 , xDomain = null // Override x domain (skips the calculation from data)
11420 , yDomain = null // Override y domain
11421 , xRange = null // Override x range
11422 , yRange = null // Override y range
11423 , sizeDomain = null // Override point size domain
11425 , singlePoint = false
11426 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
11427 , useVoronoi = true
11431 //============================================================
11434 //============================================================
11435 // Private Variables
11436 //------------------------------------------------------------
11438 var x0, y0, z0 // used to store previous scales
11440 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
11441 , renderWatch = nv.utils.renderWatch(dispatch, duration)
11444 //============================================================
11447 function chart(selection) {
11448 renderWatch.reset();
11449 selection.each(function(data) {
11450 var availableWidth = width - margin.left - margin.right,
11451 availableHeight = height - margin.top - margin.bottom,
11452 container = d3.select(this);
11453 nv.utils.initSVG(container);
11455 //add series index to each data point for reference
11456 data.forEach(function(series, i) {
11457 series.values.forEach(function(point) {
11462 //------------------------------------------------------------
11465 // remap and flatten the data for use in calculating the scales' domains
11466 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
11468 data.map(function(d) {
11469 return d.values.map(function(d,i) {
11470 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
11475 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
11477 if (padData && data[0])
11478 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
11479 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
11481 x.range(xRange || [0, availableWidth]);
11483 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
11484 .range(yRange || [availableHeight, 0]);
11486 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
11487 .range(sizeRange || [16, 256]);
11489 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
11490 if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
11491 if (x.domain()[0] === x.domain()[1])
11493 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
11494 : x.domain([-1,1]);
11496 if (y.domain()[0] === y.domain()[1])
11498 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
11499 : y.domain([-1,1]);
11501 if ( isNaN(x.domain()[0])) {
11505 if ( isNaN(y.domain()[0])) {
11514 //------------------------------------------------------------
11517 //------------------------------------------------------------
11518 // Setup containers and skeleton of chart
11520 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
11521 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
11522 var defsEnter = wrapEnter.append('defs');
11523 var gEnter = wrapEnter.append('g');
11524 var g = wrap.select('g');
11526 gEnter.append('g').attr('class', 'nv-groups');
11527 gEnter.append('g').attr('class', 'nv-point-paths');
11529 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11531 //------------------------------------------------------------
11534 defsEnter.append('clipPath')
11535 .attr('id', 'nv-edge-clip-' + id)
11538 wrap.select('#nv-edge-clip-' + id + ' rect')
11539 .attr('width', availableWidth)
11540 .attr('height', (availableHeight > 0) ? availableHeight : 0);
11542 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
11545 function updateInteractiveLayer() {
11547 if (!interactive) return false;
11551 var vertices = d3.merge(data.map(function(group, groupIndex) {
11552 return group.values
11553 .map(function(point, pointIndex) {
11554 // *Adding noise to make duplicates very unlikely
11555 // *Injecting series and point index for reference
11556 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
11558 var pX = getX(point,pointIndex);
11559 var pY = getY(point,pointIndex);
11561 return [x(pX)+ Math.random() * 1e-7,
11562 y(pY)+ Math.random() * 1e-7,
11564 pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
11566 .filter(function(pointArray, pointIndex) {
11567 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
11574 //inject series and point index for reference into voronoi
11575 if (useVoronoi === true) {
11578 var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
11582 pointClipsEnter.append('clipPath')
11583 .attr('class', 'nv-point-clips')
11584 .attr('id', 'nv-points-clip-' + id);
11586 var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
11588 pointClips.enter().append('circle')
11589 .attr('r', clipRadius);
11590 pointClips.exit().remove();
11592 .attr('cx', function(d) { return d[0] })
11593 .attr('cy', function(d) { return d[1] });
11595 wrap.select('.nv-point-paths')
11596 .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
11600 if(vertices.length) {
11601 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
11602 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
11603 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11604 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11605 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11608 var bounds = d3.geom.polygon([
11611 [width + 10,height + 10],
11615 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11617 'data': bounds.clip(d),
11618 'series': vertices[i][2],
11619 'point': vertices[i][3]
11624 var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11626 pointPaths.enter().append('path')
11627 .attr('class', function(d,i) { return 'nv-path-'+i; });
11628 pointPaths.exit().remove();
11630 .attr('d', function(d) {
11631 if (!d || !d.data || d.data.length === 0)
11634 return 'M' + d.data.join('L') + 'Z';
11637 var mouseEventCallback = function(d,mDispatch) {
11638 if (needsUpdate) return 0;
11639 var series = data[d.series];
11640 if (typeof series === 'undefined') return;
11642 var point = series.values[d.point];
11647 pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11648 seriesIndex: d.series,
11649 pointIndex: d.point
11654 .on('click', function(d) {
11655 mouseEventCallback(d, dispatch.elementClick);
11657 .on('dblclick', function(d) {
11658 mouseEventCallback(d, dispatch.elementDblClick);
11660 .on('mouseover', function(d) {
11661 mouseEventCallback(d, dispatch.elementMouseover);
11663 .on('mouseout', function(d, i) {
11664 mouseEventCallback(d, dispatch.elementMouseout);
11670 // bring data in form needed for click handlers
11671 var dataWithPoints = vertices.map(function(d, i) {
11674 'series': vertices[i][2],
11675 'point': vertices[i][3]
11680 // add event handlers to points instead voronoi paths
11681 wrap.select('.nv-groups').selectAll('.nv-group')
11682 .selectAll('.nv-point')
11683 //.data(dataWithPoints)
11684 //.style('pointer-events', 'auto') // recativate events, disabled by css
11685 .on('click', function(d,i) {
11686 //nv.log('test', d, i);
11687 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11688 var series = data[d.series],
11689 point = series.values[i];
11691 dispatch.elementClick({
11694 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11695 seriesIndex: d.series,
11699 .on('mouseover', function(d,i) {
11700 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11701 var series = data[d.series],
11702 point = series.values[i];
11704 dispatch.elementMouseover({
11707 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11708 seriesIndex: d.series,
11712 .on('mouseout', function(d,i) {
11713 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11714 var series = data[d.series],
11715 point = series.values[i];
11717 dispatch.elementMouseout({
11720 seriesIndex: d.series,
11726 needsUpdate = false;
11729 needsUpdate = true;
11730 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11731 .data(function(d) { return d }, function(d) { return d.key });
11732 groups.enter().append('g')
11733 .style('stroke-opacity', 1e-6)
11734 .style('fill-opacity', 1e-6);
11738 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11739 .classed('hover', function(d) { return d.hover });
11740 groups.watchTransition(renderWatch, 'scatter: groups')
11741 .style('fill', function(d,i) { return color(d, i) })
11742 .style('stroke', function(d,i) { return color(d, i) })
11743 .style('stroke-opacity', 1)
11744 .style('fill-opacity', .5);
11748 var points = groups.selectAll('circle.nv-point')
11749 .data(function(d) { return d.values }, pointKey);
11750 points.enter().append('circle')
11751 .style('fill', function (d,i) { return d.color })
11752 .style('stroke', function (d,i) { return d.color })
11753 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11754 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11755 .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11756 points.exit().remove();
11757 groups.exit().selectAll('path.nv-point')
11758 .watchTransition(renderWatch, 'scatter exit')
11759 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11760 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11762 points.each(function(d,i) {
11764 .classed('nv-point', true)
11765 .classed('nv-point-' + i, true)
11766 .classed('hover',false)
11770 .watchTransition(renderWatch, 'scatter points')
11771 .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11772 .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11773 .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11777 var points = groups.selectAll('path.nv-point')
11778 .data(function(d) { return d.values });
11779 points.enter().append('path')
11780 .style('fill', function (d,i) { return d.color })
11781 .style('stroke', function (d,i) { return d.color })
11782 .attr('transform', function(d,i) {
11783 return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11788 .size(function(d,i) { return z(getSize(d,i)) })
11790 points.exit().remove();
11791 groups.exit().selectAll('path.nv-point')
11792 .watchTransition(renderWatch, 'scatter exit')
11793 .attr('transform', function(d,i) {
11794 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11797 points.each(function(d,i) {
11799 .classed('nv-point', true)
11800 .classed('nv-point-' + i, true)
11801 .classed('hover',false)
11805 .watchTransition(renderWatch, 'scatter points')
11806 .attr('transform', function(d,i) {
11807 //nv.log(d,i,getX(d,i), x(getX(d,i)));
11808 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11813 .size(function(d,i) { return z(getSize(d,i)) })
11818 // Delay updating the invisible interactive layer for smoother animation
11819 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11820 timeoutID = setTimeout(updateInteractiveLayer, 300);
11821 //updateInteractiveLayer();
11823 //store old scales for use in transitions on update
11829 renderWatch.renderEnd('scatter immediate');
11834 //============================================================
11835 // Event Handling/Dispatching (out of chart's scope)
11836 //------------------------------------------------------------
11837 chart.clearHighlights = function() {
11838 //Remove the 'hover' class from all highlighted points.
11839 d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11842 chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11843 d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11844 .classed("hover",isHoverOver);
11848 dispatch.on('elementMouseover.point', function(d) {
11849 if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11852 dispatch.on('elementMouseout.point', function(d) {
11853 if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11856 //============================================================
11859 //============================================================
11860 // Expose Public Variables
11861 //------------------------------------------------------------
11863 chart.dispatch = dispatch;
11864 chart.options = nv.utils.optionsFunc.bind(chart);
11866 chart.x = function(_) {
11867 if (!arguments.length) return getX;
11868 getX = d3.functor(_);
11872 chart.y = function(_) {
11873 if (!arguments.length) return getY;
11874 getY = d3.functor(_);
11878 chart.size = function(_) {
11879 if (!arguments.length) return getSize;
11880 getSize = d3.functor(_);
11884 chart.margin = function(_) {
11885 if (!arguments.length) return margin;
11886 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11887 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11888 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11889 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11893 chart.width = function(_) {
11894 if (!arguments.length) return width;
11899 chart.height = function(_) {
11900 if (!arguments.length) return height;
11905 chart.xScale = function(_) {
11906 if (!arguments.length) return x;
11911 chart.yScale = function(_) {
11912 if (!arguments.length) return y;
11917 chart.zScale = function(_) {
11918 if (!arguments.length) return z;
11923 chart.xDomain = function(_) {
11924 if (!arguments.length) return xDomain;
11929 chart.yDomain = function(_) {
11930 if (!arguments.length) return yDomain;
11935 chart.sizeDomain = function(_) {
11936 if (!arguments.length) return sizeDomain;
11941 chart.xRange = function(_) {
11942 if (!arguments.length) return xRange;
11947 chart.yRange = function(_) {
11948 if (!arguments.length) return yRange;
11953 chart.sizeRange = function(_) {
11954 if (!arguments.length) return sizeRange;
11959 chart.forceX = function(_) {
11960 if (!arguments.length) return forceX;
11965 chart.forceY = function(_) {
11966 if (!arguments.length) return forceY;
11971 chart.forceSize = function(_) {
11972 if (!arguments.length) return forceSize;
11977 chart.interactive = function(_) {
11978 if (!arguments.length) return interactive;
11983 chart.pointKey = function(_) {
11984 if (!arguments.length) return pointKey;
11989 chart.pointActive = function(_) {
11990 if (!arguments.length) return pointActive;
11995 chart.padData = function(_) {
11996 if (!arguments.length) return padData;
12001 chart.padDataOuter = function(_) {
12002 if (!arguments.length) return padDataOuter;
12007 chart.clipEdge = function(_) {
12008 if (!arguments.length) return clipEdge;
12013 chart.clipVoronoi= function(_) {
12014 if (!arguments.length) return clipVoronoi;
12019 chart.useVoronoi= function(_) {
12020 if (!arguments.length) return useVoronoi;
12022 if (useVoronoi === false) {
12023 clipVoronoi = false;
12028 chart.clipRadius = function(_) {
12029 if (!arguments.length) return clipRadius;
12034 chart.color = function(_) {
12035 if (!arguments.length) return color;
12036 color = nv.utils.getColor(_);
12040 chart.shape = function(_) {
12041 if (!arguments.length) return getShape;
12046 chart.onlyCircles = function(_) {
12047 if (!arguments.length) return onlyCircles;
12052 chart.id = function(_) {
12053 if (!arguments.length) return id;
12058 chart.singlePoint = function(_) {
12059 if (!arguments.length) return singlePoint;
12064 chart.duration = function(_) {
12065 if (!arguments.length) return duration;
12067 renderWatch.reset(duration);
12071 //============================================================
12076 nv.models.scatterChart = function() {
12078 //============================================================
12079 // Public Variables with Default Settings
12080 //------------------------------------------------------------
12082 var scatter = nv.models.scatter()
12083 , xAxis = nv.models.axis()
12084 , yAxis = nv.models.axis()
12085 , legend = nv.models.legend()
12086 , controls = nv.models.legend()
12087 , distX = nv.models.distribution()
12088 , distY = nv.models.distribution()
12091 var margin = {top: 30, right: 20, bottom: 50, left: 75}
12094 , color = nv.utils.defaultColor()
12095 , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12096 , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12099 , showDistX = false
12100 , showDistY = false
12101 , showLegend = true
12104 , rightAlignYAxis = false
12105 , showControls = !!d3.fisheye
12107 , pauseFisheye = false
12109 , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
12110 , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
12112 , state = nv.utils.state()
12113 , defaultState = null
12114 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
12115 , noData = "No Data Available."
12116 , transitionDuration = 250
12129 .orient((rightAlignYAxis) ? 'right' : 'left')
12139 controls.updateState(false);
12141 //============================================================
12144 //============================================================
12145 // Private Variables
12146 //------------------------------------------------------------
12149 var renderWatch = nv.utils.renderWatch(dispatch, duration);
12151 var showTooltip = function(e, offsetElement) {
12152 //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12154 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12155 top = e.pos[1] + ( offsetElement.offsetTop || 0),
12156 leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12157 topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12158 leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12159 topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12160 xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12161 yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12163 if( tooltipX != null )
12164 nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12165 if( tooltipY != null )
12166 nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12167 if( tooltip != null )
12168 nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12171 var controlsData = [
12172 { key: 'Magnify', disabled: true }
12175 var stateGetter = function(data) {
12178 active: data.map(function(d) { return !d.disabled })
12183 var stateSetter = function(data) {
12184 return function(state) {
12185 if (state.active !== undefined)
12186 data.forEach(function(series,i) {
12187 series.disabled = !state.active[i];
12192 //============================================================
12195 function chart(selection) {
12196 renderWatch.reset();
12197 renderWatch.models(scatter);
12198 if (showXAxis) renderWatch.models(xAxis);
12199 if (showYAxis) renderWatch.models(yAxis);
12200 if (showDistX) renderWatch.models(distX);
12201 if (showDistY) renderWatch.models(distY);
12203 selection.each(function(data) {
12204 var container = d3.select(this),
12206 nv.utils.initSVG(container);
12208 var availableWidth = (width || parseInt(container.style('width')) || 960)
12209 - margin.left - margin.right,
12210 availableHeight = (height || parseInt(container.style('height')) || 400)
12211 - margin.top - margin.bottom;
12213 chart.update = function() {
12214 if (duration === 0)
12215 container.call(chart);
12217 container.transition().duration(duration).call(chart);
12219 chart.container = this;
12223 .setter(stateSetter(data), chart.update)
12224 .getter(stateGetter(data))
12227 // DEPRECATED set state.disabled
12228 state.disabled = data.map(function(d) { return !!d.disabled });
12230 if (!defaultState) {
12233 for (key in state) {
12234 if (state[key] instanceof Array)
12235 defaultState[key] = state[key].slice(0);
12237 defaultState[key] = state[key];
12241 //------------------------------------------------------------
12242 // Display noData message if there's nothing to show.
12244 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12245 var noDataText = container.selectAll('.nv-noData').data([noData]);
12247 noDataText.enter().append('text')
12248 .attr('class', 'nvd3 nv-noData')
12249 .attr('dy', '-.7em')
12250 .style('text-anchor', 'middle');
12253 .attr('x', margin.left + availableWidth / 2)
12254 .attr('y', margin.top + availableHeight / 2)
12255 .text(function(d) { return d });
12259 container.selectAll('.nv-noData').remove();
12262 //------------------------------------------------------------
12265 //------------------------------------------------------------
12271 //------------------------------------------------------------
12274 //------------------------------------------------------------
12275 // Setup containers and skeleton of chart
12277 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12278 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12279 var gEnter = wrapEnter.append('g');
12280 var g = wrap.select('g');
12282 // background for pointer events
12283 gEnter.append('rect').attr('class', 'nvd3 nv-background');
12285 gEnter.append('g').attr('class', 'nv-x nv-axis');
12286 gEnter.append('g').attr('class', 'nv-y nv-axis');
12287 gEnter.append('g').attr('class', 'nv-scatterWrap');
12288 gEnter.append('g').attr('class', 'nv-distWrap');
12289 gEnter.append('g').attr('class', 'nv-legendWrap');
12290 gEnter.append('g').attr('class', 'nv-controlsWrap');
12292 //------------------------------------------------------------
12295 //------------------------------------------------------------
12299 var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
12300 legend.width(legendWidth);
12302 wrap.select('.nv-legendWrap')
12306 if ( margin.top != legend.height()) {
12307 margin.top = legend.height();
12308 availableHeight = (height || parseInt(container.style('height')) || 400)
12309 - margin.top - margin.bottom;
12312 wrap.select('.nv-legendWrap')
12313 .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
12316 //------------------------------------------------------------
12319 //------------------------------------------------------------
12322 if (showControls) {
12323 controls.width(180).color(['#444']);
12324 g.select('.nv-controlsWrap')
12325 .datum(controlsData)
12326 .attr('transform', 'translate(0,' + (-margin.top) +')')
12330 //------------------------------------------------------------
12333 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12335 if (rightAlignYAxis) {
12336 g.select(".nv-y.nv-axis")
12337 .attr("transform", "translate(" + availableWidth + ",0)");
12340 //------------------------------------------------------------
12341 // Main Chart Component(s)
12344 .width(availableWidth)
12345 .height(availableHeight)
12346 .color(data.map(function(d,i) {
12347 return d.color || color(d, i);
12348 }).filter(function(d,i) { return !data[i].disabled }));
12350 if (xPadding !== 0)
12351 scatter.xDomain(null);
12353 if (yPadding !== 0)
12354 scatter.yDomain(null);
12355 wrap.select('.nv-scatterWrap')
12356 .datum(data.filter(function(d) { return !d.disabled }))
12359 //Adjust for x and y padding
12360 if (xPadding !== 0) {
12361 var xRange = x.domain()[1] - x.domain()[0];
12362 scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
12365 if (yPadding !== 0) {
12366 var yRange = y.domain()[1] - y.domain()[0];
12367 scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
12370 //Only need to update the scatter again if x/yPadding changed the domain.
12371 if (yPadding !== 0 || xPadding !== 0) {
12372 wrap.select('.nv-scatterWrap')
12373 .datum(data.filter(function(d) { return !d.disabled }))
12377 //------------------------------------------------------------
12380 //------------------------------------------------------------
12385 .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
12386 .tickSize( -availableHeight , 0);
12388 g.select('.nv-x.nv-axis')
12389 .attr('transform', 'translate(0,' + y.range()[0] + ')')
12397 .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
12398 .tickSize( -availableWidth, 0);
12400 g.select('.nv-y.nv-axis')
12407 .getData(scatter.x())
12409 .width(availableWidth)
12410 .color(data.map(function(d,i) {
12411 return d.color || color(d, i);
12412 }).filter(function(d,i) { return !data[i].disabled }));
12413 gEnter.select('.nv-distWrap').append('g')
12414 .attr('class', 'nv-distributionX');
12415 g.select('.nv-distributionX')
12416 .attr('transform', 'translate(0,' + y.range()[0] + ')')
12417 .datum(data.filter(function(d) { return !d.disabled }))
12423 .getData(scatter.y())
12425 .width(availableHeight)
12426 .color(data.map(function(d,i) {
12427 return d.color || color(d, i);
12428 }).filter(function(d,i) { return !data[i].disabled }));
12429 gEnter.select('.nv-distWrap').append('g')
12430 .attr('class', 'nv-distributionY');
12431 g.select('.nv-distributionY')
12433 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12434 .datum(data.filter(function(d) { return !d.disabled }))
12438 //------------------------------------------------------------
12444 g.select('.nv-background')
12445 .attr('width', availableWidth)
12446 .attr('height', availableHeight);
12448 g.select('.nv-background').on('mousemove', updateFisheye);
12449 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12450 scatter.dispatch.on('elementClick.freezeFisheye', function() {
12451 pauseFisheye = !pauseFisheye;
12456 function updateFisheye() {
12457 if (pauseFisheye) {
12458 g.select('.nv-point-paths').style('pointer-events', 'all');
12462 g.select('.nv-point-paths').style('pointer-events', 'none' );
12464 var mouse = d3.mouse(this);
12465 x.distortion(fisheye).focus(mouse[0]);
12466 y.distortion(fisheye).focus(mouse[1]);
12468 g.select('.nv-scatterWrap')
12472 g.select('.nv-x.nv-axis').call(xAxis);
12475 g.select('.nv-y.nv-axis').call(yAxis);
12477 g.select('.nv-distributionX')
12478 .datum(data.filter(function(d) { return !d.disabled }))
12480 g.select('.nv-distributionY')
12481 .datum(data.filter(function(d) { return !d.disabled }))
12487 //============================================================
12488 // Event Handling/Dispatching (in chart's scope)
12489 //------------------------------------------------------------
12491 controls.dispatch.on('legendClick', function(d,i) {
12492 d.disabled = !d.disabled;
12494 fisheye = d.disabled ? 0 : 2.5;
12495 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12496 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12499 x.distortion(fisheye).focus(0);
12500 y.distortion(fisheye).focus(0);
12502 g.select('.nv-scatterWrap').call(scatter);
12503 g.select('.nv-x.nv-axis').call(xAxis);
12504 g.select('.nv-y.nv-axis').call(yAxis);
12506 pauseFisheye = false;
12509 dispatch.stateChange(state);
12514 legend.dispatch.on('stateChange', function(newState) {
12515 for (var key in newState)
12516 state[key] = newState[key];
12517 dispatch.stateChange(state);
12521 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12522 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12523 .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
12524 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12525 .attr('x2', e.pos[0] + distX.size());
12527 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12528 dispatch.tooltipShow(e);
12531 dispatch.on('tooltipShow', function(e) {
12532 if (tooltips) showTooltip(e, that.parentNode);
12535 // Update chart from a state object passed to event handler
12536 dispatch.on('changeState', function(e) {
12538 if (typeof e.disabled !== 'undefined') {
12539 data.forEach(function(series,i) {
12540 series.disabled = e.disabled[i];
12543 state.disabled = e.disabled;
12549 //============================================================
12552 //store old scales for use in transitions on update
12558 renderWatch.renderEnd('scatterChart immediate');
12563 //============================================================
12564 // Event Handling/Dispatching (out of chart's scope)
12565 //------------------------------------------------------------
12567 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12568 dispatch.tooltipHide(e);
12570 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12572 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12573 .attr('x2', distY.size());
12575 dispatch.on('tooltipHide', function() {
12576 if (tooltips) nv.tooltip.cleanup();
12579 //============================================================
12582 //============================================================
12583 // Expose Public Variables
12584 //------------------------------------------------------------
12586 // expose chart's sub-components
12587 chart.dispatch = dispatch;
12588 chart.scatter = scatter;
12589 chart.legend = legend;
12590 chart.controls = controls;
12591 chart.xAxis = xAxis;
12592 chart.yAxis = yAxis;
12593 chart.distX = distX;
12594 chart.distY = distY;
12595 // DO NOT DELETE. This is currently overridden below
12596 // until deprecated portions are removed.
12597 chart.state = state;
12599 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');
12600 chart.options = nv.utils.optionsFunc.bind(chart);
12602 chart.margin = function(_) {
12603 if (!arguments.length) return margin;
12604 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12605 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12606 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12607 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12611 chart.width = function(_) {
12612 if (!arguments.length) return width;
12617 chart.height = function(_) {
12618 if (!arguments.length) return height;
12623 chart.color = function(_) {
12624 if (!arguments.length) return color;
12625 color = nv.utils.getColor(_);
12626 legend.color(color);
12627 distX.color(color);
12628 distY.color(color);
12632 chart.showDistX = function(_) {
12633 if (!arguments.length) return showDistX;
12638 chart.showDistY = function(_) {
12639 if (!arguments.length) return showDistY;
12644 chart.showControls = function(_) {
12645 if (!arguments.length) return showControls;
12650 chart.showLegend = function(_) {
12651 if (!arguments.length) return showLegend;
12656 chart.showXAxis = function(_) {
12657 if (!arguments.length) return showXAxis;
12662 chart.showYAxis = function(_) {
12663 if (!arguments.length) return showYAxis;
12668 chart.rightAlignYAxis = function(_) {
12669 if(!arguments.length) return rightAlignYAxis;
12670 rightAlignYAxis = _;
12671 yAxis.orient( (_) ? 'right' : 'left');
12676 chart.fisheye = function(_) {
12677 if (!arguments.length) return fisheye;
12682 chart.xPadding = function(_) {
12683 if (!arguments.length) return xPadding;
12688 chart.yPadding = function(_) {
12689 if (!arguments.length) return yPadding;
12694 chart.tooltips = function(_) {
12695 if (!arguments.length) return tooltips;
12700 chart.tooltipContent = function(_) {
12701 if (!arguments.length) return tooltip;
12706 chart.tooltipXContent = function(_) {
12707 if (!arguments.length) return tooltipX;
12712 chart.tooltipYContent = function(_) {
12713 if (!arguments.length) return tooltipY;
12719 chart.state = function(_) {
12720 nv.deprecated('scatterChart.state');
12721 if (!arguments.length) return state;
12725 for (var key in state) {
12726 chart.state[key] = state[key];
12730 chart.defaultState = function(_) {
12731 if (!arguments.length) return defaultState;
12736 chart.noData = function(_) {
12737 if (!arguments.length) return noData;
12741 chart.duration = function(_) {
12742 if (!arguments.length) return duration;
12744 renderWatch.reset(duration);
12745 scatter.duration(duration);
12746 xAxis.duration(duration);
12747 yAxis.duration(duration);
12748 distX.duration(duration);
12749 distY.duration(duration);
12752 chart.transitionDuration = function(_) {
12753 nv.deprecated('scatterChart.transitionDuration');
12754 return chart.duration(_);
12757 //============================================================
12763 nv.models.scatterPlusLineChart = function() {
12765 //============================================================
12766 // Public Variables with Default Settings
12767 //------------------------------------------------------------
12769 var scatter = nv.models.scatter()
12770 , xAxis = nv.models.axis()
12771 , yAxis = nv.models.axis()
12772 , legend = nv.models.legend()
12773 , controls = nv.models.legend()
12774 , distX = nv.models.distribution()
12775 , distY = nv.models.distribution()
12778 var margin = {top: 30, right: 20, bottom: 50, left: 75}
12781 , color = nv.utils.defaultColor()
12782 , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12783 , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12784 , showDistX = false
12785 , showDistY = false
12786 , showLegend = true
12789 , rightAlignYAxis = false
12790 , showControls = !!d3.fisheye
12792 , pauseFisheye = false
12794 , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
12795 , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
12796 , tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
12797 + '<p>' + date + '</p>' }
12798 , state = nv.utils.state()
12799 , defaultState = null
12800 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
12801 , noData = "No Data Available."
12805 state.fisheye = 0; // DEPRECATED Maintained for backward compatibility
12816 .orient((rightAlignYAxis) ? 'right' : 'left')
12826 controls.updateState(false);
12827 //============================================================
12830 //============================================================
12831 // Private Variables
12832 //------------------------------------------------------------
12835 , renderWatch = nv.utils.renderWatch(dispatch, duration);
12837 var showTooltip = function(e, offsetElement) {
12838 //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12840 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12841 top = e.pos[1] + ( offsetElement.offsetTop || 0),
12842 leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12843 topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12844 leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12845 topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12846 xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12847 yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12849 if( tooltipX != null )
12850 nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12851 if( tooltipY != null )
12852 nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12853 if( tooltip != null )
12854 nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12857 var controlsData = [
12858 { key: 'Magnify', disabled: true }
12861 var stateGetter = function(data) {
12864 active: data.map(function(d) { return !d.disabled }),
12870 var stateSetter = function(data) {
12871 return function(state) {
12872 if (state.fisheye !== undefined)
12873 fisheye = state.fisheye;
12874 if (state.active !== undefined)
12875 data.forEach(function(series,i) {
12876 series.disabled = !state.active[i];
12881 //============================================================
12884 function chart(selection) {
12885 renderWatch.reset();
12886 renderWatch.models(scatter);
12887 if (showXAxis) renderWatch.models(xAxis);
12888 if (showYAxis) renderWatch.models(yAxis);
12889 if (showDistX) renderWatch.models(distX);
12890 if (showDistY) renderWatch.models(distY);
12892 selection.each(function(data) {
12893 var container = d3.select(this),
12895 nv.utils.initSVG(container);
12897 var availableWidth = (width || parseInt(container.style('width')) || 960)
12898 - margin.left - margin.right,
12899 availableHeight = (height || parseInt(container.style('height')) || 400)
12900 - margin.top - margin.bottom;
12902 chart.update = function() {
12903 if (duration === 0)
12904 container.call(chart);
12906 container.transition().duration(duration).call(chart);
12908 chart.container = this;
12911 .setter(stateSetter(data), chart.update)
12912 .getter(stateGetter(data))
12915 // DEPRECATED set state.disableddisabled
12916 state.disabled = data.map(function(d) { return !!d.disabled });
12918 if (!defaultState) {
12921 for (key in state) {
12922 if (state[key] instanceof Array)
12923 defaultState[key] = state[key].slice(0);
12925 defaultState[key] = state[key];
12929 //------------------------------------------------------------
12930 // Display noData message if there's nothing to show.
12932 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12933 var noDataText = container.selectAll('.nv-noData').data([noData]);
12935 noDataText.enter().append('text')
12936 .attr('class', 'nvd3 nv-noData')
12937 .attr('dy', '-.7em')
12938 .style('text-anchor', 'middle');
12941 .attr('x', margin.left + availableWidth / 2)
12942 .attr('y', margin.top + availableHeight / 2)
12943 .text(function(d) { return d });
12945 renderWatch.renderEnd('scatter immediate');
12949 container.selectAll('.nv-noData').remove();
12952 //------------------------------------------------------------
12955 //------------------------------------------------------------
12958 x = scatter.xScale();
12959 y = scatter.yScale();
12964 //------------------------------------------------------------
12967 //------------------------------------------------------------
12968 // Setup containers and skeleton of chart
12970 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12971 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12972 var gEnter = wrapEnter.append('g');
12973 var g = wrap.select('g');
12975 // background for pointer events
12976 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12978 gEnter.append('g').attr('class', 'nv-x nv-axis');
12979 gEnter.append('g').attr('class', 'nv-y nv-axis');
12980 gEnter.append('g').attr('class', 'nv-scatterWrap');
12981 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12982 gEnter.append('g').attr('class', 'nv-distWrap');
12983 gEnter.append('g').attr('class', 'nv-legendWrap');
12984 gEnter.append('g').attr('class', 'nv-controlsWrap');
12986 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12988 if (rightAlignYAxis) {
12989 g.select(".nv-y.nv-axis")
12990 .attr("transform", "translate(" + availableWidth + ",0)");
12993 //------------------------------------------------------------
12996 //------------------------------------------------------------
13000 legend.width( availableWidth / 2 );
13002 wrap.select('.nv-legendWrap')
13006 if ( margin.top != legend.height()) {
13007 margin.top = legend.height();
13008 availableHeight = (height || parseInt(container.style('height')) || 400)
13009 - margin.top - margin.bottom;
13012 wrap.select('.nv-legendWrap')
13013 .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
13016 //------------------------------------------------------------
13019 //------------------------------------------------------------
13022 if (showControls) {
13023 controls.width(180).color(['#444']);
13024 g.select('.nv-controlsWrap')
13025 .datum(controlsData)
13026 .attr('transform', 'translate(0,' + (-margin.top) +')')
13030 //------------------------------------------------------------
13033 //------------------------------------------------------------
13034 // Main Chart Component(s)
13037 .width(availableWidth)
13038 .height(availableHeight)
13039 .color(data.map(function(d,i) {
13040 return d.color || color(d, i);
13041 }).filter(function(d,i) { return !data[i].disabled }));
13043 wrap.select('.nv-scatterWrap')
13044 .datum(data.filter(function(d) { return !d.disabled }))
13047 wrap.select('.nv-regressionLinesWrap')
13048 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
13050 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
13051 .data(function(d) {return d });
13053 regWrap.enter().append('g').attr('class', 'nv-regLines');
13055 var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
13056 var regLineEnter = regLine.enter()
13057 .append('line').attr('class', 'nv-regLine')
13058 .style('stroke-opacity', 0);
13061 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
13062 .attr('x1', x.range()[0])
13063 .attr('x2', x.range()[1])
13064 .attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
13065 .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
13066 .style('stroke', function(d,i,j) { return color(d,j) })
13067 .style('stroke-opacity', function(d,i) {
13068 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
13071 //------------------------------------------------------------
13074 //------------------------------------------------------------
13080 .ticks( xAxis.ticks() ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
13081 .tickSize( -availableHeight , 0);
13083 g.select('.nv-x.nv-axis')
13084 .attr('transform', 'translate(0,' + y.range()[0] + ')')
13091 .ticks( yAxis.ticks() ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
13092 .tickSize( -availableWidth, 0);
13094 g.select('.nv-y.nv-axis')
13101 .getData(scatter.x())
13103 .width(availableWidth)
13104 .color(data.map(function(d,i) {
13105 return d.color || color(d, i);
13106 }).filter(function(d,i) { return !data[i].disabled }));
13107 gEnter.select('.nv-distWrap').append('g')
13108 .attr('class', 'nv-distributionX');
13109 g.select('.nv-distributionX')
13110 .attr('transform', 'translate(0,' + y.range()[0] + ')')
13111 .datum(data.filter(function(d) { return !d.disabled }))
13117 .getData(scatter.y())
13119 .width(availableHeight)
13120 .color(data.map(function(d,i) {
13121 return d.color || color(d, i);
13122 }).filter(function(d,i) { return !data[i].disabled }));
13123 gEnter.select('.nv-distWrap').append('g')
13124 .attr('class', 'nv-distributionY');
13125 g.select('.nv-distributionY')
13126 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
13127 .datum(data.filter(function(d) { return !d.disabled }))
13131 //------------------------------------------------------------
13137 g.select('.nv-background')
13138 .attr('width', availableWidth)
13139 .attr('height', availableHeight)
13142 g.select('.nv-background').on('mousemove', updateFisheye);
13143 g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
13144 scatter.dispatch.on('elementClick.freezeFisheye', function() {
13145 pauseFisheye = !pauseFisheye;
13149 // At this point, everything has been selected and bound... I think
13152 function updateFisheye() {
13153 if (pauseFisheye) {
13154 g.select('.nv-point-paths').style('pointer-events', 'all');
13158 g.select('.nv-point-paths').style('pointer-events', 'none' );
13160 var mouse = d3.mouse(this);
13161 x.distortion(fisheye).focus(mouse[0]);
13162 y.distortion(fisheye).focus(mouse[1]);
13164 g.select('.nv-scatterWrap')
13165 .datum(data.filter(function(d) { return !d.disabled }))
13169 g.select('.nv-x.nv-axis').call(xAxis);
13172 g.select('.nv-y.nv-axis').call(yAxis);
13174 g.select('.nv-distributionX')
13175 .datum(data.filter(function(d) { return !d.disabled }))
13177 g.select('.nv-distributionY')
13178 .datum(data.filter(function(d) { return !d.disabled }))
13184 //============================================================
13185 // Event Handling/Dispatching (in chart's scope)
13186 //------------------------------------------------------------
13188 controls.dispatch.on('legendClick', function(d,i) {
13189 d.disabled = !d.disabled;
13191 fisheye = d.disabled ? 0 : 2.5;
13192 g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
13193 g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
13196 x.distortion(fisheye).focus(0);
13197 y.distortion(fisheye).focus(0);
13199 g.select('.nv-scatterWrap').call(scatter);
13200 g.select('.nv-x.nv-axis').call(xAxis);
13201 g.select('.nv-y.nv-axis').call(yAxis);
13203 pauseFisheye = false;
13206 state.fisheye = fisheye;
13207 dispatch.stateChange(state);
13212 legend.dispatch.on('stateChange', function(newState) {
13213 for (var key in newState)
13214 state[key] = newState[key];
13215 dispatch.stateChange(state);
13220 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13221 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
13222 .attr('y1', e.pos[1] - availableHeight);
13223 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
13224 .attr('x2', e.pos[0] + distX.size());
13226 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
13227 dispatch.tooltipShow(e);
13230 dispatch.on('tooltipShow', function(e) {
13231 if (tooltips) showTooltip(e, that.parentNode);
13234 // Update chart from a state object passed to event handler
13235 dispatch.on('changeState', function(e) {
13237 if (typeof e.disabled !== 'undefined') {
13238 data.forEach(function(series,i) {
13239 series.disabled = e.disabled[i];
13242 state.disabled = e.disabled;
13245 if (typeof e.fisheye !== 'undefined') {
13246 state.fisheye = e.fisheye;
13247 fisheye = e.fisheye;
13253 //============================================================
13256 //store old scales for use in transitions on update
13263 renderWatch.renderEnd('scatter with line immediate');
13267 //============================================================
13268 // Event Handling/Dispatching (out of chart's scope)
13269 //------------------------------------------------------------
13271 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13272 dispatch.tooltipHide(e);
13274 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
13276 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
13277 .attr('x2', distY.size());
13279 dispatch.on('tooltipHide', function() {
13280 if (tooltips) nv.tooltip.cleanup();
13283 //============================================================
13286 //============================================================
13287 // Expose Public Variables
13288 //------------------------------------------------------------
13290 // expose chart's sub-components
13291 chart.dispatch = dispatch;
13292 chart.scatter = scatter;
13293 chart.legend = legend;
13294 chart.controls = controls;
13295 chart.xAxis = xAxis;
13296 chart.yAxis = yAxis;
13297 chart.distX = distX;
13298 chart.distY = distY;
13300 // DO NOT DELETE. This is currently overridden below
13301 // until deprecated portions are removed.
13302 chart.state = state;
13304 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');
13306 chart.options = nv.utils.optionsFunc.bind(chart);
13308 chart.margin = function(_) {
13309 if (!arguments.length) return margin;
13310 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13311 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13312 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13313 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13317 chart.width = function(_) {
13318 if (!arguments.length) return width;
13323 chart.height = function(_) {
13324 if (!arguments.length) return height;
13329 chart.color = function(_) {
13330 if (!arguments.length) return color;
13331 color = nv.utils.getColor(_);
13332 legend.color(color);
13333 distX.color(color);
13334 distY.color(color);
13338 chart.showDistX = function(_) {
13339 if (!arguments.length) return showDistX;
13344 chart.showDistY = function(_) {
13345 if (!arguments.length) return showDistY;
13350 chart.showControls = function(_) {
13351 if (!arguments.length) return showControls;
13356 chart.showLegend = function(_) {
13357 if (!arguments.length) return showLegend;
13362 chart.showXAxis = function(_) {
13363 if (!arguments.length) return showXAxis;
13368 chart.showYAxis = function(_) {
13369 if (!arguments.length) return showYAxis;
13374 chart.rightAlignYAxis = function(_) {
13375 if(!arguments.length) return rightAlignYAxis;
13376 rightAlignYAxis = _;
13377 yAxis.orient( (_) ? 'right' : 'left');
13381 chart.fisheye = function(_) {
13382 if (!arguments.length) return fisheye;
13387 chart.tooltips = function(_) {
13388 if (!arguments.length) return tooltips;
13393 chart.tooltipContent = function(_) {
13394 if (!arguments.length) return tooltip;
13399 chart.tooltipXContent = function(_) {
13400 if (!arguments.length) return tooltipX;
13405 chart.tooltipYContent = function(_) {
13406 if (!arguments.length) return tooltipY;
13412 chart.state = function(_) {
13413 nv.deprecated('scatterPlusLineChart.state');
13414 if (!arguments.length) return state;
13418 for (var key in state) {
13419 chart.state[key] = state[key];
13423 chart.defaultState = function(_) {
13424 if (!arguments.length) return defaultState;
13429 chart.noData = function(_) {
13430 if (!arguments.length) return noData;
13435 chart.transitionDuration = function(_) {
13436 nv.deprecated('scatterPlusLineChart.transitionDuration');
13437 return chart.duration(_);
13440 chart.duration = function(_) {
13441 if (!arguments.length) return duration;
13446 //============================================================
13452 nv.models.sparkline = function() {
13454 //============================================================
13455 // Public Variables with Default Settings
13456 //------------------------------------------------------------
13458 var margin = {top: 2, right: 0, bottom: 2, left: 0}
13462 , x = d3.scale.linear()
13463 , y = d3.scale.linear()
13464 , getX = function(d) { return d.x }
13465 , getY = function(d) { return d.y }
13466 , color = nv.utils.getColor(['#000'])
13473 //============================================================
13476 function chart(selection) {
13477 selection.each(function(data) {
13478 var availableWidth = width - margin.left - margin.right,
13479 availableHeight = height - margin.top - margin.bottom,
13480 container = d3.select(this);
13481 nv.utils.initSVG(container);
13483 //------------------------------------------------------------
13486 x .domain(xDomain || d3.extent(data, getX ))
13487 .range(xRange || [0, availableWidth]);
13489 y .domain(yDomain || d3.extent(data, getY ))
13490 .range(yRange || [availableHeight, 0]);
13492 //------------------------------------------------------------
13495 //------------------------------------------------------------
13496 // Setup containers and skeleton of chart
13498 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
13499 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
13500 var gEnter = wrapEnter.append('g');
13501 var g = wrap.select('g');
13503 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
13505 //------------------------------------------------------------
13508 var paths = wrap.selectAll('path')
13509 .data(function(d) { return [d] });
13510 paths.enter().append('path');
13511 paths.exit().remove();
13513 .style('stroke', function(d,i) { return d.color || color(d, i) })
13514 .attr('d', d3.svg.line()
13515 .x(function(d,i) { return x(getX(d,i)) })
13516 .y(function(d,i) { return y(getY(d,i)) })
13520 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
13521 var points = wrap.selectAll('circle.nv-point')
13522 .data(function(data) {
13523 var yValues = data.map(function(d, i) { return getY(d,i); });
13524 function pointIndex(index) {
13526 var result = data[index];
13527 result.pointIndex = index;
13533 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
13534 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
13535 currentPoint = pointIndex(yValues.length - 1);
13536 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
13538 points.enter().append('circle');
13539 points.exit().remove();
13541 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
13542 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
13544 .attr('class', function(d,i) {
13545 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
13546 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
13554 //============================================================
13555 // Expose Public Variables
13556 //------------------------------------------------------------
13557 chart.options = nv.utils.optionsFunc.bind(chart);
13559 chart.margin = function(_) {
13560 if (!arguments.length) return margin;
13561 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13562 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13563 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13564 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13568 chart.width = function(_) {
13569 if (!arguments.length) return width;
13574 chart.height = function(_) {
13575 if (!arguments.length) return height;
13580 chart.x = function(_) {
13581 if (!arguments.length) return getX;
13582 getX = d3.functor(_);
13586 chart.y = function(_) {
13587 if (!arguments.length) return getY;
13588 getY = d3.functor(_);
13592 chart.xScale = function(_) {
13593 if (!arguments.length) return x;
13598 chart.yScale = function(_) {
13599 if (!arguments.length) return y;
13604 chart.xDomain = function(_) {
13605 if (!arguments.length) return xDomain;
13610 chart.yDomain = function(_) {
13611 if (!arguments.length) return yDomain;
13616 chart.xRange = function(_) {
13617 if (!arguments.length) return xRange;
13622 chart.yRange = function(_) {
13623 if (!arguments.length) return yRange;
13628 chart.animate = function(_) {
13629 if (!arguments.length) return animate;
13634 chart.color = function(_) {
13635 if (!arguments.length) return color;
13636 color = nv.utils.getColor(_);
13640 //============================================================
13646 nv.models.sparklinePlus = function() {
13648 //============================================================
13649 // Public Variables with Default Settings
13650 //------------------------------------------------------------
13652 var sparkline = nv.models.sparkline();
13654 var margin = {top: 15, right: 100, bottom: 10, left: 50}
13661 , xTickFormat = d3.format(',r')
13662 , yTickFormat = d3.format(',.2f')
13664 , alignValue = true
13665 , rightAlignValue = false
13666 , noData = "No Data Available."
13669 //============================================================
13672 function chart(selection) {
13673 selection.each(function(data) {
13674 var container = d3.select(this);
13675 nv.utils.initSVG(container);
13677 var availableWidth = (width || parseInt(container.style('width')) || 960)
13678 - margin.left - margin.right,
13679 availableHeight = (height || parseInt(container.style('height')) || 400)
13680 - margin.top - margin.bottom;
13684 chart.update = function() { chart(selection) };
13685 chart.container = this;
13688 //------------------------------------------------------------
13689 // Display No Data message if there's nothing to show.
13691 if (!data || !data.length) {
13692 var noDataText = container.selectAll('.nv-noData').data([noData]);
13694 noDataText.enter().append('text')
13695 .attr('class', 'nvd3 nv-noData')
13696 .attr('dy', '-.7em')
13697 .style('text-anchor', 'middle');
13700 .attr('x', margin.left + availableWidth / 2)
13701 .attr('y', margin.top + availableHeight / 2)
13702 .text(function(d) { return d });
13706 container.selectAll('.nv-noData').remove();
13709 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
13711 //------------------------------------------------------------
13715 //------------------------------------------------------------
13718 x = sparkline.xScale();
13719 y = sparkline.yScale();
13721 //------------------------------------------------------------
13724 //------------------------------------------------------------
13725 // Setup containers and skeleton of chart
13727 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
13728 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
13729 var gEnter = wrapEnter.append('g');
13730 var g = wrap.select('g');
13732 gEnter.append('g').attr('class', 'nv-sparklineWrap');
13733 gEnter.append('g').attr('class', 'nv-valueWrap');
13734 gEnter.append('g').attr('class', 'nv-hoverArea');
13736 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13738 //------------------------------------------------------------
13741 //------------------------------------------------------------
13742 // Main Chart Component(s)
13744 var sparklineWrap = g.select('.nv-sparklineWrap');
13747 .width(availableWidth)
13748 .height(availableHeight);
13753 //------------------------------------------------------------
13756 var valueWrap = g.select('.nv-valueWrap');
13758 var value = valueWrap.selectAll('.nv-currentValue')
13759 .data([currentValue]);
13761 value.enter().append('text').attr('class', 'nv-currentValue')
13762 .attr('dx', rightAlignValue ? -8 : 8)
13763 .attr('dy', '.9em')
13764 .style('text-anchor', rightAlignValue ? 'end' : 'start');
13767 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13768 .attr('y', alignValue ? function(d) { return y(d) } : 0)
13769 .style('fill', sparkline.color()(data[data.length-1], data.length-1))
13770 .text(yTickFormat(currentValue));
13774 gEnter.select('.nv-hoverArea').append('rect')
13775 .on('mousemove', sparklineHover)
13776 .on('click', function() { paused = !paused })
13777 .on('mouseout', function() { index = []; updateValueLine(); });
13778 //.on('mouseout', function() { index = null; updateValueLine(); });
13780 g.select('.nv-hoverArea rect')
13781 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13782 .attr('width', availableWidth + margin.left + margin.right)
13783 .attr('height', availableHeight + margin.top);
13787 function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13788 if (paused) return;
13790 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13792 var hoverEnter = hoverValue.enter()
13793 .append('g').attr('class', 'nv-hoverValue')
13794 .style('stroke-opacity', 0)
13795 .style('fill-opacity', 0);
13798 .transition().duration(250)
13799 .style('stroke-opacity', 0)
13800 .style('fill-opacity', 0)
13804 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13805 .transition().duration(250)
13806 .style('stroke-opacity', 1)
13807 .style('fill-opacity', 1);
13809 if (!index.length) return;
13811 hoverEnter.append('line')
13813 .attr('y1', -margin.top)
13815 .attr('y2', availableHeight);
13818 hoverEnter.append('text').attr('class', 'nv-xValue')
13820 .attr('y', -margin.top)
13821 .attr('text-anchor', 'end')
13822 .attr('dy', '.9em')
13825 g.select('.nv-hoverValue .nv-xValue')
13826 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13828 hoverEnter.append('text').attr('class', 'nv-yValue')
13830 .attr('y', -margin.top)
13831 .attr('text-anchor', 'start')
13832 .attr('dy', '.9em')
13834 g.select('.nv-hoverValue .nv-yValue')
13835 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13840 function sparklineHover() {
13841 if (paused) return;
13843 var pos = d3.mouse(this)[0] - margin.left;
13845 function getClosestIndex(data, x) {
13846 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13847 var closestIndex = 0;
13848 for (var i = 0; i < data.length; i++){
13849 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13850 distance = Math.abs(sparkline.x()(data[i], i) - x);
13854 return closestIndex;
13857 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13868 //============================================================
13869 // Expose Public Variables
13870 //------------------------------------------------------------
13872 // expose chart's sub-components
13873 chart.sparkline = sparkline;
13875 d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13877 chart.options = nv.utils.optionsFunc.bind(chart);
13879 chart.margin = function(_) {
13880 if (!arguments.length) return margin;
13881 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13882 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13883 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13884 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13888 chart.width = function(_) {
13889 if (!arguments.length) return width;
13894 chart.height = function(_) {
13895 if (!arguments.length) return height;
13900 chart.xTickFormat = function(_) {
13901 if (!arguments.length) return xTickFormat;
13906 chart.yTickFormat = function(_) {
13907 if (!arguments.length) return yTickFormat;
13912 chart.showValue = function(_) {
13913 if (!arguments.length) return showValue;
13918 chart.alignValue = function(_) {
13919 if (!arguments.length) return alignValue;
13924 chart.rightAlignValue = function(_) {
13925 if (!arguments.length) return rightAlignValue;
13926 rightAlignValue = _;
13930 chart.noData = function(_) {
13931 if (!arguments.length) return noData;
13936 //============================================================
13942 nv.models.stackedArea = function() {
13944 //============================================================
13945 // Public Variables with Default Settings
13946 //------------------------------------------------------------
13948 var margin = {top: 0, right: 0, bottom: 0, left: 0}
13951 , color = nv.utils.defaultColor() // a function that computes the color
13952 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13953 , getX = function(d) { return d.x } // accessor to get the x value from a data point
13954 , getY = function(d) { return d.y } // accessor to get the y value from a data point
13957 , order = 'default'
13958 , interpolate = 'linear' // controls the line interpolation
13959 , clipEdge = false // if true, masks lines within x and y scale
13960 , x //can be accessed via chart.xScale()
13961 , y //can be accessed via chart.yScale()
13962 , scatter = nv.models.scatter()
13964 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout','renderEnd')
13967 // scatter is interactive by default, but this chart isn't so must disable
13968 scatter.interactive(false);
13971 .size(2.2) // default size
13972 .sizeDomain([2.2,2.2]) // all the same size by default
13975 /************************************
13977 * 'wiggle' (stream)
13979 * 'expand' (normalize to 100%)
13980 * 'silhouette' (simple centered)
13983 * 'inside-out' (stream)
13984 * 'default' (input order)
13985 ************************************/
13987 //============================================================
13988 var renderWatch = nv.utils.renderWatch(dispatch, duration);
13991 function chart(selection) {
13992 renderWatch.reset();
13993 renderWatch.models(scatter);
13994 selection.each(function(data) {
13995 var availableWidth = width - margin.left - margin.right,
13996 availableHeight = height - margin.top - margin.bottom,
13997 container = d3.select(this);
13998 nv.utils.initSVG(container);
14000 //------------------------------------------------------------
14003 x = scatter.xScale();
14004 y = scatter.yScale();
14006 //------------------------------------------------------------
14008 var dataRaw = data;
14009 // Injecting point index into each point because d3.layout.stack().out does not give index
14010 data.forEach(function(aseries, i) {
14011 aseries.seriesIndex = i;
14012 aseries.values = aseries.values.map(function(d, j) {
14019 var dataFiltered = data.filter(function(series) {
14020 return !series.disabled;
14023 data = d3.layout.stack()
14026 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
14029 .out(function(d, y0, y) {
14030 var yHeight = (getY(d) === 0) ? 0 : y;
14039 //------------------------------------------------------------
14040 // Setup containers and skeleton of chart
14042 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
14043 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
14044 var defsEnter = wrapEnter.append('defs');
14045 var gEnter = wrapEnter.append('g');
14046 var g = wrap.select('g');
14048 gEnter.append('g').attr('class', 'nv-areaWrap');
14049 gEnter.append('g').attr('class', 'nv-scatterWrap');
14051 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
14053 //------------------------------------------------------------
14057 .width(availableWidth)
14058 .height(availableHeight)
14060 .y(function(d) { return d.display.y + d.display.y0 })
14062 .color(data.map(function(d,i) {
14063 return d.color || color(d, d.seriesIndex);
14067 var scatterWrap = g.select('.nv-scatterWrap')
14070 scatterWrap.call(scatter);
14072 defsEnter.append('clipPath')
14073 .attr('id', 'nv-edge-clip-' + id)
14076 wrap.select('#nv-edge-clip-' + id + ' rect')
14077 .attr('width', availableWidth)
14078 .attr('height', availableHeight);
14080 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
14082 var area = d3.svg.area()
14083 .x(function(d,i) { return x(getX(d,i)) })
14085 return y(d.display.y0)
14088 return y(d.display.y + d.display.y0)
14090 .interpolate(interpolate);
14092 var zeroArea = d3.svg.area()
14093 .x(function(d,i) { return x(getX(d,i)) })
14094 .y0(function(d) { return y(d.display.y0) })
14095 .y1(function(d) { return y(d.display.y0) });
14098 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
14099 .data(function(d) { return d });
14101 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
14102 .attr('d', function(d,i){
14103 return zeroArea(d.values, d.seriesIndex);
14105 .on('mouseover', function(d,i) {
14106 d3.select(this).classed('hover', true);
14107 dispatch.areaMouseover({
14110 pos: [d3.event.pageX, d3.event.pageY],
14111 seriesIndex: d.seriesIndex
14114 .on('mouseout', function(d,i) {
14115 d3.select(this).classed('hover', false);
14116 dispatch.areaMouseout({
14119 pos: [d3.event.pageX, d3.event.pageY],
14120 seriesIndex: d.seriesIndex
14123 .on('click', function(d,i) {
14124 d3.select(this).classed('hover', false);
14125 dispatch.areaClick({
14128 pos: [d3.event.pageX, d3.event.pageY],
14129 seriesIndex: d.seriesIndex
14133 path.exit().remove();
14136 .style('fill', function(d,i){
14137 return d.color || color(d, d.seriesIndex)
14139 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
14140 path.watchTransition(renderWatch,'stackedArea path')
14141 .attr('d', function(d,i) {
14142 return area(d.values,i)
14147 //============================================================
14148 // Event Handling/Dispatching (in chart's scope)
14149 //------------------------------------------------------------
14151 scatter.dispatch.on('elementMouseover.area', function(e) {
14152 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
14154 scatter.dispatch.on('elementMouseout.area', function(e) {
14155 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
14158 //============================================================
14159 //Special offset functions
14160 chart.d3_stackedOffset_stackPercent = function(stackData) {
14161 var n = stackData.length, //How many series
14162 m = stackData[0].length, //how many points per series
14169 for (j = 0; j < m; ++j) { //Looping through all points
14170 for (i = 0, o = 0; i < dataRaw.length; i++) //looping through series'
14171 o += getY(dataRaw[i].values[j]) //total value of all points at a certian point in time.
14173 if (o) for (i = 0; i < n; i++)
14174 stackData[i][j][1] /= o;
14176 for (i = 0; i < n; i++)
14177 stackData[i][j][1] = k;
14179 for (j = 0; j < m; ++j) y0[j] = 0;
14185 renderWatch.renderEnd('stackedArea immediate');
14190 //============================================================
14191 // Event Handling/Dispatching (out of chart's scope)
14192 //------------------------------------------------------------
14194 scatter.dispatch.on('elementClick.area', function(e) {
14195 dispatch.areaClick(e);
14197 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
14198 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
14199 dispatch.tooltipShow(e);
14201 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
14202 dispatch.tooltipHide(e);
14205 //============================================================
14207 //============================================================
14208 // Global getters and setters
14209 //------------------------------------------------------------
14211 chart.dispatch = dispatch;
14212 chart.scatter = scatter;
14214 d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
14215 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
14217 chart.options = nv.utils.optionsFunc.bind(chart);
14219 chart.x = function(_) {
14220 if (!arguments.length) return getX;
14221 getX = d3.functor(_);
14225 chart.y = function(_) {
14226 if (!arguments.length) return getY;
14227 getY = d3.functor(_);
14231 chart.margin = function(_) {
14232 if (!arguments.length) return margin;
14233 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
14234 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
14235 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14236 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
14240 chart.width = function(_) {
14241 if (!arguments.length) return width;
14246 chart.height = function(_) {
14247 if (!arguments.length) return height;
14252 chart.clipEdge = function(_) {
14253 if (!arguments.length) return clipEdge;
14258 chart.color = function(_) {
14259 if (!arguments.length) return color;
14260 color = nv.utils.getColor(_);
14264 chart.offset = function(_) {
14265 if (!arguments.length) return offset;
14270 chart.order = function(_) {
14271 if (!arguments.length) return order;
14276 //shortcut for offset + order
14277 chart.style = function(_) {
14278 if (!arguments.length) return style;
14283 chart.offset('zero');
14284 chart.order('default');
14287 chart.offset('wiggle');
14288 chart.order('inside-out');
14290 case 'stream-center':
14291 chart.offset('silhouette');
14292 chart.order('inside-out');
14295 chart.offset('expand');
14296 chart.order('default');
14298 case 'stack_percent':
14299 chart.offset(chart.d3_stackedOffset_stackPercent);
14300 chart.order('default');
14307 chart.interpolate = function(_) {
14308 if (!arguments.length) return interpolate;
14313 chart.duration = function(_) {
14314 if (!arguments.length) return duration;
14316 renderWatch.reset(duration);
14317 scatter.duration(duration);
14321 //============================================================
14327 nv.models.stackedAreaChart = function() {
14329 //============================================================
14330 // Public Variables with Default Settings
14331 //------------------------------------------------------------
14333 var stacked = nv.models.stackedArea()
14334 , xAxis = nv.models.axis()
14335 , yAxis = nv.models.axis()
14336 , legend = nv.models.legend()
14337 , controls = nv.models.legend()
14338 , interactiveLayer = nv.interactiveGuideline()
14341 var margin = {top: 30, right: 25, bottom: 50, left: 60}
14344 , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
14345 , showControls = true
14346 , showLegend = true
14349 , rightAlignYAxis = false
14350 , useInteractiveGuideline = false
14352 , tooltip = function(key, x, y, e, graph) {
14353 return '<h3>' + key + '</h3>' +
14354 '<p>' + y + ' on ' + x + '</p>'
14356 , x //can be accessed via chart.xScale()
14357 , y //can be accessed via chart.yScale()
14358 , yAxisTickFormat = d3.format(',.2f')
14359 , state = nv.utils.state()
14360 , defaultState = null
14361 , noData = 'No Data Available.'
14362 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
14363 , controlWidth = 250
14364 , cData = ['Stacked','Stream','Expanded']
14365 , controlLabels = {}
14369 state.style = stacked.style();
14376 .orient((rightAlignYAxis) ? 'right' : 'left')
14379 controls.updateState(false);
14380 //============================================================
14383 //============================================================
14384 // Private Variables
14385 //------------------------------------------------------------
14386 var renderWatch = nv.utils.renderWatch(dispatch);
14387 var style = stacked.style();
14389 var showTooltip = function(e, offsetElement) {
14390 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
14391 top = e.pos[1] + ( offsetElement.offsetTop || 0),
14392 x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
14393 y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
14394 content = tooltip(e.series.key, x, y, e, chart);
14396 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
14399 var stateGetter = function(data) {
14402 active: data.map(function(d) { return !d.disabled }),
14403 style: stacked.style()
14408 var stateSetter = function(data) {
14409 return function(state) {
14410 if (state.style !== undefined)
14411 style = state.style;
14412 if (state.active !== undefined)
14413 data.forEach(function(series,i) {
14414 series.disabled = !state.active[i];
14419 //============================================================
14422 function chart(selection) {
14423 renderWatch.reset();
14424 renderWatch.models(stacked);
14425 if (showXAxis) renderWatch.models(xAxis);
14426 if (showYAxis) renderWatch.models(yAxis);
14428 selection.each(function(data) {
14429 var container = d3.select(this),
14431 nv.utils.initSVG(container);
14433 var availableWidth = (width || parseInt(container.style('width')) || 960)
14434 - margin.left - margin.right,
14435 availableHeight = (height || parseInt(container.style('height')) || 400)
14436 - margin.top - margin.bottom;
14438 chart.update = function() { container.transition().duration(duration).call(chart); };
14439 chart.container = this;
14442 .setter(stateSetter(data), chart.update)
14443 .getter(stateGetter(data))
14446 // DEPRECATED set state.disabled
14447 state.disabled = data.map(function(d) { return !!d.disabled });
14449 if (!defaultState) {
14452 for (key in state) {
14453 if (state[key] instanceof Array)
14454 defaultState[key] = state[key].slice(0);
14456 defaultState[key] = state[key];
14460 //------------------------------------------------------------
14461 // Display No Data message if there's nothing to show.
14463 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
14464 var noDataText = container.selectAll('.nv-noData').data([noData]);
14466 noDataText.enter().append('text')
14467 .attr('class', 'nvd3 nv-noData')
14468 .attr('dy', '-.7em')
14469 .style('text-anchor', 'middle');
14472 .attr('x', margin.left + availableWidth / 2)
14473 .attr('y', margin.top + availableHeight / 2)
14474 .text(function(d) { return d });
14478 container.selectAll('.nv-noData').remove();
14481 //------------------------------------------------------------
14484 //------------------------------------------------------------
14487 x = stacked.xScale();
14488 y = stacked.yScale();
14490 //------------------------------------------------------------
14493 //------------------------------------------------------------
14494 // Setup containers and skeleton of chart
14496 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
14497 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
14498 var g = wrap.select('g');
14500 gEnter.append("rect").style("opacity",0);
14501 gEnter.append('g').attr('class', 'nv-x nv-axis');
14502 gEnter.append('g').attr('class', 'nv-y nv-axis');
14503 gEnter.append('g').attr('class', 'nv-stackedWrap');
14504 gEnter.append('g').attr('class', 'nv-legendWrap');
14505 gEnter.append('g').attr('class', 'nv-controlsWrap');
14506 gEnter.append('g').attr('class', 'nv-interactive');
14508 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
14509 //------------------------------------------------------------
14513 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
14515 .width(legendWidth);
14517 g.select('.nv-legendWrap')
14521 if ( margin.top != legend.height()) {
14522 margin.top = legend.height();
14523 availableHeight = (height || parseInt(container.style('height')) || 400)
14524 - margin.top - margin.bottom;
14527 g.select('.nv-legendWrap')
14528 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
14531 //------------------------------------------------------------
14534 //------------------------------------------------------------
14537 if (showControls) {
14538 var controlsData = [
14540 key: controlLabels.stacked || 'Stacked',
14541 metaKey: 'Stacked',
14542 disabled: stacked.style() != 'stack',
14546 key: controlLabels.stream || 'Stream',
14548 disabled: stacked.style() != 'stream',
14552 key: controlLabels.expanded || 'Expanded',
14553 metaKey: 'Expanded',
14554 disabled: stacked.style() != 'expand',
14558 key: controlLabels.stack_percent || 'Stack %',
14559 metaKey: 'Stack_Percent',
14560 disabled: stacked.style() != 'stack_percent',
14561 style: 'stack_percent'
14565 controlWidth = (cData.length/3) * 260;
14567 controlsData = controlsData.filter(function(d) {
14568 return cData.indexOf(d.metaKey) !== -1;
14572 .width( controlWidth )
14573 .color(['#444', '#444', '#444']);
14575 g.select('.nv-controlsWrap')
14576 .datum(controlsData)
14580 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
14581 margin.top = Math.max(controls.height(), legend.height());
14582 availableHeight = (height || parseInt(container.style('height')) || 400)
14583 - margin.top - margin.bottom;
14587 g.select('.nv-controlsWrap')
14588 .attr('transform', 'translate(0,' + (-margin.top) +')');
14591 //------------------------------------------------------------
14594 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
14596 if (rightAlignYAxis) {
14597 g.select(".nv-y.nv-axis")
14598 .attr("transform", "translate(" + availableWidth + ",0)");
14601 //------------------------------------------------------------
14602 // Main Chart Component(s)
14604 //------------------------------------------------------------
14605 //Set up interactive layer
14606 if (useInteractiveGuideline) {
14608 .width(availableWidth)
14609 .height(availableHeight)
14610 .margin({left: margin.left, top: margin.top})
14611 .svgContainer(container)
14613 wrap.select(".nv-interactive").call(interactiveLayer);
14617 .width(availableWidth)
14618 .height(availableHeight);
14620 var stackedWrap = g.select('.nv-stackedWrap')
14623 stackedWrap.transition().call(stacked);
14625 //------------------------------------------------------------
14628 //------------------------------------------------------------
14634 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
14635 .tickSize( -availableHeight, 0);
14637 g.select('.nv-x.nv-axis')
14638 .attr('transform', 'translate(0,' + availableHeight + ')');
14640 g.select('.nv-x.nv-axis')
14641 .transition().duration(0)
14648 .ticks(stacked.offset() == 'wiggle' ? 0 : nv.utils.calcTicksY(availableHeight/36, data) )
14649 .tickSize(-availableWidth, 0)
14650 .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
14651 ? d3.format('%') : yAxisTickFormat);
14653 g.select('.nv-y.nv-axis')
14654 .transition().duration(0)
14658 //------------------------------------------------------------
14661 //============================================================
14662 // Event Handling/Dispatching (in chart's scope)
14663 //------------------------------------------------------------
14665 stacked.dispatch.on('areaClick.toggle', function(e) {
14666 if (data.filter(function(d) { return !d.disabled }).length === 1)
14667 data.forEach(function(d) {
14668 d.disabled = false;
14671 data.forEach(function(d,i) {
14672 d.disabled = (i != e.seriesIndex);
14675 state.disabled = data.map(function(d) { return !!d.disabled });
14676 dispatch.stateChange(state);
14681 legend.dispatch.on('stateChange', function(newState) {
14682 for (var key in newState)
14683 state[key] = newState[key];
14684 dispatch.stateChange(state);
14688 controls.dispatch.on('legendClick', function(d,i) {
14689 if (!d.disabled) return;
14691 controlsData = controlsData.map(function(s) {
14695 d.disabled = false;
14697 stacked.style(d.style);
14700 state.style = stacked.style();
14701 dispatch.stateChange(state);
14707 interactiveLayer.dispatch.on('elementMousemove', function(e) {
14708 stacked.clearHighlights();
14709 var singlePoint, pointIndex, pointXLocation, allData = [];
14711 .filter(function(series, i) {
14712 series.seriesIndex = i;
14713 return !series.disabled;
14715 .forEach(function(series,i) {
14716 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
14717 stacked.highlightPoint(i, pointIndex, true);
14718 var point = series.values[pointIndex];
14719 if (typeof point === 'undefined') return;
14720 if (typeof singlePoint === 'undefined') singlePoint = point;
14721 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
14723 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
14724 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
14727 value: tooltipValue,
14728 color: color(series,series.seriesIndex),
14729 stackedValue: point.display
14735 //Highlight the tooltip entry based on which stack the mouse is closest to.
14736 if (allData.length > 2) {
14737 var yValue = chart.yScale().invert(e.mouseY);
14738 var yDistMax = Infinity, indexToHighlight = null;
14739 allData.forEach(function(series,i) {
14741 //To handle situation where the stacked area chart is negative, we need to use absolute values
14742 //when checking if the mouse Y value is within the stack area.
14743 yValue = Math.abs(yValue);
14744 var stackedY0 = Math.abs(series.stackedValue.y0);
14745 var stackedY = Math.abs(series.stackedValue.y);
14746 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
14748 indexToHighlight = i;
14752 if (indexToHighlight != null)
14753 allData[indexToHighlight].highlight = true;
14756 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
14758 //If we are in 'expand' mode, force the format to be a percentage.
14759 var valueFormatter = (stacked.style() == 'expand') ?
14760 function(d,i) {return d3.format(".1%")(d);} :
14761 function(d,i) {return yAxis.tickFormat()(d); };
14762 interactiveLayer.tooltip
14763 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
14764 .chartContainer(that.parentNode)
14766 .valueFormatter(valueFormatter)
14774 interactiveLayer.renderGuideLine(pointXLocation);
14778 interactiveLayer.dispatch.on("elementMouseout",function(e) {
14779 dispatch.tooltipHide();
14780 stacked.clearHighlights();
14784 dispatch.on('tooltipShow', function(e) {
14785 if (tooltips) showTooltip(e, that.parentNode);
14788 // Update chart from a state object passed to event handler
14789 dispatch.on('changeState', function(e) {
14791 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
14792 data.forEach(function(series,i) {
14793 series.disabled = e.disabled[i];
14796 state.disabled = e.disabled;
14799 if (typeof e.style !== 'undefined') {
14800 stacked.style(e.style);
14809 renderWatch.renderEnd('stacked Area chart immediate');
14814 //============================================================
14815 // Event Handling/Dispatching (out of chart's scope)
14816 //------------------------------------------------------------
14818 stacked.dispatch.on('tooltipShow', function(e) {
14819 //disable tooltips when value ~= 0
14820 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
14822 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
14823 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
14828 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
14829 dispatch.tooltipShow(e);
14832 stacked.dispatch.on('tooltipHide', function(e) {
14833 dispatch.tooltipHide(e);
14836 dispatch.on('tooltipHide', function() {
14837 if (tooltips) nv.tooltip.cleanup();
14840 //============================================================
14843 //============================================================
14844 // Expose Public Variables
14845 //------------------------------------------------------------
14847 // expose chart's sub-components
14848 chart.dispatch = dispatch;
14849 chart.stacked = stacked;
14850 chart.legend = legend;
14851 chart.controls = controls;
14852 chart.xAxis = xAxis;
14853 chart.yAxis = yAxis;
14854 chart.interactiveLayer = interactiveLayer;
14856 // DO NOT DELETE. This is currently overridden below
14857 // until deprecated portions are removed.
14858 chart.state = state;
14860 d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
14862 chart.options = nv.utils.optionsFunc.bind(chart);
14864 chart.margin = function(_) {
14865 if (!arguments.length) return margin;
14866 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
14867 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
14868 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14869 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
14873 chart.width = function(_) {
14874 if (!arguments.length) return width;
14879 chart.height = function(_) {
14880 if (!arguments.length) return height;
14885 chart.color = function(_) {
14886 if (!arguments.length) return color;
14887 color = nv.utils.getColor(_);
14888 legend.color(color);
14889 stacked.color(color);
14893 chart.showControls = function(_) {
14894 if (!arguments.length) return showControls;
14899 chart.showLegend = function(_) {
14900 if (!arguments.length) return showLegend;
14905 chart.showXAxis = function(_) {
14906 if (!arguments.length) return showXAxis;
14911 chart.showYAxis = function(_) {
14912 if (!arguments.length) return showYAxis;
14917 chart.rightAlignYAxis = function(_) {
14918 if(!arguments.length) return rightAlignYAxis;
14919 rightAlignYAxis = _;
14920 yAxis.orient( (_) ? 'right' : 'left');
14924 chart.useInteractiveGuideline = function(_) {
14925 if(!arguments.length) return useInteractiveGuideline;
14926 useInteractiveGuideline = _;
14928 chart.interactive(false);
14929 chart.useVoronoi(false);
14934 chart.tooltip = function(_) {
14935 if (!arguments.length) return tooltip;
14940 chart.tooltips = function(_) {
14941 if (!arguments.length) return tooltips;
14946 chart.tooltipContent = function(_) {
14947 if (!arguments.length) return tooltip;
14953 chart.state = function(_) {
14954 nv.deprecated('stackedAreaChart.state');
14955 if (!arguments.length) return state;
14959 for (var key in state) {
14960 chart.state[key] = state[key];
14964 chart.defaultState = function(_) {
14965 if (!arguments.length) return defaultState;
14970 chart.noData = function(_) {
14971 if (!arguments.length) return noData;
14976 chart.transitionDuration = function(_) {
14977 nv.deprecated('lineChart.transitionDuration');
14978 return chart.duration(_);
14981 chart.controlsData = function(_) {
14982 if (!arguments.length) return cData;
14987 chart.controlLabels = function(_) {
14988 if (!arguments.length) return controlLabels;
14989 if (typeof _ !== 'object') return controlLabels;
14994 yAxis.setTickFormat = yAxis.tickFormat;
14996 yAxis.tickFormat = function(_) {
14997 if (!arguments.length) return yAxisTickFormat;
14998 yAxisTickFormat = _;
15002 chart.duration = function(_) {
15003 if (!arguments.length) return duration;
15005 renderWatch.reset(duration);
15006 stacked.duration(duration);
15007 xAxis.duration(duration);
15008 yAxis.duration(duration);
15013 //============================================================
15018 nv.version = "1.6.0";